You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2015/11/23 12:21:45 UTC

[3/4] ignite git commit: IGNITE-1753 Refactored usages of deprectaed CacheTypeMetadata to JdbcType.

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStore.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStore.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStore.java
index d78ea48..aa013b9 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStore.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStore.java
@@ -21,226 +21,392 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import javax.cache.CacheException;
 import javax.cache.integration.CacheLoaderException;
-import org.apache.ignite.cache.CacheTypeFieldMetadata;
-import org.apache.ignite.cache.CacheTypeMetadata;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.binary.BinaryObjectBuilder;
 import org.apache.ignite.cache.store.CacheStore;
 import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
 
 /**
  * Implementation of {@link CacheStore} backed by JDBC and POJO via reflection.
  *
- * This implementation stores objects in underlying database using java beans mapping description via reflection.
- * <p>
+ * This implementation stores objects in underlying database using java beans mapping description via reflection. <p>
  * Use {@link CacheJdbcPojoStoreFactory} factory to pass {@link CacheJdbcPojoStore} to {@link CacheConfiguration}.
  */
 public class CacheJdbcPojoStore<K, V> extends CacheAbstractJdbcStore<K, V> {
+    /** POJO methods cache. */
+    private volatile Map<String, Map<String, PojoMethodsCache>> pojosMthds = Collections.emptyMap();
+
     /**
-     * POJO methods cache.
+     * Get field value from object for use as query parameter.
+     *
+     * @param cacheName Cache name.
+     * @param typeName Type name.
+     * @param fldName Field name.
+     * @param obj Cache object.
+     * @return Field value from object.
+     * @throws CacheException in case of error.
      */
-    protected static class PojoMethodsCache {
-        /** POJO class. */
-        protected final Class<?> cls;
-
-        /** Constructor for POJO object. */
-        private Constructor ctor;
-
-        /** {@code true} if object is a simple type. */
-        private final boolean simple;
-
-        /** Cached setters for POJO object. */
-        private Map<String, Method> getters;
-
-        /** Cached getters for POJO object. */
-        private Map<String, Method> setters;
-
-        /**
-         * POJO methods cache.
-         *
-         * @param clsName Class name.
-         * @param fields Fields.
-         *
-         * @throws CacheException If failed to construct type cache.
-         */
-        public PojoMethodsCache(String clsName, Collection<CacheTypeFieldMetadata> fields) throws CacheException {
-            try {
-                cls = Class.forName(clsName);
-
-                if (simple = simpleType(cls))
-                    return;
+    @Override @Nullable protected Object extractParameter(@Nullable String cacheName, String typeName, TypeKind typeKind,
+        String fldName, Object obj) throws CacheException {
+        switch (typeKind) {
+            case BUILT_IN:
+                return obj;
+            case POJO:
+                return extractPojoParameter(cacheName, typeName, fldName, obj);
+            default:
+                return extractBinaryParameter(fldName, obj);
+        }
+    }
 
-                ctor = cls.getDeclaredConstructor();
+    /**
+     * Get field value from POJO for use as query parameter.
+     *
+     * @param cacheName Cache name.
+     * @param typeName Type name.
+     * @param fldName Field name.
+     * @param obj Cache object.
+     * @return Field value from object.
+     * @throws CacheException in case of error.
+     */
+    @Nullable private Object extractPojoParameter(@Nullable String cacheName, String typeName, String fldName,
+        Object obj) throws CacheException {
+        try {
+            Map<String, PojoMethodsCache> cacheMethods = pojosMthds.get(cacheName);
 
-                if (!ctor.isAccessible())
-                    ctor.setAccessible(true);
-            }
-            catch (ClassNotFoundException e) {
-                throw new CacheException("Failed to find class: " + clsName, e);
-            }
-            catch (NoSuchMethodException e) {
-                throw new CacheException("Failed to find default constructor for class: " + clsName, e);
-            }
+            if (cacheMethods == null)
+                throw new CacheException("Failed to find POJO type metadata for cache: " + U.maskName(cacheName));
 
-            setters = U.newHashMap(fields.size());
+            PojoMethodsCache mc = cacheMethods.get(typeName);
 
-            getters = U.newHashMap(fields.size());
+            if (mc == null)
+                throw new CacheException("Failed to find POJO type metadata for type: " + typeName);
 
-            for (CacheTypeFieldMetadata field : fields) {
-                String prop = capitalFirst(field.getJavaName());
+            Method getter = mc.getters.get(fldName);
 
-                try {
-                    getters.put(field.getJavaName(), cls.getMethod("get" + prop));
-                }
-                catch (NoSuchMethodException ignored) {
-                    try {
-                        getters.put(field.getJavaName(), cls.getMethod("is" + prop));
-                    }
-                    catch (NoSuchMethodException e) {
-                        throw new CacheException("Failed to find getter in POJO class [clsName=" + clsName +
-                            ", prop=" + field.getJavaName() + "]", e);
-                    }
-                }
+            if (getter == null)
+                throw new CacheLoaderException("Failed to find getter in POJO class [class=" + typeName +
+                    ", prop=" + fldName + "]");
 
-                try {
-                    setters.put(field.getJavaName(), cls.getMethod("set" + prop, field.getJavaType()));
-                }
-                catch (NoSuchMethodException e) {
-                    throw new CacheException("Failed to find setter in POJO class [clsName=" + clsName +
-                        ", prop=" + field.getJavaName() + "]", e);
-                }
-            }
+            return getter.invoke(obj);
         }
-
-        /**
-         * Capitalizes the first character of the given string.
-         *
-         * @param str String.
-         * @return String with capitalized first character.
-         */
-        @Nullable private String capitalFirst(@Nullable String str) {
-            return str == null ? null :
-                str.isEmpty() ? "" : Character.toUpperCase(str.charAt(0)) + str.substring(1);
+        catch (Exception e) {
+            throw new CacheException("Failed to read object of class: " + typeName, e);
         }
     }
 
-    /** Methods cache. */
-    protected volatile Map<String, Map<String, PojoMethodsCache>> mtdsCache = Collections.emptyMap();
+    /**
+     * Get field value from Binary object for use as query parameter.
+     *
+     * @param fieldName Field name to extract query parameter for.
+     * @param obj Object to process.
+     * @return Field value from object.
+     * @throws CacheException in case of error.
+     */
+    private Object extractBinaryParameter(String fieldName, Object obj) throws CacheException {
+        if (obj instanceof BinaryObject)
+            return ((BinaryObject)obj).field(fieldName);
+
+        throw new CacheException("Failed to read property value from non binary object [class=" +
+            obj.getClass() + ", property=" + fieldName + "]");
+    }
 
     /** {@inheritDoc} */
-    @Override protected void prepareBuilders(@Nullable String cacheName, Collection<CacheTypeMetadata> types)
-        throws CacheException {
-        Map<String, PojoMethodsCache> typeMethods = U.newHashMap(types.size() * 2);
+    @Override protected <R> R buildObject(@Nullable String cacheName, String typeName, TypeKind typeKind,
+        JdbcTypeField[] flds, Collection<String> hashFlds, Map<String, Integer> loadColIdxs, ResultSet rs)
+        throws CacheLoaderException {
+        switch (typeKind) {
+            case BUILT_IN:
+                return (R)buildBuiltinObject(typeName, flds, loadColIdxs, rs);
+            case POJO:
+                return (R)buildPojoObject(cacheName, typeName, flds, loadColIdxs, rs);
+            default:
+                return (R)buildBinaryObject(typeName, flds, hashFlds, loadColIdxs, rs);
+        }
+    }
 
-        for (CacheTypeMetadata type : types) {
-            String keyType = type.getKeyType();
-            typeMethods.put(keyType, new PojoMethodsCache(keyType, type.getKeyFields()));
+    /**
+     * Construct Java built in object from query result.
+     *
+     * @param typeName Type name.
+     * @param fields Fields descriptors.
+     * @param loadColIdxs Select query columns indexes.
+     * @param rs ResultSet to take data from.
+     * @return Constructed object.
+     * @throws CacheLoaderException If failed to construct POJO.
+     */
+    private Object buildBuiltinObject(String typeName, JdbcTypeField[] fields, Map<String, Integer> loadColIdxs,
+        ResultSet rs) throws CacheLoaderException {
+        try {
+            JdbcTypeField field = fields[0];
 
-            String valType = type.getValueType();
-            typeMethods.put(valType, new PojoMethodsCache(valType, type.getValueFields()));
+            return getColumnValue(rs, loadColIdxs.get(field.getDatabaseFieldName()), field.getJavaFieldType());
         }
+        catch (SQLException e) {
+            throw new CacheLoaderException("Failed to read object of class: " + typeName, e);
+        }
+    }
 
-        Map<String, Map<String, PojoMethodsCache>> newMtdsCache = new HashMap<>(mtdsCache);
+    /**
+     * Construct POJO from query result.
+     *
+     * @param cacheName Cache name.
+     * @param typeName Type name.
+     * @param flds Fields descriptors.
+     * @param loadColIdxs Select query columns index.
+     * @param rs ResultSet.
+     * @return Constructed POJO.
+     * @throws CacheLoaderException If failed to construct POJO.
+     */
+    private Object buildPojoObject(@Nullable String cacheName, String typeName,
+        JdbcTypeField[] flds, Map<String, Integer> loadColIdxs, ResultSet rs)
+        throws CacheLoaderException {
 
-        newMtdsCache.put(cacheName, typeMethods);
+        Map<String, PojoMethodsCache> cacheMethods = pojosMthds.get(cacheName);
 
-        mtdsCache = newMtdsCache;
-    }
+        if (cacheMethods == null)
+            throw new CacheLoaderException("Failed to find POJO types metadata for cache: " + U.maskName(cacheName));
 
-    /** {@inheritDoc} */
-    @Override protected <R> R buildObject(String cacheName, String typeName, Collection<CacheTypeFieldMetadata> fields,
-        Map<String, Integer> loadColIdxs, ResultSet rs) throws CacheLoaderException {
-        PojoMethodsCache mc = mtdsCache.get(cacheName).get(typeName);
+        PojoMethodsCache mc = cacheMethods.get(typeName);
 
         if (mc == null)
-            throw new CacheLoaderException("Failed to find cache type metadata for type: " + typeName);
+            throw new CacheLoaderException("Failed to find POJO type metadata for type: " + typeName);
 
         try {
-            if (mc.simple) {
-                CacheTypeFieldMetadata field = F.first(fields);
-
-                return (R)getColumnValue(rs, loadColIdxs.get(field.getDatabaseName()), mc.cls);
-            }
-
             Object obj = mc.ctor.newInstance();
 
-            for (CacheTypeFieldMetadata field : fields) {
-                String fldJavaName = field.getJavaName();
+            for (JdbcTypeField fld : flds) {
+                String fldJavaName = fld.getJavaFieldName();
 
                 Method setter = mc.setters.get(fldJavaName);
 
                 if (setter == null)
-                    throw new IllegalStateException("Failed to find setter in POJO class [clsName=" + typeName +
+                    throw new IllegalStateException("Failed to find setter in POJO class [type=" + typeName +
                         ", prop=" + fldJavaName + "]");
 
-                String fldDbName = field.getDatabaseName();
+                String fldDbName = fld.getDatabaseFieldName();
 
                 Integer colIdx = loadColIdxs.get(fldDbName);
 
                 try {
-                    setter.invoke(obj, getColumnValue(rs, colIdx, field.getJavaType()));
+                    Object colVal = getColumnValue(rs, colIdx, fld.getJavaFieldType());
+
+                    try {
+                        setter.invoke(obj, colVal);
+                    }
+                    catch (Exception e) {
+                        throw new CacheLoaderException("Failed to set property in POJO class [type=" + typeName +
+                            ", prop=" + fldJavaName + ", col=" + colIdx + ", dbName=" + fldDbName + "]", e);
+                    }
                 }
-                catch (Exception e) {
-                    throw new IllegalStateException("Failed to set property in POJO class [clsName=" + typeName +
+                catch (SQLException e) {
+                    throw new CacheLoaderException("Failed to read object property [type= " + typeName +
                         ", prop=" + fldJavaName + ", col=" + colIdx + ", dbName=" + fldDbName + "]", e);
                 }
             }
 
-            return (R)obj;
-        }
-        catch (SQLException e) {
-            throw new CacheLoaderException("Failed to read object of class: " + typeName, e);
+            return obj;
         }
         catch (Exception e) {
             throw new CacheLoaderException("Failed to construct instance of class: " + typeName, e);
         }
     }
 
-    /** {@inheritDoc} */
-    @Nullable @Override protected Object extractParameter(String cacheName, String typeName, String fieldName,
-        Object obj)
-        throws CacheException {
+    /**
+     * Construct binary object from query result.
+     *
+     * @param typeName Type name.
+     * @param fields Fields descriptors.
+     * @param hashFields Collection of fields to build hash for.
+     * @param loadColIdxs Select query columns index.
+     * @param rs ResultSet.
+     * @return Constructed binary object.
+     * @throws CacheLoaderException If failed to construct binary object.
+     */
+    protected Object buildBinaryObject(String typeName, JdbcTypeField[] fields,
+        Collection<String> hashFields, Map<String, Integer> loadColIdxs, ResultSet rs) throws CacheLoaderException {
         try {
-            PojoMethodsCache mc = mtdsCache.get(cacheName).get(typeName);
+            BinaryObjectBuilder builder = ignite.binary().builder(typeName);
 
-            if (mc == null)
-                throw new CacheException("Failed to find cache type metadata for type: " + typeName);
+            boolean calcHash = hashFields != null;
 
-            if (mc.simple)
-                return obj;
+            Collection<Object> hashValues = calcHash ? new ArrayList<>(hashFields.size()) : null;
 
-            Method getter = mc.getters.get(fieldName);
+            for (JdbcTypeField field : fields) {
+                Integer colIdx = loadColIdxs.get(field.getDatabaseFieldName());
 
-            if (getter == null)
-                throw new CacheLoaderException("Failed to find getter in POJO class [clsName=" + typeName +
-                    ", prop=" + fieldName + "]");
+                Object colVal = getColumnValue(rs, colIdx, field.getJavaFieldType());
 
-            return getter.invoke(obj);
+                builder.setField(field.getJavaFieldName(), colVal);
+
+                if (calcHash)
+                    hashValues.add(colVal);
+            }
+
+            if (calcHash)
+                builder.hashCode(hasher.hashCode(hashValues));
+
+            return builder.build();
         }
-        catch (Exception e) {
-            throw new CacheException("Failed to read object of class: " + typeName, e);
+        catch (SQLException e) {
+            throw new CacheException("Failed to read binary object", e);
         }
     }
 
-    /** {@inheritDoc} */
-    @Override protected Object keyTypeId(Object key) throws CacheException {
-        return key.getClass();
+    /**
+     * Calculate type ID for object.
+     *
+     * @param obj Object to calculate type ID for.
+     * @return Type ID.
+     * @throws CacheException If failed to calculate type ID for given object.
+     */
+    @Override protected Object typeIdForObject(Object obj) throws CacheException {
+        if (obj instanceof BinaryObject)
+            return ((BinaryObject)obj).typeId();
+
+        return obj.getClass();
     }
 
     /** {@inheritDoc} */
-    @Override protected Object keyTypeId(String type) throws CacheException {
+    @Override protected Object typeIdForTypeName(TypeKind kind, String typeName) throws CacheException {
+        if (kind == TypeKind.BINARY)
+            return ignite.binary().typeId(typeName);
+
         try {
-            return Class.forName(type);
+            return Class.forName(typeName);
         }
         catch (ClassNotFoundException e) {
-            throw new CacheException("Failed to find class: " + type, e);
+            throw new CacheException("Failed to find class: " + typeName, e);
+        }
+    }
+
+    /**
+     * Prepare internal store specific builders for provided types metadata.
+     *
+     * @param cacheName Cache name to prepare builders for.
+     * @param types Collection of types.
+     * @throws CacheException If failed to prepare internal builders for types.
+     */
+    @Override protected void prepareBuilders(@Nullable String cacheName, Collection<JdbcType> types)
+        throws CacheException {
+        Map<String, PojoMethodsCache> pojoMethods = U.newHashMap(types.size() * 2);
+
+        for (JdbcType type : types) {
+            String keyTypeName = type.getKeyType();
+
+            TypeKind keyKind = kindForName(keyTypeName);
+
+            if (keyKind == TypeKind.POJO) {
+                if (pojoMethods.containsKey(keyTypeName))
+                    throw new CacheException("Found duplicate key type [cache=" + U.maskName(cacheName) +
+                        ", keyType=" + keyTypeName + "]");
+
+                pojoMethods.put(keyTypeName, new PojoMethodsCache(keyTypeName, type.getKeyFields()));
+            }
+
+            String valTypeName = type.getValueType();
+
+            TypeKind valKind = kindForName(valTypeName);
+
+            if (valKind == TypeKind.POJO)
+                pojoMethods.put(valTypeName, new PojoMethodsCache(valTypeName, type.getValueFields()));
+        }
+
+        if (!pojoMethods.isEmpty()) {
+            Map<String, Map<String, PojoMethodsCache>> newPojosMethods = new HashMap<>(pojosMthds);
+
+            newPojosMethods.put(cacheName, pojoMethods);
+
+            pojosMthds = newPojosMethods;
+        }
+    }
+
+    /**
+     * POJO methods cache.
+     */
+    private static class PojoMethodsCache {
+        /** POJO class. */
+        private final Class<?> cls;
+
+        /** Constructor for POJO object. */
+        private Constructor ctor;
+
+        /** Cached setters for POJO object. */
+        private Map<String, Method> getters;
+
+        /** Cached getters for POJO object. */
+        private Map<String, Method> setters;
+
+        /**
+         * POJO methods cache.
+         *
+         * @param clsName Class name.
+         * @param fields Fields.
+         * @throws CacheException If failed to construct type cache.
+         */
+        private PojoMethodsCache(String clsName, JdbcTypeField[] fields) throws CacheException {
+            try {
+                cls = Class.forName(clsName);
+
+                ctor = cls.getDeclaredConstructor();
+
+                if (!ctor.isAccessible())
+                    ctor.setAccessible(true);
+            }
+            catch (ClassNotFoundException e) {
+                throw new CacheException("Failed to find class: " + clsName, e);
+            }
+            catch (NoSuchMethodException e) {
+                throw new CacheException("Failed to find default constructor for class: " + clsName, e);
+            }
+
+            setters = U.newHashMap(fields.length);
+
+            getters = U.newHashMap(fields.length);
+
+            for (JdbcTypeField field : fields) {
+                String prop = capitalFirst(field.getJavaFieldName());
+
+                try {
+                    getters.put(field.getJavaFieldName(), cls.getMethod("get" + prop));
+                }
+                catch (NoSuchMethodException ignored) {
+                    try {
+                        getters.put(field.getJavaFieldName(), cls.getMethod("is" + prop));
+                    }
+                    catch (NoSuchMethodException e) {
+                        throw new CacheException("Failed to find getter in POJO class [class=" + clsName +
+                            ", prop=" + field.getJavaFieldName() + "]", e);
+                    }
+                }
+
+                try {
+                    setters.put(field.getJavaFieldName(), cls.getMethod("set" + prop, field.getJavaFieldType()));
+                }
+                catch (NoSuchMethodException e) {
+                    throw new CacheException("Failed to find setter in POJO class [class=" + clsName +
+                        ", prop=" + field.getJavaFieldName() + "]", e);
+                }
+            }
+        }
+
+        /**
+         * Capitalizes the first character of the given string.
+         *
+         * @param str String.
+         * @return String with capitalized first character.
+         */
+        @Nullable private String capitalFirst(@Nullable String str) {
+            return str == null ? null :
+                str.isEmpty() ? "" : Character.toUpperCase(str.charAt(0)) + str.substring(1);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreFactory.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreFactory.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreFactory.java
index c90a69b..ded83ce 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreFactory.java
@@ -34,17 +34,53 @@ import org.apache.ignite.resources.SpringApplicationContextResource;
  *
  * <h2 class="header">Spring Example</h2>
  * <pre name="code" class="xml">
- *     &lt;bean id= "simpleDataSource" class="org.h2.jdbcx.JdbcDataSource"/&gt;
+ *     &lt;bean id= "myDataSource" class="org.h2.jdbcx.JdbcDataSource"/&gt;
  *
  *     &lt;bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"&gt;
  *          ...
  *          &lt;property name="cacheConfiguration"&gt;
  *               &lt;list&gt;
  *                  &lt;bean class="org.apache.ignite.configuration.CacheConfiguration"&gt;
+ *                      &lt;property name="name" value="myCache" /&gt;
  *                      ...
  *                      &lt;property name="cacheStoreFactory"&gt;
  *                          &lt;bean class="org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory"&gt;
- *                              &lt;property name="dataSourceBean" value = "simpleDataSource" /&gt;
+ *                              &lt;property name="dataSourceBean" value="myDataSource" /&gt;
+ *                              &lt;property name="types"&gt;
+ *                                  &lt;list&gt;
+ *                                      &lt;bean class="org.apache.ignite.cache.store.jdbc.JdbcType"&gt;
+ *                                          &lt;property name="cacheName" value="myCache" /&gt;
+ *                                          &lt;property name="databaseSchema" value="MY_DB_SCHEMA" /&gt;
+ *                                          &lt;property name="databaseTable" value="PERSON" /&gt;
+ *                                          &lt;property name="keyType" value="java.lang.Integer" /&gt;
+ *                                          &lt;property name="keyFields"&gt;
+ *                                              &lt;list&gt;
+ *                                                  &lt;bean class="org.apache.ignite.cache.store.jdbc.JdbcTypeField"&gt;
+ *                                                      &lt;property name="databaseFieldType" &gt;
+ *                                                          &lt;util:constant static-field="java.sql.Types.INTEGER"/&gt;
+ *                                                      &lt;/property&gt;
+ *                                                      &lt;property name="databaseFieldName" value="ID" /&gt;
+ *                                                      &lt;property name="javaFieldType" value="java.lang.Integer" /&gt;
+ *                                                      &lt;property name="javaFieldName" value="id" /&gt;
+ *                                                  &lt;/bean&gt;
+ *                                              &lt;/list&gt;
+ *                                          &lt;/property&gt;
+ *                                          &lt;property name="valueType" value="my.company.Person" /&gt;
+ *                                          &lt;property name="valueFields"&gt;
+ *                                              &lt;list&gt;
+ *                                                  &lt;bean class="org.apache.ignite.cache.store.jdbc.JdbcTypeField"&gt;
+ *                                                      &lt;property name="databaseFieldType" &gt;
+ *                                                          &lt;util:constant static-field="java.sql.Types.VARCHAR"/&gt;
+ *                                                      &lt;/property&gt;
+ *                                                      &lt;property name="databaseFieldName" value="NAME" /&gt;
+ *                                                      &lt;property name="javaFieldType" value="java.lang.String" /&gt;
+ *                                                      &lt;property name="javaFieldName" value="name" /&gt;
+ *                                                  &lt;/bean&gt;
+ *                                              &lt;/list&gt;
+ *                                          &lt;/property&gt;
+ *                                      &lt;/bean&gt;
+ *                                  &lt;/list&gt;
+ *                              &lt;/property&gt;
  *                          &lt;/bean&gt;
  *                      &lt;/property&gt;
  *                  &lt;/bean&gt;
@@ -57,47 +93,81 @@ import org.apache.ignite.resources.SpringApplicationContextResource;
  * <br>
  * For information about Spring framework visit <a href="http://www.springframework.org/">www.springframework.org</a>
  */
-public class CacheJdbcPojoStoreFactory<K, V> implements Factory<CacheJdbcPojoStore<K, V>> {
+public class CacheJdbcPojoStoreFactory<K, V> implements Factory<CacheAbstractJdbcStore<K, V>> {
     /** */
     private static final long serialVersionUID = 0L;
 
+    /** Default value for write attempts. */
+    public static final int DFLT_WRITE_ATTEMPTS = 2;
+
+    /** Default batch size for put and remove operations. */
+    public static final int DFLT_BATCH_SIZE = 512;
+
+    /** Default batch size for put and remove operations. */
+    public static final int DFLT_PARALLEL_LOAD_CACHE_MINIMUM_THRESHOLD = 512;
+
+    /** Maximum batch size for writeAll and deleteAll operations. */
+    private int batchSizw = DFLT_BATCH_SIZE;
+
     /** Name of data source bean. */
     private String dataSrcBean;
 
-    /** Data source. */
-    private transient DataSource dataSrc;
-
     /** Database dialect. */
     private JdbcDialect dialect;
 
+    /** Max workers thread count. These threads are responsible for load cache. */
+    private int maxPoolSize = Runtime.getRuntime().availableProcessors();
+
+    /** Maximum write attempts in case of database error. */
+    private int maxWriteAttempts = DFLT_WRITE_ATTEMPTS;
+
+    /** Parallel load cache minimum threshold. If {@code 0} then load sequentially. */
+    private int parallelLoadCacheMinThreshold = DFLT_PARALLEL_LOAD_CACHE_MINIMUM_THRESHOLD;
+
+    /** Hash calculator.  */
+    private JdbcTypeHasher hasher = JdbcTypeDefaultHasher.INSTANCE;
+
+    /** Types that store could process. */
+    private JdbcType[] types;
+
+    /** Data source. */
+    private transient DataSource dataSrc;
+
     /** Application context. */
     @SpringApplicationContextResource
-    private transient Object appContext;
+    private transient Object appCtx;
 
     /** {@inheritDoc} */
     @Override public CacheJdbcPojoStore<K, V> create() {
         CacheJdbcPojoStore<K, V> store = new CacheJdbcPojoStore<>();
 
+        store.setBatchSize(batchSizw);
         store.setDialect(dialect);
+        store.setMaximumPoolSize(maxPoolSize);
+        store.setMaximumWriteAttempts(maxWriteAttempts);
+        store.setParallelLoadCacheMinimumThreshold(parallelLoadCacheMinThreshold);
+        store.setTypes(types);
 
         if (dataSrc != null)
             store.setDataSource(dataSrc);
-        else if (dataSrcBean != null) {
-            if (appContext == null)
-                throw new IgniteException("Spring application context resource is not injected.");
+        else {
+            if (dataSrcBean != null) {
+                if (appCtx == null)
+                    throw new IgniteException("Spring application context resource is not injected.");
 
-            IgniteSpringHelper spring;
+                IgniteSpringHelper spring;
 
-            try {
-                spring = IgniteComponentType.SPRING.create(false);
+                try {
+                    spring = IgniteComponentType.SPRING.create(false);
 
-                DataSource data = spring.loadBeanFromAppContext(appContext, dataSrcBean);
+                    DataSource data = spring.loadBeanFromAppContext(appCtx, dataSrcBean);
 
-                store.setDataSource(data);
-            }
-            catch (Exception e) {
-                throw new IgniteException("Failed to load bean in application context [beanName=" + dataSrcBean +
-                    ", igniteConfig=" + appContext + ']', e);
+                    store.setDataSource(data);
+                }
+                catch (Exception e) {
+                    throw new IgniteException("Failed to load bean in application context [beanName=" + dataSrcBean +
+                        ", igniteConfig=" + appCtx + ']', e);
+                }
             }
         }
 
@@ -105,43 +175,188 @@ public class CacheJdbcPojoStoreFactory<K, V> implements Factory<CacheJdbcPojoSto
     }
 
     /**
-     * Sets name of the data source bean.
+     * Sets data source. Data source should be fully configured and ready-to-use.
      *
-     * @param dataSrcBean Data source bean name.
+     * @param dataSrc Data source.
      * @return {@code This} for chaining.
      * @see CacheJdbcPojoStore#setDataSource(DataSource)
      */
-    public CacheJdbcPojoStoreFactory<K, V> setDataSourceBean(String dataSrcBean) {
-        this.dataSrcBean = dataSrcBean;
+    public CacheJdbcPojoStoreFactory<K, V> setDataSource(DataSource dataSrc) {
+        this.dataSrc = dataSrc;
 
         return this;
     }
 
     /**
-     * Sets data source. Data source should be fully configured and ready-to-use.
+     * Get maximum batch size for delete and delete operations.
      *
-     * @param dataSrc Data source.
+     * @return Maximum batch size.
+     */
+    public int getBatchSize() {
+        return batchSizw;
+    }
+
+    /**
+     * Set maximum batch size for write and delete operations.
+     *
+     * @param batchSize Maximum batch size.
      * @return {@code This} for chaining.
-     * @see CacheJdbcPojoStore#setDataSource(DataSource)
      */
-    public CacheJdbcPojoStoreFactory<K, V> setDataSource(DataSource dataSrc) {
-        this.dataSrc = dataSrc;
+    public CacheJdbcPojoStoreFactory setBatchSize(int batchSize) {
+        this.batchSizw = batchSize;
+
+        return this;
+    }
+
+    /**
+     * Gets name of the data source bean.
+     *
+     * @return Data source bean name.
+     */
+    public String getDataSourceBean() {
+        return dataSrcBean;
+    }
+
+    /**
+     * Sets name of the data source bean.
+     *
+     * @param dataSrcBean Data source bean name.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setDataSourceBean(String dataSrcBean) {
+        this.dataSrcBean = dataSrcBean;
 
         return this;
     }
 
     /**
+     * Get database dialect.
+     *
+     * @return Database dialect.
+     */
+    public JdbcDialect getDialect() {
+        return dialect;
+    }
+
+    /**
      * Set database dialect.
      *
      * @param dialect Database dialect.
-     * @see CacheJdbcPojoStore#setDialect(JdbcDialect)
+     * @return {@code This} for chaining.
      */
-    public void setDialect(JdbcDialect dialect) {
+    public CacheJdbcPojoStoreFactory setDialect(JdbcDialect dialect) {
         this.dialect = dialect;
+
+        return this;
+    }
+
+    /**
+     * Get maximum workers thread count. These threads are responsible for queries execution.
+     *
+     * @return Maximum workers thread count.
+     */
+    public int getMaximumPoolSize() {
+        return maxPoolSize;
+    }
+
+    /**
+     * Set Maximum workers thread count. These threads are responsible for queries execution.
+     *
+     * @param maxPoolSize Max workers thread count.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setMaximumPoolSize(int maxPoolSize) {
+        this.maxPoolSize = maxPoolSize;
+
+        return this;
+    }
+
+    /**
+     * Gets maximum number of write attempts in case of database error.
+     *
+     * @return Maximum number of write attempts.
+     */
+    public int getMaximumWriteAttempts() {
+        return maxWriteAttempts;
+    }
+
+    /**
+     * Sets maximum number of write attempts in case of database error.
+     *
+     * @param maxWrtAttempts Number of write attempts.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setMaximumWriteAttempts(int maxWrtAttempts) {
+        this.maxWriteAttempts = maxWrtAttempts;
+
+        return this;
+    }
+
+    /**
+     * Parallel load cache minimum row count threshold.
+     *
+     * @return If {@code 0} then load sequentially.
+     */
+    public int getParallelLoadCacheMinimumThreshold() {
+        return parallelLoadCacheMinThreshold;
+    }
+
+    /**
+     * Parallel load cache minimum row count threshold.
+     *
+     * @param parallelLoadCacheMinThreshold Minimum row count threshold. If {@code 0} then load sequentially.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setParallelLoadCacheMinimumThreshold(int parallelLoadCacheMinThreshold) {
+        this.parallelLoadCacheMinThreshold = parallelLoadCacheMinThreshold;
+
+        return this;
+    }
+
+    /**
+     * Gets types known by store.
+     *
+     * @return Types known by store.
+     */
+    public JdbcType[] getTypes() {
+        return types;
+    }
+
+    /**
+     * Sets store configurations.
+     *
+     * @param types Store should process.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setTypes(JdbcType... types) {
+        this.types = types;
+
+        return this;
+    }
+
+    /**
+     * Gets hash code calculator.
+     *
+     * @return Hash code calculator.
+     */
+    public JdbcTypeHasher getHasher() {
+        return hasher;
+    }
+
+    /**
+     * Sets hash code calculator.
+     *
+     * @param hasher Hash code calculator.
+     * @return {@code This} for chaining.
+     */
+    public CacheJdbcPojoStoreFactory setHasher(JdbcTypeHasher hasher) {
+        this.hasher = hasher;
+
+        return this;
     }
 
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(CacheJdbcPojoStoreFactory.class, this);
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcType.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcType.java
new file mode 100644
index 0000000..2107240
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcType.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import java.io.Serializable;
+import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Description for type that could be stored into database by store.
+ */
+public class JdbcType implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Cache name. */
+    private String cacheName;
+
+    /** Schema name in database. */
+    private String dbSchema;
+
+    /** Table name in database. */
+    private String dbTbl;
+
+    /** Key class used to store key in cache. */
+    private String keyType;
+
+    /** List of fields descriptors for key object. */
+    @GridToStringInclude
+    private JdbcTypeField[] keyFields;
+
+    /** Value class used to store value in cache. */
+    private String valType;
+
+    /** List of fields descriptors for value object. */
+    @GridToStringInclude
+    private JdbcTypeField[] valFlds;
+
+    /** Custom type hasher. */
+    private JdbcTypeHasher hasher;
+
+    /**
+     * Empty constructor (all values are initialized to their defaults).
+     */
+    public JdbcType() {
+        /* No-op. */
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param type Type to copy.
+     */
+    public JdbcType(JdbcType type) {
+        cacheName = type.getCacheName();
+
+        dbSchema = type.getDatabaseSchema();
+        dbTbl = type.getDatabaseTable();
+
+        keyType = type.getKeyType();
+        keyFields = type.getKeyFields();
+
+        valType = type.getValueType();
+        valFlds = type.getValueFields();
+    }
+
+    /**
+     * Gets associated cache name.
+     *
+     * @return Сache name.
+     */
+    public String getCacheName() {
+        return cacheName;
+    }
+
+    /**
+     * Sets associated cache name.
+     *
+     * @param cacheName Cache name.
+     */
+    public JdbcType setCacheName(String cacheName) {
+        this.cacheName = cacheName;
+
+        return this;
+    }
+
+    /**
+     * Gets database schema name.
+     *
+     * @return Schema name.
+     */
+    public String getDatabaseSchema() {
+        return dbSchema;
+    }
+
+    /**
+     * Sets database schema name.
+     *
+     * @param dbSchema Schema name.
+     */
+    public JdbcType setDatabaseSchema(String dbSchema) {
+        this.dbSchema = dbSchema;
+
+        return this;
+    }
+
+    /**
+     * Gets table name in database.
+     *
+     * @return Table name in database.
+     */
+    public String getDatabaseTable() {
+        return dbTbl;
+    }
+
+    /**
+     * Table name in database.
+     *
+     * @param dbTbl Table name in database.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setDatabaseTable(String dbTbl) {
+        this.dbTbl = dbTbl;
+
+        return this;
+    }
+
+    /**
+     * Gets key type.
+     *
+     * @return Key type.
+     */
+    public String getKeyType() {
+        return keyType;
+    }
+
+    /**
+     * Sets key type.
+     *
+     * @param keyType Key type.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setKeyType(String keyType) {
+        this.keyType = keyType;
+
+        return this;
+    }
+
+    /**
+     * Sets key type.
+     *
+     * @param cls Key type class.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setKeyType(Class<?> cls) {
+        setKeyType(cls.getName());
+
+        return this;
+    }
+
+    /**
+     * Gets value type.
+     *
+     * @return Key type.
+     */
+    public String getValueType() {
+        return valType;
+    }
+
+    /**
+     * Sets value type.
+     *
+     * @param valType Value type.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setValueType(String valType) {
+        this.valType = valType;
+
+        return this;
+    }
+
+    /**
+     * Sets value type.
+     *
+     * @param cls Value type class.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setValueType(Class<?> cls) {
+        setValueType(cls.getName());
+
+        return this;
+    }
+
+    /**
+     * Gets optional persistent key fields (needed only if {@link CacheJdbcPojoStore} is used).
+     *
+     * @return Persistent key fields.
+     */
+    public JdbcTypeField[] getKeyFields() {
+        return keyFields;
+    }
+
+    /**
+     * Sets optional persistent key fields (needed only if {@link CacheJdbcPojoStore} is used).
+     *
+     * @param keyFlds Persistent key fields.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setKeyFields(JdbcTypeField... keyFlds) {
+        this.keyFields = keyFlds;
+
+        return this;
+    }
+
+    /**
+     * Gets optional persistent value fields (needed only if {@link CacheJdbcPojoStore} is used).
+     *
+     * @return Persistent value fields.
+     */
+    public JdbcTypeField[] getValueFields() {
+        return valFlds;
+    }
+
+    /**
+     * Sets optional persistent value fields (needed only if {@link CacheJdbcPojoStore} is used).
+     *
+     * @param valFlds Persistent value fields.
+     * @return {@code this} for chaining.
+     */
+    public JdbcType setValueFields(JdbcTypeField... valFlds) {
+        this.valFlds = valFlds;
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(JdbcType.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeDefaultHasher.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeDefaultHasher.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeDefaultHasher.java
new file mode 100644
index 0000000..3baad3b
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeDefaultHasher.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import java.util.Collection;
+
+/**
+ * Default implementation of {@link JdbcTypeHasher}.
+ *
+ * This implementation ignores type and field names.
+ */
+public class JdbcTypeDefaultHasher implements JdbcTypeHasher {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Singleton instance to use. */
+    public static final JdbcTypeHasher INSTANCE = new JdbcTypeDefaultHasher();
+
+    /** {@inheritDoc} */
+    @Override public int hashCode(Collection<?> values) {
+        int hash = 0;
+
+        for (Object val : values)
+            hash = 31 * hash + (val != null ? val.hashCode() : 0);
+
+        return hash;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeField.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeField.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeField.java
new file mode 100644
index 0000000..3396b81
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeField.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import java.io.Serializable;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Description of how field declared in database and in cache.
+ */
+public class JdbcTypeField implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Field JDBC type in database. */
+    private int dbFldType;
+
+    /** Field name in database. */
+    private String dbFldName;
+
+    /** Field java type. */
+    private Class<?> javaFldType;
+
+    /** Field name in java object. */
+    private String javaFldName;
+
+    /**
+     * Default constructor.
+     */
+    public JdbcTypeField() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     *
+     * @param dbFldType Field JDBC type in database.
+     * @param dbFldName Field name in database.
+     * @param javaFldType Field java type.
+     * @param javaFldName Field name in java object.
+     */
+    public JdbcTypeField(int dbFldType, String dbFldName, Class<?> javaFldType, String javaFldName) {
+        this.dbFldType = dbFldType;
+        this.dbFldName = dbFldName;
+        this.javaFldType = javaFldType;
+        this.javaFldName = javaFldName;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param field Field to copy.
+     */
+    public JdbcTypeField(JdbcTypeField field) {
+        this(field.getDatabaseFieldType(), field.getDatabaseFieldName(),
+            field.getJavaFieldType(), field.getJavaFieldName());
+    }
+
+    /**
+     * @return Column JDBC type in database.
+     */
+    public int getDatabaseFieldType() {
+        return dbFldType;
+    }
+
+    /**
+     * @param dbFldType Column JDBC type in database.
+     * @return {@code this} for chaining.
+     */
+    public JdbcTypeField setDatabaseFieldType(int dbFldType) {
+        this.dbFldType = dbFldType;
+
+        return this;
+    }
+
+
+    /**
+     * @return Column name in database.
+     */
+    public String getDatabaseFieldName() {
+        return dbFldName;
+    }
+
+    /**
+     * @param dbFldName Column name in database.
+     * @return {@code this} for chaining.
+     */
+    public JdbcTypeField setDatabaseFieldName(String dbFldName) {
+        this.dbFldName = dbFldName;
+
+        return this;
+    }
+
+    /**
+     * @return Field java type.
+     */
+    public Class<?> getJavaFieldType() {
+        return javaFldType;
+    }
+
+    /**
+     * @param javaFldType Corresponding java type.
+     * @return {@code this} for chaining.
+     */
+    public JdbcTypeField setJavaFieldType(Class<?> javaFldType) {
+        this.javaFldType = javaFldType;
+
+        return this;
+    }
+
+    /**
+     * @return Field name in java object.
+     */
+    public String getJavaFieldName() {
+        return javaFldName;
+    }
+
+    /**
+     * @param javaFldName Field name in java object.
+     * @return {@code this} for chaining.
+     */
+    public JdbcTypeField setJavaFieldName(String javaFldName) {
+        this.javaFldName = javaFldName;
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof JdbcTypeField))
+            return false;
+
+        JdbcTypeField that = (JdbcTypeField)o;
+
+        return dbFldType == that.dbFldType && dbFldName.equals(that.dbFldName) &&
+            javaFldType == that.javaFldType && javaFldName.equals(that.javaFldName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = dbFldType;
+        res = 31 * res + dbFldName.hashCode();
+
+        res = 31 * res + javaFldType.hashCode();
+        res = 31 * res + javaFldName.hashCode();
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(JdbcTypeField.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeHasher.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeHasher.java b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeHasher.java
new file mode 100644
index 0000000..9d1fcea
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/cache/store/jdbc/JdbcTypeHasher.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * API for implementing custom hashing logic for binary objects on server side.
+ */
+public interface JdbcTypeHasher extends Serializable {
+    /**
+     * Calculate hash code for specified object and fields.
+     *
+     * @param values Collection of values that should participate in hash code calculation.
+     * @return Hash code.
+     */
+    public int hashCode(Collection<?> values);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index fbe54e0..d0eeeb1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -260,6 +260,10 @@ public class GridQueryProcessor extends GridProcessorAdapter {
                     if (F.isEmpty(meta.getValueType()))
                         throw new IgniteCheckedException("Value type is not set: " + meta);
 
+                    if (meta.getQueryFields().isEmpty() && meta.getAscendingFields().isEmpty() &&
+                        meta.getDescendingFields().isEmpty() && meta.getGroups().isEmpty())
+                        continue;
+
                     TypeDescriptor desc = new TypeDescriptor();
 
                     // Key and value classes still can be available if they are primitive or JDK part.
@@ -2314,4 +2318,4 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     private enum IndexType {
         ASC, DESC, TEXT
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCache.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCache.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCache.java
index 6171ff8..1bbcf2f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCache.java
@@ -229,7 +229,9 @@ public class VisorCache implements Serializable {
     protected void estimateMemorySize(IgniteEx ignite, GridCacheAdapter ca, int sample) throws IgniteCheckedException {
         int size = ca.size();
 
-        Set<GridCacheEntryEx> set = ca.map().entries0();
+        Set<GridCacheEntryEx> set = ca.context().isNear()
+            ? ((GridNearCacheAdapter)ca).dht().map().entries0()
+            : ca.map().entries0();
 
         long memSz = 0;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreAbstractSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreAbstractSelfTest.java b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreAbstractSelfTest.java
new file mode 100644
index 0000000..0f40bd9
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreAbstractSelfTest.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.store.jdbc.dialect.H2Dialect;
+import org.apache.ignite.cache.store.jdbc.model.Person;
+import org.apache.ignite.cache.store.jdbc.model.PersonKey;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.h2.jdbcx.JdbcConnectionPool;
+
+import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+
+/**
+ * Class for {@code PojoCacheStore} tests.
+ */
+public abstract class CacheJdbcPojoStoreAbstractSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    protected static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** DB connection URL. */
+    protected static final String DFLT_CONN_URL = "jdbc:h2:mem:TestDatabase;DB_CLOSE_DELAY=-1";
+
+    /** Organization count. */
+    protected static final int ORGANIZATION_CNT = 1000;
+
+    /** Person count. */
+    protected static final int PERSON_CNT = 100000;
+
+    /** Flag indicating that tests should use transactional cache. */
+    protected static boolean transactional;
+
+    /** Flag indicating that tests should use primitive classes like java.lang.Integer for keys. */
+    protected static boolean builtinKeys;
+
+    /** Flag indicating that classes for keys available on class path or not. */
+    protected static boolean noKeyClasses;
+
+    /** Flag indicating that classes for values available on class path or not. */
+    protected static boolean noValClasses;
+
+    /**
+     * @return Connection to test in-memory H2 database.
+     * @throws SQLException
+     */
+    protected Connection getConnection() throws SQLException {
+        return DriverManager.getConnection(DFLT_CONN_URL, "sa", "");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        Connection conn = getConnection();
+
+        Statement stmt = conn.createStatement();
+
+        stmt.executeUpdate("DROP TABLE IF EXISTS Organization");
+        stmt.executeUpdate("DROP TABLE IF EXISTS Person");
+
+        stmt.executeUpdate("CREATE TABLE Organization (" +
+            " id INTEGER PRIMARY KEY," +
+            " name VARCHAR(50)," +
+            " city VARCHAR(50))");
+
+        stmt.executeUpdate("CREATE TABLE Person (" +
+            " id INTEGER PRIMARY KEY," +
+            " org_id INTEGER," +
+            " name VARCHAR(50))");
+
+        conn.commit();
+
+        U.closeQuiet(stmt);
+
+        fillSampleDatabase(conn);
+
+        U.closeQuiet(conn);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setCacheConfiguration(cacheConfiguration());
+
+        cfg.setMarshaller(marshaller());
+
+        ConnectorConfiguration connCfg = new ConnectorConfiguration();
+        cfg.setConnectorConfiguration(connCfg);
+
+        return cfg;
+    }
+
+    /**
+     * @return Marshaller to be used in test.
+     */
+    protected abstract Marshaller marshaller();
+
+    /**
+     * @return Types to be used in test.
+     */
+    protected JdbcType[] storeTypes() {
+        JdbcType[] storeTypes = new JdbcType[2];
+
+        storeTypes[0] = new JdbcType();
+        storeTypes[0].setDatabaseSchema("PUBLIC");
+        storeTypes[0].setDatabaseTable("ORGANIZATION");
+
+        if (builtinKeys) {
+            storeTypes[0].setKeyType("java.lang.Integer");
+            storeTypes[0].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"));
+        }
+        else {
+            storeTypes[0].setKeyType("org.apache.ignite.cache.store.jdbc.model.OrganizationKey" + (noKeyClasses ? "1" : ""));
+            storeTypes[0].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"));
+        }
+
+        storeTypes[0].setValueType("org.apache.ignite.cache.store.jdbc.model.Organization" + (noValClasses ? "1" : ""));
+        storeTypes[0].setValueFields(
+            new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"),
+            new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "name"),
+            new JdbcTypeField(Types.VARCHAR, "CITY", String.class, "city"));
+
+        storeTypes[1] = new JdbcType();
+        storeTypes[1].setDatabaseSchema("PUBLIC");
+        storeTypes[1].setDatabaseTable("PERSON");
+
+        if (builtinKeys) {
+            storeTypes[1].setKeyType("java.lang.Long");
+            storeTypes[1].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Long.class, "id"));
+        }
+        else {
+            storeTypes[1].setKeyType("org.apache.ignite.cache.store.jdbc.model.PersonKey" + (noKeyClasses ? "1" : ""));
+            storeTypes[1].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"));
+        }
+
+        storeTypes[1].setValueType("org.apache.ignite.cache.store.jdbc.model.Person" + (noValClasses ? "1" : ""));
+        storeTypes[1].setValueFields(
+            new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"),
+            new JdbcTypeField(Types.INTEGER, "ORG_ID", Integer.class, "orgId"),
+            new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "name"));
+
+        return storeTypes;
+    }
+
+    /**
+     * @return Cache configuration for test.
+     * @throws Exception In case when failed to create cache configuration.
+     */
+    protected CacheConfiguration cacheConfiguration() throws Exception {
+        CacheConfiguration cc = defaultCacheConfiguration();
+
+        cc.setCacheMode(PARTITIONED);
+        cc.setAtomicityMode(transactional ? TRANSACTIONAL : ATOMIC);
+        cc.setSwapEnabled(false);
+        cc.setWriteBehindEnabled(false);
+
+        CacheJdbcPojoStoreFactory<Object, Object> storeFactory = new CacheJdbcPojoStoreFactory<>();
+        storeFactory.setDialect(new H2Dialect());
+        storeFactory.setTypes(storeTypes());
+        storeFactory.setDataSource(JdbcConnectionPool.create(DFLT_CONN_URL, "sa", "")); // H2 DataSource
+
+        cc.setCacheStoreFactory(storeFactory);
+        cc.setReadThrough(true);
+        cc.setWriteThrough(true);
+        cc.setLoadPreviousValue(true);
+
+        return cc;
+    }
+
+    /**
+     * Fill in-memory database with sample data.
+     *
+     * @param conn Connection to database.
+     * @throws SQLException In case of filling database with sample data failed.
+     */
+    protected void fillSampleDatabase(Connection conn) throws SQLException {
+        info("Start to fill sample database...");
+
+        PreparedStatement orgStmt = conn.prepareStatement("INSERT INTO Organization(id, name, city) VALUES (?, ?, ?)");
+
+        for (int i = 0; i < ORGANIZATION_CNT; i++) {
+            orgStmt.setInt(1, i);
+            orgStmt.setString(2, "name" + i);
+            orgStmt.setString(3, "city" + i % 10);
+
+            orgStmt.addBatch();
+        }
+
+        orgStmt.executeBatch();
+
+        U.closeQuiet(orgStmt);
+
+        conn.commit();
+
+        PreparedStatement prnStmt = conn.prepareStatement("INSERT INTO Person(id, org_id, name) VALUES (?, ?, ?)");
+
+        for (int i = 0; i < PERSON_CNT; i++) {
+            prnStmt.setInt(1, i);
+            prnStmt.setInt(2, i % 100);
+            prnStmt.setString(3, "name" + i);
+
+            prnStmt.addBatch();
+        }
+
+        prnStmt.executeBatch();
+
+        conn.commit();
+
+        U.closeQuiet(prnStmt);
+
+        info("Sample database prepared.");
+    }
+
+    /**
+     * Start test grid with specified options.
+     *
+     * @param builtin {@code True} if keys are built in java types.
+     * @param noKeyCls {@code True} if keys classes are not on class path.
+     * @param noValCls {@code True} if values classes are not on class path.
+     * @param trn {@code True} if cache should be started in transactional mode.
+     * @throws Exception
+     */
+    protected void startTestGrid(boolean builtin, boolean noKeyCls, boolean noValCls, boolean trn) throws Exception {
+        builtinKeys = builtin;
+        noKeyClasses = noKeyCls;
+        noValClasses = noValCls;
+        transactional = trn;
+
+        startGrid();
+    }
+
+    /**
+     * Check that data was loaded correctly.
+     */
+    protected void checkCacheContent() {
+        IgniteCache<Object, Object> c1 = grid().cache(null);
+
+        c1.loadCache(null);
+
+        assertEquals(ORGANIZATION_CNT + PERSON_CNT, c1.size());
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCache() throws Exception {
+        startTestGrid(false, false, false, false);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheTx() throws Exception {
+        startTestGrid(false, false, false, true);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCachePrimitiveKeys() throws Exception {
+        startTestGrid(true, false, false, false);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCachePrimitiveKeysTx() throws Exception {
+        startTestGrid(true, false, false, true);
+
+        checkCacheContent();
+    }
+
+    /**
+     * Check put in cache and store it in db.
+     *
+     * @throws Exception If failed.
+     */
+    private void checkPut() throws Exception {
+        IgniteCache<PersonKey, Person> c1 = grid().cache(null);
+
+        Connection conn = getConnection();
+        try {
+            PreparedStatement stmt = conn.prepareStatement("SELECT ID, ORG_ID, NAME FROM PERSON WHERE ID = ?");
+
+            stmt.setInt(1, -1);
+
+            ResultSet rs = stmt.executeQuery();
+
+            assertFalse("Unexpected non empty result set", rs.next());
+
+            U.closeQuiet(rs);
+
+            // Test put-insert.
+            PersonKey key = new PersonKey(-1);
+
+            c1.put(key, new Person(-1, -2, "Person-to-test-put-insert", 999));
+
+            rs = stmt.executeQuery();
+
+            assertTrue("Unexpected empty result set", rs.next());
+
+            assertEquals(-1, rs.getInt(1));
+            assertEquals(-2, rs.getInt(2));
+            assertEquals("Person-to-test-put-insert", rs.getString(3));
+
+            assertFalse("Unexpected more data in result set", rs.next());
+
+            U.closeQuiet(rs);
+
+            // Test put-update.
+            c1.put(key, new Person(-1, -3, "Person-to-test-put-update", 999));
+
+            rs = stmt.executeQuery();
+
+            assertTrue("Unexpected empty result set", rs.next());
+
+            assertEquals(-1, rs.getInt(1));
+            assertEquals(-3, rs.getInt(2));
+            assertEquals("Person-to-test-put-update", rs.getString(3));
+
+            assertFalse("Unexpected more data in result set", rs.next());
+
+            U.closeQuiet(rs);
+        }
+        finally {
+            U.closeQuiet(conn);
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPut() throws Exception {
+        startTestGrid(false, false, false, false);
+
+        checkPut();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPutTx() throws Exception {
+        startTestGrid(false, false, false, true);
+
+        checkPut();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreOptimizedMarshallerSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreOptimizedMarshallerSelfTest.java b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreOptimizedMarshallerSelfTest.java
new file mode 100644
index 0000000..f40f7d7
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreOptimizedMarshallerSelfTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.marshaller.optimized.OptimizedMarshaller;
+
+/**
+ * Class for {@code PojoCacheStore} tests.
+ */
+public class CacheJdbcPojoStoreOptimizedMarshallerSelfTest extends CacheJdbcPojoStoreAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected Marshaller marshaller(){
+        return new OptimizedMarshaller();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStorePortableMarshallerSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStorePortableMarshallerSelfTest.java b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStorePortableMarshallerSelfTest.java
new file mode 100644
index 0000000..39504b1
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStorePortableMarshallerSelfTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cache.store.jdbc;
+
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.marshaller.portable.BinaryMarshaller;
+
+/**
+ * Class for {@code PojoCacheStore} tests.
+ */
+public class CacheJdbcPojoStorePortableMarshallerSelfTest extends CacheJdbcPojoStoreAbstractSelfTest {
+    /** {@inheritDoc} */
+    @Override protected Marshaller marshaller(){
+        return new BinaryMarshaller();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoKeyClasses() throws Exception {
+        startTestGrid(false, true, false, false);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoKeyClassesTx() throws Exception {
+        startTestGrid(false, true, false, true);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoValueClasses() throws Exception {
+        startTestGrid(false, false, true, false);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoValueClassesTx() throws Exception {
+        startTestGrid(false, false, true, true);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoKeyAndValueClasses() throws Exception {
+        startTestGrid(false, true, true, false);
+
+        checkCacheContent();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLoadCacheNoKeyAndValueClassesTx() throws Exception {
+        startTestGrid(false, true, true, true);
+
+        checkCacheContent();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreTest.java b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreTest.java
index 0ad2cad..d8f75d3 100644
--- a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcPojoStoreTest.java
@@ -17,23 +17,19 @@
 
 package org.apache.ignite.cache.store.jdbc;
 
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Timestamp;
+import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import javax.cache.integration.CacheWriterException;
-import org.apache.ignite.IgniteException;
-import org.apache.ignite.cache.CacheTypeMetadata;
-import org.apache.ignite.cache.store.jdbc.dialect.BasicJdbcDialect;
-import org.apache.ignite.cache.store.jdbc.dialect.JdbcDialect;
+
+import org.apache.ignite.cache.store.jdbc.dialect.H2Dialect;
 import org.apache.ignite.cache.store.jdbc.model.Organization;
 import org.apache.ignite.cache.store.jdbc.model.OrganizationKey;
 import org.apache.ignite.cache.store.jdbc.model.Person;
@@ -41,16 +37,11 @@ import org.apache.ignite.cache.store.jdbc.model.PersonComplexKey;
 import org.apache.ignite.cache.store.jdbc.model.PersonKey;
 import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
 import org.apache.ignite.internal.util.typedef.CI2;
-import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteBiInClosure;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.cache.GridAbstractCacheStoreSelfTest;
 import org.h2.jdbcx.JdbcConnectionPool;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
-import org.springframework.context.support.GenericApplicationContext;
-import org.springframework.core.io.UrlResource;
 
 /**
  * Class for {@code PojoCacheStore} tests.
@@ -59,9 +50,6 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
     /** DB connection URL. */
     private static final String DFLT_CONN_URL = "jdbc:h2:mem:autoCacheStore;DB_CLOSE_DELAY=-1";
 
-    /** Default config with mapping. */
-    private static final String DFLT_MAPPING_CONFIG = "modules/core/src/test/config/store/jdbc/ignite-type-metadata.xml";
-
     /** Organization count. */
     protected static final int ORGANIZATION_CNT = 1000;
 
@@ -77,71 +65,86 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
 
     /** {@inheritDoc} */
     @Override protected CacheJdbcPojoStore<Object, Object> store() {
-        CacheJdbcPojoStore<Object, Object> store = new CacheJdbcPojoStore<>();
-
-//        PGPoolingDataSource ds = new PGPoolingDataSource();
-//        ds.setUser("postgres");
-//        ds.setPassword("postgres");
-//        ds.setServerName("ip");
-//        ds.setDatabaseName("postgres");
-//        store.setDataSource(ds);
-
-//        MysqlDataSource ds = new MysqlDataSource();
-//        ds.setURL("jdbc:mysql://ip:port/dbname");
-//        ds.setUser("mysql");
-//        ds.setPassword("mysql");
-
+        CacheJdbcPojoStoreFactory<Object, Object> storeFactory = new CacheJdbcPojoStoreFactory<>();
+
+        JdbcType[] storeTypes = new JdbcType[6];
+
+        storeTypes[0] = new JdbcType();
+        storeTypes[0].setDatabaseSchema("PUBLIC");
+        storeTypes[0].setDatabaseTable("ORGANIZATION");
+        storeTypes[0].setKeyType("org.apache.ignite.cache.store.jdbc.model.OrganizationKey");
+        storeTypes[0].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"));
+
+        storeTypes[0].setValueType("org.apache.ignite.cache.store.jdbc.model.Organization");
+        storeTypes[0].setValueFields(
+            new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"),
+            new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "name"),
+            new JdbcTypeField(Types.VARCHAR, "CITY", String.class, "city"));
+
+        storeTypes[1] = new JdbcType();
+        storeTypes[1].setDatabaseSchema("PUBLIC");
+        storeTypes[1].setDatabaseTable("PERSON");
+        storeTypes[1].setKeyType("org.apache.ignite.cache.store.jdbc.model.PersonKey");
+        storeTypes[1].setKeyFields(new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"));
+
+        storeTypes[1].setValueType("org.apache.ignite.cache.store.jdbc.model.Person");
+        storeTypes[1].setValueFields(
+            new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"),
+            new JdbcTypeField(Types.INTEGER, "ORG_ID", Integer.class, "orgId"),
+            new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "name"));
+
+        storeTypes[2] = new JdbcType();
+        storeTypes[2].setDatabaseSchema("PUBLIC");
+        storeTypes[2].setDatabaseTable("PERSON_COMPLEX");
+        storeTypes[2].setKeyType("org.apache.ignite.cache.store.jdbc.model.PersonComplexKey");
+        storeTypes[2].setKeyFields(
+            new JdbcTypeField(Types.INTEGER, "ID", int.class, "id"),
+            new JdbcTypeField(Types.INTEGER, "ORG_ID", int.class, "orgId"),
+            new JdbcTypeField(Types.INTEGER, "CITY_ID", int.class, "cityId"));
+
+        storeTypes[2].setValueType("org.apache.ignite.cache.store.jdbc.model.Person");
+        storeTypes[2].setValueFields(
+            new JdbcTypeField(Types.INTEGER, "ID", Integer.class, "id"),
+            new JdbcTypeField(Types.INTEGER, "ORG_ID", Integer.class, "orgId"),
+            new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "name"),
+            new JdbcTypeField(Types.INTEGER, "SALARY", Integer.class, "salary"));
+
+        storeTypes[3] = new JdbcType();
+        storeTypes[3].setDatabaseSchema("PUBLIC");
+        storeTypes[3].setDatabaseTable("TIMESTAMP_ENTRIES");
+        storeTypes[3].setKeyType("java.sql.Timestamp");
+        storeTypes[3].setKeyFields(new JdbcTypeField(Types.TIMESTAMP, "KEY", Timestamp.class, null));
+
+        storeTypes[3].setValueType("java.lang.Integer");
+        storeTypes[3].setValueFields(new JdbcTypeField(Types.INTEGER, "VAL", Integer.class, null));
+
+        storeTypes[4] = new JdbcType();
+        storeTypes[4].setDatabaseSchema("PUBLIC");
+        storeTypes[4].setDatabaseTable("STRING_ENTRIES");
+        storeTypes[4].setKeyType("java.lang.String");
+        storeTypes[4].setKeyFields(new JdbcTypeField(Types.VARCHAR, "KEY", String.class, null));
+
+        storeTypes[4].setValueType("java.lang.String");
+        storeTypes[4].setValueFields(new JdbcTypeField(Types.VARCHAR, "VAL", Integer.class, null));
+
+        storeTypes[5] = new JdbcType();
+        storeTypes[5].setDatabaseSchema("PUBLIC");
+        storeTypes[5].setDatabaseTable("UUID_ENTRIES");
+        storeTypes[5].setKeyType("java.util.UUID");
+        storeTypes[5].setKeyFields(new JdbcTypeField(Types.BINARY, "KEY", UUID.class, null));
+
+        storeTypes[5].setValueType("java.util.UUID");
+        storeTypes[5].setValueFields(new JdbcTypeField(Types.BINARY, "VAL", UUID.class, null));
+
+        storeFactory.setTypes(storeTypes);
+
+        storeFactory.setDialect(new H2Dialect());
+
+        CacheJdbcPojoStore<Object, Object> store = storeFactory.create();
+
+        // H2 DataSource
         store.setDataSource(JdbcConnectionPool.create(DFLT_CONN_URL, "sa", ""));
 
-        URL cfgUrl;
-
-        try {
-            cfgUrl = new URL(DFLT_MAPPING_CONFIG);
-        }
-        catch (MalformedURLException ignore) {
-            cfgUrl = U.resolveIgniteUrl(DFLT_MAPPING_CONFIG);
-        }
-
-        if (cfgUrl == null)
-            throw new IgniteException("Failed to resolve metadata path: " + DFLT_MAPPING_CONFIG);
-
-        try {
-            GenericApplicationContext springCtx = new GenericApplicationContext();
-
-            new XmlBeanDefinitionReader(springCtx).loadBeanDefinitions(new UrlResource(cfgUrl));
-
-            springCtx.refresh();
-
-            Collection<CacheTypeMetadata> typeMeta = springCtx.getBeansOfType(CacheTypeMetadata.class).values();
-
-            Map<Integer, Map<Object, CacheAbstractJdbcStore.EntryMapping>> cacheMappings = new HashMap<>();
-
-            JdbcDialect dialect = store.resolveDialect();
-
-            GridTestUtils.setFieldValue(store, CacheAbstractJdbcStore.class, "dialect", dialect);
-
-            Map<Object, CacheAbstractJdbcStore.EntryMapping> entryMappings = U.newHashMap(typeMeta.size());
-
-            for (CacheTypeMetadata type : typeMeta)
-                entryMappings.put(store.keyTypeId(type.getKeyType()),
-                    new CacheAbstractJdbcStore.EntryMapping(null, dialect, type));
-
-            store.prepareBuilders(null, typeMeta);
-
-            cacheMappings.put(null, entryMappings);
-
-            GridTestUtils.setFieldValue(store, CacheAbstractJdbcStore.class, "cacheMappings", cacheMappings);
-        }
-        catch (BeansException e) {
-            if (X.hasCause(e, ClassNotFoundException.class))
-                throw new IgniteException("Failed to instantiate Spring XML application context " +
-                    "(make sure all classes used in Spring configuration are present at CLASSPATH) " +
-                    "[springUrl=" + cfgUrl + ']', e);
-            else
-                throw new IgniteException("Failed to instantiate Spring XML application context [springUrl=" +
-                    cfgUrl + ", err=" + e.getMessage() + ']', e);
-        }
-
         return store;
     }
 
@@ -224,7 +227,6 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
         super.beforeTest();
     }
 
-
     /**
      * @throws Exception If failed.
      */
@@ -274,7 +276,7 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
             if (i > 0)
                 prnComplexStmt.setInt(5, 1000 + i * 500);
             else // Add person with null salary
-                prnComplexStmt.setNull(5, java.sql.Types.INTEGER);
+                prnComplexStmt.setNull(5, Types.INTEGER);
 
             prnComplexStmt.addBatch();
         }
@@ -302,9 +304,9 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
 
                     Person val = (Person)v;
 
-                    assert key.getId() == val.getId();
-                    assert key.getOrgId() == val.getOrgId();
-                    assertEquals("name"  + key.getId(), val.getName());
+                    assertTrue("Key ID should be the same as value ID", key.getId() == val.getId());
+                    assertTrue("Key orgID should be the same as value orgID", key.getOrgId() == val.getOrgId());
+                    assertEquals("name" + key.getId(), val.getName());
 
                     prnComplexKeys.add((PersonComplexKey)k);
                 }
@@ -351,25 +353,23 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
      * @throws Exception If failed.
      */
     public void testWriteRetry() throws Exception {
+        CacheJdbcPojoStore<Object, Object> store = store();
+
         // Special dialect that will skip updates, to test write retry.
-        BasicJdbcDialect dialect = new BasicJdbcDialect() {
+        store.setDialect(new H2Dialect() {
             /** {@inheritDoc} */
-            @Override public String updateQuery(String tblName, Collection<String> keyCols, Iterable<String> valCols) {
-                return super.updateQuery(tblName, keyCols, valCols) + " AND 1 = 0";
+            @Override public boolean hasMerge() {
+                return false;
             }
-        };
-
-        store.setDialect(dialect);
-
-        Map<String, Map<Object, CacheAbstractJdbcStore.EntryMapping>> cacheMappings =
-            GridTestUtils.getFieldValue(store, CacheAbstractJdbcStore.class, "cacheMappings");
-
-        CacheAbstractJdbcStore.EntryMapping em = cacheMappings.get(null).get(OrganizationKey.class);
 
-        CacheTypeMetadata typeMeta = GridTestUtils.getFieldValue(em, CacheAbstractJdbcStore.EntryMapping.class, "typeMeta");
+            /** {@inheritDoc} */
+            @Override public String updateQuery(String tblName, Collection<String> keyCols,
+                Iterable<String> valCols) {
+                return super.updateQuery(tblName, keyCols, valCols) + " AND 1 = 0";
+            }
+        });
 
-        cacheMappings.get(null).put(OrganizationKey.class,
-            new CacheAbstractJdbcStore.EntryMapping(null, dialect, typeMeta));
+        inject(store);
 
         Connection conn = store.openConnection(false);
 
@@ -392,6 +392,8 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
 
         try {
             store.write(new CacheEntryImpl<>(k1, v1));
+
+            fail("CacheWriterException wasn't thrown.");
         }
         catch (CacheWriterException e) {
             if (!e.getMessage().startsWith("Failed insert entry in database, violate a unique index or primary key") ||
@@ -418,4 +420,4 @@ public class CacheJdbcPojoStoreTest extends GridAbstractCacheStoreSelfTest<Cache
 
         assertNull(store.load(k));
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/d71f6129/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcStoreAbstractMultithreadedSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcStoreAbstractMultithreadedSelfTest.java b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcStoreAbstractMultithreadedSelfTest.java
index 757cedd..4c4bd58 100644
--- a/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcStoreAbstractMultithreadedSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/cache/store/jdbc/CacheJdbcStoreAbstractMultithreadedSelfTest.java
@@ -308,4 +308,4 @@ public abstract class CacheJdbcStoreAbstractMultithreadedSelfTest<T extends Cach
             }
         }, 8, "tx");
     }
-}
\ No newline at end of file
+}