diff --git a/build.gradle b/build.gradle
index b67c602688..b457d6f03b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ subprojects {
apply plugin: 'maven-publish'
group 'ru.tinkoff.qa.neptune'
- version '0.1.8-ALPHA'
+ version '0.1.9-ALPHA'
sourceCompatibility = 1.9
targetCompatibility = 1.9
diff --git a/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/CompositeKey.java b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/CompositeKey.java
new file mode 100644
index 0000000000..0ecee4a538
--- /dev/null
+++ b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/CompositeKey.java
@@ -0,0 +1,55 @@
+package ru.tinkoff.qa.neptune.data.base.api;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.lang.reflect.Modifier.isStatic;
+import static java.util.Arrays.stream;
+
+/**
+ * This class is designed to implement classes of composite key objects.
+ * It is supposed to be used at the case below
+ * {@code @PersistenceCapable(table = "TABLE_NAME", objectIdClass = CompositeKeySubclass.class)}
+ *
+ * WARNING: it is necessary to override following methods:
+ *
{@link #hashCode()}
+ * {@link #equals(Object)}
+ * {@link #toString()}
+ *
+ * It is enough
+ *
+ * {@code 'public int hashCode() {
+ * return super.hashCode();
+ * }'}
+ *
+ */
+public abstract class CompositeKey extends OrmObject implements Serializable {
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ Class> clazz = this.getClass();
+ while (!clazz.equals(Object.class)) {
+ List fields = stream(clazz.getDeclaredFields()).filter(field -> !isStatic(field.getModifiers()))
+ .collect(Collectors.toList());
+ for (Field f : fields) {
+ f.setAccessible(true);
+ try {
+ Object v = f.get(this);
+ if (v == null) {
+ continue;
+ }
+ result = result ^ v.hashCode();
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+
+ return result;
+ }
+}
diff --git a/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/OrmObject.java b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/OrmObject.java
new file mode 100644
index 0000000000..64a5338c99
--- /dev/null
+++ b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/OrmObject.java
@@ -0,0 +1,60 @@
+package ru.tinkoff.qa.neptune.data.base.api;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.lang.reflect.Modifier.isStatic;
+import static java.util.Arrays.stream;
+import static java.util.Optional.ofNullable;
+
+class OrmObject {
+
+ private boolean equalsByFields(Object obj) {
+ Class> clazz = this.getClass();
+ while (!clazz.equals(Object.class)) {
+ List fields = stream(clazz.getDeclaredFields()).filter(field -> !isStatic(field.getModifiers()))
+ .collect(Collectors.toList());
+ for (Field f: fields) {
+ f.setAccessible(true);
+ try {
+ Object v1 = f.get(this);
+ Object v2 = f.get(obj);
+
+ if (v1 == null && v2 == null) {
+ continue;
+ }
+
+ if ((v1 != null && v2 == null) || (v1 == null && v2 != null)) {
+ return false;
+ }
+
+ if (v1.equals(v2)) {
+ continue;
+ }
+
+ return false;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ OrmObject toCheck = this;
+ return ofNullable(obj)
+ .map(o -> {
+ if (toCheck == o) {
+ return true;
+ }
+ return toCheck.getClass().equals(obj.getClass()) &&
+ toCheck.equalsByFields(obj);
+ })
+ .orElse(false);
+ }
+}
diff --git a/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/PersistableObject.java b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/PersistableObject.java
index e22c82bc90..52b02a26ff 100644
--- a/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/PersistableObject.java
+++ b/data.base.api/src/main/java/ru/tinkoff/qa/neptune/data/base/api/PersistableObject.java
@@ -2,69 +2,10 @@
import com.google.gson.Gson;
-import java.lang.reflect.Field;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static java.lang.reflect.Modifier.isStatic;
-import static java.util.Arrays.stream;
-import static java.util.Optional.ofNullable;
-
/**
* This abstract class is designed to mark persistable classes.
*/
-public abstract class PersistableObject implements Cloneable {
-
- private boolean equalsByFields(Object obj) {
- Class> clazz = this.getClass();
- while (!clazz.equals(PersistableObject.class)) {
- List fields = stream(clazz.getDeclaredFields()).filter(field -> !isStatic(field.getModifiers()))
- .collect(Collectors.toList());
- for (Field f: fields) {
- f.setAccessible(true);
- try {
- Object v1 = f.get(this);
- Object v2 = f.get(obj);
-
- if (v1 == null && v2 == null) {
- continue;
- }
-
- if ((v1 != null && v2 == null) || (v1 == null && v2 != null)) {
- return false;
- }
-
- if (v1.equals(v2)) {
- continue;
- }
-
- return false;
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
- }
- clazz = clazz.getSuperclass();
- }
-
- return true;
- }
-
- @Override
- public boolean equals(Object obj) {
- PersistableObject toCheck = this;
- return ofNullable(obj)
- .map(o -> {
- if (toCheck == o) {
- return true;
- }
- else if (!PersistableObject.class.isAssignableFrom(obj.getClass())) {
- return false;
- }
- return toCheck.getClass().equals(obj.getClass()) &&
- toCheck.equalsByFields(obj);
- })
- .orElse(false);
- }
+public abstract class PersistableObject extends OrmObject implements Cloneable {
@Override
public String toString() {