Ver código fonte

Merge branch 'dev' of https://git.oschina.net/baomidou/mybatis-plus into dev

= 8 anos atrás
pai
commit
178c1e2456
37 arquivos alterados com 2734 adições e 545 exclusões
  1. 2 2
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/enums/SqlMethod.java
  2. 10 3
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/AutoSqlInjector.java
  3. 2 2
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/BaseMapper.java
  4. 14 6
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/LogicSqlInjector.java
  5. 526 59
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/Wrapper.java
  6. 236 294
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/plugins/OptimisticLockerInterceptor.java
  7. 4 1
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/service/impl/ServiceImpl.java
  8. 36 9
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/toolkit/ReflectionKit.java
  9. 31 26
      mybatis-plus/src/main/java/com/baomidou/mybatisplus/toolkit/TableInfoHelper.java
  10. 7 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/GlobalConfigurationTest.java
  11. 119 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserAddrJoinTest.java
  12. 210 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserDateVersionTest.java
  13. 311 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserNoOptLockTest.java
  14. 236 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserNoVersionTest.java
  15. 229 5
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserTest.java
  16. 13 3
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/config/MybatisPlusConfig.java
  17. 56 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/config/MybatisPlusNoOptLockConfig.java
  18. 16 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserDateVersionMapper.java
  19. 19 1
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserMapper.java
  20. 16 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserNoVersionMapper.java
  21. 59 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2Addr.java
  22. 184 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2UserDateVersion.java
  23. 170 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2UserNoVersion.java
  24. 8 11
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/service/IH2UserNoVersionService.java
  25. 36 0
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/service/impl/H2UserNoVersionServiceImpl.java
  26. 14 22
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/plugins/optimisticLocker/OptimisticLockerInterceptorTest.java
  27. 0 13
      mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/plugins/optimisticLocker/StringTypeHandler.java
  28. 6 0
      mybatis-plus/src/test/resources/h2/addr.ddl.sql
  29. 5 0
      mybatis-plus/src/test/resources/h2/addr.insert.sql
  30. 28 0
      mybatis-plus/src/test/resources/h2/optlock/CreateDB.sql
  31. 35 0
      mybatis-plus/src/test/resources/h2/optlock/mybatis-config.xml
  32. 67 68
      mybatis-plus/src/test/resources/h2/spring-jdbc.xml
  33. 9 8
      mybatis-plus/src/test/resources/h2/spring-test-h2.xml
  34. 13 0
      mybatis-plus/src/test/resources/h2/spring-test-no-opt-lock-h2.xml
  35. 4 4
      mybatis-plus/src/test/resources/h2/user.insert.sql
  36. 1 7
      mybatis-plus/src/test/resources/plugins/optimisticLockerInterceptor.xml
  37. 2 1
      mybatis-plus/src/test/resources/properties/jdbc-h2.properties

+ 2 - 2
mybatis-plus/src/main/java/com/baomidou/mybatisplus/enums/SqlMethod.java

@@ -49,8 +49,8 @@ public enum SqlMethod {
     /**
      * 修改
      */
-    UPDATE_BY_ID("updateById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s}</script>"),
-    UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", "<script>UPDATE %s %s WHERE %s=#{%s}</script>"),
+    UPDATE_BY_ID("updateById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"),
+    UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"),
     UPDATE("update", "根据 whereEntity 条件,更新记录", "<script>UPDATE %s %s %s</script>"),
 
     /**

+ 10 - 3
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/AutoSqlInjector.java

@@ -307,7 +307,8 @@ public class AutoSqlInjector implements ISqlInjector {
     protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
         SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID;
         SqlSource sqlSource;
-        String idStr = table.getKeyColumn();
+        // 因为后面要通过get方法获取类型,所以这里要获取key的属性值
+        String idStr = table.getKeyProperty();
         if (batch) {
             sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS;
             StringBuilder ids = new StringBuilder();
@@ -332,8 +333,14 @@ public class AutoSqlInjector implements ISqlInjector {
      */
     protected void injectUpdateByIdSql(boolean selective, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
         SqlMethod sqlMethod = selective ? SqlMethod.UPDATE_BY_ID : SqlMethod.UPDATE_ALL_COLUMN_BY_ID;
-        String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, null), table.getKeyColumn(),
-                table.getKeyProperty());
+        String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, "et."), table.getKeyColumn(),
+                "et."+table.getKeyProperty(),
+                "<if test=\"et instanceof java.util.Map\">"+
+                        "<if test=\"et.MP_OPTLOCK_VERSION_ORIGINAL!=null\">"
+                        +"and ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}"
+                        + "</if>"
+                +"</if>"
+        );
         SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
         this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
     }

+ 2 - 2
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/BaseMapper.java

@@ -110,7 +110,7 @@ public interface BaseMapper<T> {
      *            实体对象
      * @return int
      */
-    Integer updateById(T entity);
+    Integer updateById(@Param("et") T entity);
 
     /**
      * <p>
@@ -121,7 +121,7 @@ public interface BaseMapper<T> {
      *            实体对象
      * @return int
      */
-    Integer updateAllColumnById(T entity);
+    Integer updateAllColumnById(@Param("et") T entity);
 
     /**
      * <p>

+ 14 - 6
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/LogicSqlInjector.java

@@ -15,16 +15,17 @@
  */
 package com.baomidou.mybatisplus.mapper;
 
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.scripting.defaults.RawSqlSource;
+
 import com.baomidou.mybatisplus.entity.TableFieldInfo;
 import com.baomidou.mybatisplus.entity.TableInfo;
 import com.baomidou.mybatisplus.enums.SqlMethod;
 import com.baomidou.mybatisplus.toolkit.SqlReservedWords;
 import com.baomidou.mybatisplus.toolkit.StringUtils;
-import org.apache.ibatis.mapping.SqlSource;
-import org.apache.ibatis.scripting.defaults.RawSqlSource;
-
-import java.util.List;
-import java.util.Map;
 
 /**
  * <p>
@@ -148,7 +149,14 @@ public class LogicSqlInjector extends AutoSqlInjector {
 		if (table.isLogicDelete()) {
 			SqlMethod sqlMethod = selective ? SqlMethod.LOGIC_UPDATE_BY_ID : SqlMethod.LOGIC_UPDATE_ALL_COLUMN_BY_ID;
 			String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, null),
-					table.getKeyColumn(), table.getKeyProperty(), getLogicDeleteSql(table));
+					table.getKeyColumn(), table.getKeyProperty(),
+					"<if test=\"et instanceof java.util.Map\">"+
+						"<if test=\"et.MP_OPTLOCK_VERSION_ORIGINAL!=null\">"
+							+"and ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}"
+						+ "</if>"
+					+"</if>"+
+					getLogicDeleteSql(table)
+			);
 			SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
 			this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
 		} else {

+ 526 - 59
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/Wrapper.java

@@ -32,6 +32,7 @@ import com.baomidou.mybatisplus.toolkit.MapUtils;
 import com.baomidou.mybatisplus.toolkit.SqlUtils;
 import com.baomidou.mybatisplus.toolkit.StringUtils;
 
+
 /**
  * <p>
  * 条件构造抽象类,定义T-SQL语法
@@ -69,7 +70,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     protected Boolean isWhere;
     /**
-     * 拼接WHERE后应该是AND还是OR
+     * 拼接WHERE后应该是AND还是ORnull
      */
     protected String AND_OR = "AND";
 
@@ -83,10 +84,7 @@ public abstract class Wrapper<T> implements Serializable {
     }
 
     public String getSqlSelect() {
-        if (StringUtils.isEmpty(sqlSelect)) {
-            return null;
-        }
-        return stripSqlInjection(sqlSelect);
+        return StringUtils.isEmpty(sqlSelect) ? null : stripSqlInjection(sqlSelect);
     }
 
     public Wrapper<T> setSqlSelect(String sqlSelect) {
@@ -129,11 +127,37 @@ public abstract class Wrapper<T> implements Serializable {
     public abstract String getSqlSegment();
 
     public String toString() {
+        StringBuilder sb = new StringBuilder("Wrapper<T>:");
         String sqlSegment = getSqlSegment();
         if (StringUtils.isNotEmpty(sqlSegment)) {
-            sqlSegment = sqlSegment.replaceAll("#\\{" + getParamAlias() + ".paramNameValuePairs.MPGENVAL[0-9]+}", "\\?");
+            sb.append(getSqlSegment().replaceAll("#\\{" + getParamAlias() + ".paramNameValuePairs.MPGENVAL[0-9]+}", "\\?")).append("\n");
         }
-        return sqlSegment;
+        Object entity = getEntity();
+        if (entity != null) {
+            sb.append("entity=").append(entity.toString());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * <p>
+     * SQL中WHERE关键字跟的条件语句
+     * </p>
+     * <p>
+     * eg: ew.where("name='zhangsan'").where("id={0}","123");
+     * <p>
+     * 输出: WHERE (NAME='zhangsan' AND id=123)
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlWhere where语句
+     * @param params   参数集
+     * @return this
+     */
+    public Wrapper<T> where(boolean isAdd, String sqlWhere, Object... params) {
+        if (isAdd)
+            sql.WHERE(formatSql(sqlWhere, params));
+        return this;
     }
 
     /**
@@ -151,7 +175,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> where(String sqlWhere, Object... params) {
-        sql.WHERE(formatSql(sqlWhere, params));
+        return where(true, sqlWhere, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field=value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> eq(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s = {0}", column), params));
         return this;
     }
 
@@ -165,7 +204,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> eq(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s = {0}", column), params));
+        return eq(true, column, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field <> value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> ne(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s <> {0}", column), params));
         return this;
     }
 
@@ -179,8 +233,8 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> ne(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s <> {0}", column), params));
-        return this;
+        return ne(true, column, params);
+
     }
 
     /**
@@ -188,12 +242,13 @@ public abstract class Wrapper<T> implements Serializable {
      * 等同于SQL的"field=value"表达式
      * </p>
      *
+     * @param isAdd
      * @param params
      * @return
      */
     @SuppressWarnings({"rawtypes", "unchecked"})
-    public Wrapper<T> allEq(Map<String, Object> params) {
-        if (MapUtils.isNotEmpty(params)) {
+    public Wrapper<T> allEq(boolean isAdd, Map<String, Object> params) {
+        if (isAdd && MapUtils.isNotEmpty(params)) {
             Iterator iterator = params.entrySet().iterator();
             while (iterator.hasNext()) {
                 Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
@@ -208,6 +263,35 @@ public abstract class Wrapper<T> implements Serializable {
         return this;
     }
 
+    /**
+     * <p>
+     * 等同于SQL的"field=value"表达式
+     * </p>
+     *
+     * @param params
+     * @return
+     */
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public Wrapper<T> allEq(Map<String, Object> params) {
+        return allEq(true, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field>value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> gt(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s > {0}", column), params));
+        return this;
+    }
+
     /**
      * <p>
      * 等同于SQL的"field>value"表达式
@@ -218,7 +302,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> gt(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s > {0}", column), params));
+        return gt(true, column, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field>=value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> ge(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s >= {0}", column), params));
         return this;
     }
 
@@ -232,7 +331,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> ge(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s >= {0}", column), params));
+        return ge(true, column, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field<value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> lt(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s < {0}", column), params));
         return this;
     }
 
@@ -246,7 +360,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> lt(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s < {0}", column), params));
+        return lt(true, column, params);
+    }
+
+    /**
+     * <p>
+     * 等同于SQL的"field<=value"表达式
+     * </p>
+     *
+     * @param isAdd
+     * @param column
+     * @param params
+     * @return
+     */
+    public Wrapper<T> le(boolean isAdd, String column, Object params) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s <= {0}", column), params));
         return this;
     }
 
@@ -260,7 +389,22 @@ public abstract class Wrapper<T> implements Serializable {
      * @return
      */
     public Wrapper<T> le(String column, Object params) {
-        sql.WHERE(formatSql(String.format("%s <= {0}", column), params));
+        return le(true, column, params);
+    }
+
+    /**
+     * <p>
+     * AND 连接后续条件
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlAnd and条件语句
+     * @param params 参数集
+     * @return this
+     */
+    public Wrapper<T> and(boolean isAdd, String sqlAnd, Object... params) {
+        if (isAdd)
+            sql.AND().WHERE(formatSql(sqlAnd, params));
         return this;
     }
 
@@ -274,7 +418,26 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> and(String sqlAnd, Object... params) {
-        sql.AND().WHERE(formatSql(sqlAnd, params));
+        return and(true, sqlAnd, params);
+    }
+
+    /**
+     * <p>
+     * 使用AND连接并换行
+     * </p>
+     * <p>
+     * eg: ew.where("name='zhangsan'").and("id=11").andNew("statu=1"); 输出: WHERE
+     * (name='zhangsan' AND id=11) AND (statu=1)
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlAnd AND 条件语句
+     * @param params 参数值
+     * @return this
+     */
+    public Wrapper<T> andNew(boolean isAdd, String sqlAnd, Object... params) {
+        if (isAdd)
+            sql.AND_NEW().WHERE(formatSql(sqlAnd, params));
         return this;
     }
 
@@ -292,8 +455,7 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> andNew(String sqlAnd, Object... params) {
-        sql.AND_NEW().WHERE(formatSql(sqlAnd, params));
-        return this;
+        return andNew(true, sqlAnd, params);
     }
 
     /**
@@ -321,6 +483,26 @@ public abstract class Wrapper<T> implements Serializable {
         return this;
     }
 
+    /**
+     * <p>
+     * 添加OR条件
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlOr  or 条件语句
+     * @param params 参数集
+     * @return this
+     */
+    public Wrapper<T> or(boolean isAdd, String sqlOr, Object... params) {
+        if (isAdd) {
+            if (StringUtils.isEmpty(sql.toString())) {
+                AND_OR = "OR";
+            }
+            sql.OR().WHERE(formatSql(sqlOr, params));
+        }
+        return this;
+    }
+
     /**
      * <p>
      * 添加OR条件
@@ -331,10 +513,30 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> or(String sqlOr, Object... params) {
-        if (StringUtils.isEmpty(sql.toString())) {
-            AND_OR = "OR";
+        return or(true, sqlOr, params);
+    }
+
+    /**
+     * <p>
+     * 使用OR换行,并添加一个带()的新的条件
+     * </p>
+     * <p>
+     * eg: ew.where("name='zhangsan'").and("id=11").orNew("statu=1"); 输出: WHERE
+     * (name='zhangsan' AND id=11) OR (statu=1)
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlOr  AND 条件语句
+     * @param params 参数值
+     * @return this
+     */
+    public Wrapper<T> orNew(boolean isAdd, String sqlOr, Object... params) {
+        if (isAdd) {
+            if (StringUtils.isEmpty(sql.toString())) {
+                AND_OR = "OR";
+            }
+            sql.OR_NEW().WHERE(formatSql(sqlOr, params));
         }
-        sql.OR().WHERE(formatSql(sqlOr, params));
         return this;
     }
 
@@ -352,10 +554,24 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> orNew(String sqlOr, Object... params) {
-        if (StringUtils.isEmpty(sql.toString())) {
-            AND_OR = "OR";
-        }
-        sql.OR_NEW().WHERE(formatSql(sqlOr, params));
+        return orNew(true, sqlOr, params);
+    }
+
+    /**
+     * <p>
+     * SQL中groupBy关键字跟的条件语句
+     * </p>
+     * <p>
+     * eg: ew.where("name='zhangsan'").groupBy("id,name")
+     * </p>
+     *
+     * @param isAdd
+     * @param columns SQL 中的 Group by 语句,无需输入 Group By 关键字
+     * @return this
+     */
+    public Wrapper<T> groupBy(boolean isAdd, String columns) {
+        if (isAdd)
+            sql.GROUP_BY(columns);
         return this;
     }
 
@@ -371,7 +587,25 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> groupBy(String columns) {
-        sql.GROUP_BY(columns);
+        return groupBy(true, columns);
+    }
+
+    /**
+     * <p>
+     * SQL中having关键字跟的条件语句
+     * </p>
+     * <p>
+     * eg: ew.groupBy("id,name").having("id={0}",22).and("password is not null")
+     * </p>
+     *
+     * @param isAdd
+     * @param sqlHaving having关键字后面跟随的语句
+     * @param params    参数集
+     * @return EntityWrapper<T>
+     */
+    public Wrapper<T> having(boolean isAdd, String sqlHaving, Object... params) {
+        if (isAdd)
+            sql.HAVING(formatSql(sqlHaving, params));
         return this;
     }
 
@@ -388,7 +622,25 @@ public abstract class Wrapper<T> implements Serializable {
      * @return EntityWrapper<T>
      */
     public Wrapper<T> having(String sqlHaving, Object... params) {
-        sql.HAVING(formatSql(sqlHaving, params));
+        return having(true, sqlHaving, params);
+    }
+
+    /**
+     * <p>
+     * SQL中orderby关键字跟的条件语句
+     * </p>
+     * <p>
+     * eg: ew.groupBy("id,name").having("id={0}",22).and("password is not null"
+     * ).orderBy("id,name")
+     * </p>
+     *
+     * @param isAdd
+     * @param columns SQL 中的 order by 语句,无需输入 Order By 关键字
+     * @return this
+     */
+    public Wrapper<T> orderBy(boolean isAdd, String columns) {
+        if (isAdd)
+            sql.ORDER_BY(columns);
         return this;
     }
 
@@ -405,8 +657,7 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> orderBy(String columns) {
-        sql.ORDER_BY(columns);
-        return this;
+        return orderBy(true, columns);
     }
 
     /**
@@ -418,13 +669,40 @@ public abstract class Wrapper<T> implements Serializable {
      * @param isAsc   是否为升序
      * @return this
      */
-    public Wrapper<T> orderBy(String columns, boolean isAsc) {
-        if (StringUtils.isNotEmpty(columns)) {
+    public Wrapper<T> orderBy(boolean isAdd, String columns, boolean isAsc) {
+        if (isAdd && StringUtils.isNotEmpty(columns)) {
             sql.ORDER_BY(columns + (isAsc ? " ASC" : " DESC"));
         }
         return this;
     }
 
+    /**
+     * <p>
+     * SQL中orderby关键字跟的条件语句,可根据变更动态排序
+     * </p>
+     *
+     * @param columns SQL 中的 order by 语句,无需输入 Order By 关键字
+     * @param isAsc   是否为升序
+     * @return this
+     */
+    public Wrapper<T> orderBy(String columns, boolean isAsc) {
+        return orderBy(true, columns, isAsc);
+    }
+
+    /**
+     * LIKE条件语句,value中无需前后%
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param value  匹配值
+     * @return this
+     */
+    public Wrapper<T> like(boolean isAdd, String column, String value) {
+        if (isAdd)
+            handerLike(column, value, SqlLike.DEFAULT, false);
+        return this;
+    }
+
     /**
      * LIKE条件语句,value中无需前后%
      *
@@ -433,7 +711,20 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> like(String column, String value) {
-        handerLike(column, value, SqlLike.DEFAULT, false);
+        return like(true, column, value);
+    }
+
+    /**
+     * NOT LIKE条件语句,value中无需前后%
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param value  匹配值
+     * @return this
+     */
+    public Wrapper<T> notLike(boolean isAdd, String column, String value) {
+        if (isAdd)
+            handerLike(column, value, SqlLike.DEFAULT, true);
         return this;
     }
 
@@ -445,8 +736,7 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> notLike(String column, String value) {
-        handerLike(column, value, SqlLike.DEFAULT, true);
-        return this;
+        return notLike(true, column, value);
     }
 
     /**
@@ -468,6 +758,21 @@ public abstract class Wrapper<T> implements Serializable {
         }
     }
 
+    /**
+     * LIKE条件语句,value中无需前后%
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param value  匹配值
+     * @param type
+     * @return this
+     */
+    public Wrapper<T> like(boolean isAdd, String column, String value, SqlLike type) {
+        if (isAdd)
+            handerLike(column, value, type, false);
+        return this;
+    }
+
     /**
      * LIKE条件语句,value中无需前后%
      *
@@ -477,7 +782,21 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> like(String column, String value, SqlLike type) {
-        handerLike(column, value, type, false);
+        return like(true, column, value, type);
+    }
+
+    /**
+     * NOT LIKE条件语句,value中无需前后%
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param value  匹配值
+     * @param type
+     * @return this
+     */
+    public Wrapper<T> notLike(boolean isAdd, String column, String value, SqlLike type) {
+        if (isAdd)
+            handerLike(column, value, type, true);
         return this;
     }
 
@@ -490,7 +809,19 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> notLike(String column, String value, SqlLike type) {
-        handerLike(column, value, type, true);
+        return notLike(true, column, value, type);
+    }
+
+    /**
+     * is not null 条件
+     *
+     * @param isAdd
+     * @param columns 字段名称。多个字段以逗号分隔。
+     * @return this
+     */
+    public Wrapper<T> isNotNull(boolean isAdd, String columns) {
+        if (isAdd)
+            sql.IS_NOT_NULL(columns);
         return this;
     }
 
@@ -501,7 +832,19 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> isNotNull(String columns) {
-        sql.IS_NOT_NULL(columns);
+        return isNotNull(true, columns);
+    }
+
+    /**
+     * is not null 条件
+     *
+     * @param isAdd
+     * @param columns 字段名称。多个字段以逗号分隔。
+     * @return this
+     */
+    public Wrapper<T> isNull(boolean isAdd, String columns) {
+        if (isAdd)
+            sql.IS_NULL(columns);
         return this;
     }
 
@@ -512,7 +855,19 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> isNull(String columns) {
-        sql.IS_NULL(columns);
+        return isNull(true, columns);
+    }
+
+    /**
+     * EXISTS 条件语句,目前适配mysql及oracle
+     *
+     * @param isAdd
+     * @param value 匹配值
+     * @return this
+     */
+    public Wrapper<T> exists(boolean isAdd, String value) {
+        if (isAdd)
+            sql.EXISTS(value);
         return this;
     }
 
@@ -523,7 +878,19 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> exists(String value) {
-        sql.EXISTS(value);
+        return exists(true, value);
+    }
+
+    /**
+     * NOT EXISTS条件语句
+     *
+     * @param isAdd
+     * @param value 匹配值
+     * @return this
+     */
+    public Wrapper<T> notExists(boolean isAdd, String value) {
+        if (isAdd)
+            sql.NOT_EXISTS(value);
         return this;
     }
 
@@ -534,90 +901,161 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> notExists(String value) {
-        sql.NOT_EXISTS(value);
-        return this;
+        return notExists(true, value);
     }
 
     /**
      * IN 条件语句,目前适配mysql及oracle
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  逗号拼接的字符串
      * @return this
      */
-    public Wrapper<T> in(String column, String value) {
-        if (StringUtils.isNotEmpty(value)) {
+    public Wrapper<T> in(boolean isAdd, String column, String value) {
+        if (isAdd && StringUtils.isNotEmpty(value)) {
             in(column, StringUtils.splitWorker(value, ",", -1, false));
         }
         return this;
     }
 
+    /**
+     * IN 条件语句,目前适配mysql及oracle
+     *
+     * @param column 字段名称
+     * @param value  逗号拼接的字符串
+     * @return this
+     */
+    public Wrapper<T> in(String column, String value) {
+        return in(true, column, value);
+    }
+
     /**
      * NOT IN条件语句
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  逗号拼接的字符串
      * @return this
      */
-    public Wrapper<T> notIn(String column, String value) {
-        if (StringUtils.isNotEmpty(value)) {
+    public Wrapper<T> notIn(boolean isAdd, String column, String value) {
+        if (isAdd && StringUtils.isNotEmpty(value)) {
             notIn(column, StringUtils.splitWorker(value, ",", -1, false));
         }
         return this;
     }
 
+    /**
+     * NOT IN条件语句
+     *
+     * @param column 字段名称
+     * @param value  逗号拼接的字符串
+     * @return this
+     */
+    public Wrapper<T> notIn(String column, String value) {
+        return notIn(true, column, value);
+    }
+
     /**
      * IN 条件语句,目前适配mysql及oracle
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  匹配值 List集合
      * @return this
      */
-    public Wrapper<T> in(String column, Collection<?> value) {
-        if (CollectionUtils.isNotEmpty(value))
+    public Wrapper<T> in(boolean isAdd, String column, Collection<?> value) {
+        if (isAdd && CollectionUtils.isNotEmpty(value))
             sql.WHERE(formatSql(inExpression(column, value, false), value.toArray()));
         return this;
     }
 
+    /**
+     * IN 条件语句,目前适配mysql及oracle
+     *
+     * @param column 字段名称
+     * @param value  匹配值 List集合
+     * @return this
+     */
+    public Wrapper<T> in(String column, Collection<?> value) {
+        return in(true, column, value);
+    }
+
     /**
      * NOT IN 条件语句,目前适配mysql及oracle
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  匹配值 List集合
      * @return this
      */
-    public Wrapper<T> notIn(String column, Collection<?> value) {
-        if (CollectionUtils.isNotEmpty(value))
+    public Wrapper<T> notIn(boolean isAdd, String column, Collection<?> value) {
+        if (isAdd && CollectionUtils.isNotEmpty(value))
             sql.WHERE(formatSql(inExpression(column, value, true), value.toArray()));
         return this;
     }
 
+    /**
+     * NOT IN 条件语句,目前适配mysql及oracle
+     *
+     * @param column 字段名称
+     * @param value  匹配值 List集合
+     * @return this
+     */
+    public Wrapper<T> notIn(String column, Collection<?> value) {
+        return notIn(true, column, value);
+    }
+
     /**
      * IN 条件语句,目前适配mysql及oracle
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  匹配值 object数组
      * @return this
      */
-    public Wrapper<T> in(String column, Object[] value) {
-        if (ArrayUtils.isNotEmpty(value))
+    public Wrapper<T> in(boolean isAdd, String column, Object[] value) {
+        if (isAdd && ArrayUtils.isNotEmpty(value))
             sql.WHERE(formatSql(inExpression(column, Arrays.asList(value), false), value));
         return this;
     }
 
+    /**
+     * IN 条件语句,目前适配mysql及oracle
+     *
+     * @param column 字段名称
+     * @param value  匹配值 object数组
+     * @return this
+     */
+    public Wrapper<T> in(String column, Object[] value) {
+        return in(true, column, value);
+    }
+
     /**
      * NOT IN 条件语句,目前适配mysql及oracle
      *
+     * @param isAdd
      * @param column 字段名称
      * @param value  匹配值 object数组
      * @return this
      */
-    public Wrapper<T> notIn(String column, Object... value) {
-        if (ArrayUtils.isNotEmpty(value))
+    public Wrapper<T> notIn(boolean isAdd, String column, Object... value) {
+        if (isAdd && ArrayUtils.isNotEmpty(value))
             sql.WHERE(formatSql(inExpression(column, Arrays.asList(value), true), value));
         return this;
     }
 
+    /**
+     * NOT IN 条件语句,目前适配mysql及oracle
+     *
+     * @param column 字段名称
+     * @param value  匹配值 object数组
+     * @return this
+     */
+    public Wrapper<T> notIn(String column, Object... value) {
+        return notIn(true, column, value);
+    }
+
     /**
      * 获取in表达式
      *
@@ -647,6 +1085,21 @@ public abstract class Wrapper<T> implements Serializable {
         return null;
     }
 
+    /**
+     * betwwee 条件语句
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param val1
+     * @param val2
+     * @return this
+     */
+    public Wrapper<T> between(boolean isAdd, String column, Object val1, Object val2) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s BETWEEN {0} AND {1}", column), val1, val2));
+        return this;
+    }
+
     /**
      * betwwee 条件语句
      *
@@ -656,7 +1109,21 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> between(String column, Object val1, Object val2) {
-        sql.WHERE(formatSql(String.format("%s BETWEEN {0} AND {1}", column), val1, val2));
+        return between(true, column, val1, val2);
+    }
+
+    /**
+     * NOT betwwee 条件语句
+     *
+     * @param isAdd
+     * @param column 字段名称
+     * @param val1
+     * @param val2
+     * @return this
+     */
+    public Wrapper<T> notBetween(boolean isAdd, String column, Object val1, Object val2) {
+        if (isAdd)
+            sql.WHERE(formatSql(String.format("%s NOT BETWEEN {0} AND {1}", column), val1, val2));
         return this;
     }
 
@@ -669,8 +1136,8 @@ public abstract class Wrapper<T> implements Serializable {
      * @return this
      */
     public Wrapper<T> notBetween(String column, Object val1, Object val2) {
-        sql.WHERE(formatSql(String.format("%s NOT BETWEEN {0} AND {1}", column), val1, val2));
-        return this;
+        return notBetween(true, column, val1, val2);
+
     }
 
     /**
@@ -733,7 +1200,7 @@ public abstract class Wrapper<T> implements Serializable {
      * <p>
      * 根据需要格式化SQL<BR>
      * <BR>
-     * Format SQL for methods: EntityWrapper.where/and/or...("name={0}", value);
+     * Format SQL for methods: EntityWrapper<T>.where/and/or...("name={0}", value);
      * ALL the {<b>i</b>} will be replaced with #{MPGENVAL<b>i</b>}<BR>
      * <BR>
      * ew.where("sample_name=<b>{0}</b>", "haha").and("sample_age &gt;<b>{0}</b>

+ 236 - 294
mybatis-plus/src/main/java/com/baomidou/mybatisplus/plugins/OptimisticLockerInterceptor.java

@@ -1,321 +1,263 @@
-/**
- * Copyright (c) 2011-2014, hubin (jobob@qq.com).
- * <p>
- * Licensed 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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 com.baomidou.mybatisplus.plugins;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.sql.Connection;
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
 
-import org.apache.ibatis.binding.MapperMethod.ParamMap;
-import org.apache.ibatis.exceptions.ExceptionFactory;
-import org.apache.ibatis.executor.statement.StatementHandler;
-import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.binding.MapperMethod;
+import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.mapping.ParameterMapping;
 import org.apache.ibatis.mapping.SqlCommandType;
 import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.plugin.Intercepts;
 import org.apache.ibatis.plugin.Invocation;
 import org.apache.ibatis.plugin.Plugin;
 import org.apache.ibatis.plugin.Signature;
-import org.apache.ibatis.reflection.MetaObject;
-import org.apache.ibatis.reflection.SystemMetaObject;
-import org.apache.ibatis.session.Configuration;
-import org.apache.ibatis.session.defaults.DefaultSqlSession;
-import org.apache.ibatis.type.TypeException;
-import org.apache.ibatis.type.UnknownTypeHandler;
 
-import com.baomidou.mybatisplus.annotations.TableField;
 import com.baomidou.mybatisplus.annotations.Version;
-import com.baomidou.mybatisplus.mapper.EntityWrapper;
-import com.baomidou.mybatisplus.toolkit.PluginUtils;
-import com.baomidou.mybatisplus.toolkit.StringUtils;
-
-import net.sf.jsqlparser.expression.BinaryExpression;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
-import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import net.sf.jsqlparser.schema.Column;
-import net.sf.jsqlparser.statement.update.Update;
+import com.baomidou.mybatisplus.entity.TableFieldInfo;
+import com.baomidou.mybatisplus.entity.TableInfo;
+import com.baomidou.mybatisplus.mapper.Wrapper;
+import com.baomidou.mybatisplus.toolkit.ReflectionKit;
+import com.baomidou.mybatisplus.toolkit.TableInfoHelper;
 
 /**
  * <p>
- * MyBatis乐观锁插件
+ * Optimistic Lock Light version<BR>
+ *     Intercept on {@link Executor}.update;<BR>
+ *     Support version types: int/Integer, long/Long, java.util.Date, java.sql.Timestamp<BR>
+ *     For extra types, please define a subclass and override {@code getUpdatedVersionVal}() method.<BR>
+ * <BR>
+ * How to use?<BR>
+ *     (1) Define an Entity and add {@link Version} annotation on one entity field.<BR>
+ *     (2) Add {@link OptimisticLockerInterceptor} into mybatis plugin.
+ *
+ * How to work?<BR>
+ *     if update entity with version column=1:<BR>
+ *         (1) no {@link OptimisticLockerInterceptor}:<BR>
+ *             SQL: update tbl_test set name='abc' where id=100001;<BR>
+ *         (2) add {@link OptimisticLockerInterceptor}:<BR>
+ *             SQL: update tbl_test set name='abc',version=2 where id=100001 and version=1;
  * </p>
- * 
- * <pre>
- * 之前:update user set name = ?, password = ? where id = ?
- * 之后:update user set name = ?, password = ?, version = version+1 where id = ? and version = ?
- * 对象上的version字段上添加{@link Version}注解
- * sql可以不需要写version字段,只要对象version有值就会更新
- * 支持,int Integer, long Long, Date,Timestamp
- * 其他类型可以自定义实现,注入versionHandlers,多个以逗号分隔
- * </pre>
  *
- * @author TaoYu 小锅盖 tantan
- * @since 2017-04-08
+ * @author yuxiaobin
+ * @date 2017/5/24
  */
 @Intercepts({
-		@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
-public final class OptimisticLockerInterceptor implements Interceptor {
-
-	/**
-	 * 根据对象类型缓存version基本信息
-	 */
-	private static final Map<Class<?>, LockerCache> versionCache = new ConcurrentHashMap<>();
-
-	/**
-	 * 根据version字段类型缓存的处理器
-	 */
-	private static final Map<Type, VersionHandler<?>> typeHandlers = new HashMap<>();
-
-	private static final Expression RIGHT_EXPRESSION = new Column("?");
-
-	static {
-		IntegerTypeHandler integerTypeHandler = new IntegerTypeHandler();
-		typeHandlers.put(int.class, integerTypeHandler);
-		typeHandlers.put(Integer.class, integerTypeHandler);
-
-		LongTypeHandler longTypeHandler = new LongTypeHandler();
-		typeHandlers.put(long.class, longTypeHandler);
-		typeHandlers.put(Long.class, longTypeHandler);
-
-		typeHandlers.put(Date.class, new DateTypeHandler());
-		typeHandlers.put(Timestamp.class, new TimestampTypeHandler());
-	}
-
-	public Object intercept(Invocation invocation) throws Exception {
-		StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget());
-		MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
-		// 先判断是不是真正的UPDATE操作
-		MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
-		if (!ms.getSqlCommandType().equals(SqlCommandType.UPDATE)) {
-			return invocation.proceed();
-		}
-		BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
-		// 获得参数类型,去缓存中快速判断是否有version注解才继续执行
-		Class<?> parameterClass = ms.getParameterMap().getType();
-		LockerCache lockerCache = versionCache.get(parameterClass);
-		if (lockerCache != null) {
-			if (lockerCache.lock) {
-				processChangeSql(ms, boundSql, lockerCache);
-			}
-		} else {
-			Field versionField = getVersionField(parameterClass);
-			if (versionField != null) {
-				Class<?> fieldType = versionField.getType();
-				if (!typeHandlers.containsKey(fieldType)) {
-					throw new TypeException("乐观锁不支持" + fieldType.getName() + "类型,请自定义实现");
-				}
-				final TableField tableField = versionField.getAnnotation(TableField.class);
-				String versionColumn = versionField.getName();
-				if (tableField != null) {
-					versionColumn = tableField.value();
-				}
-				LockerCache lc = new LockerCache(true, versionColumn, versionField, typeHandlers.get(fieldType));
-				versionCache.put(parameterClass, lc);
-				processChangeSql(ms, boundSql, lc);
-			} else {
-				versionCache.put(parameterClass, LockerCache.INSTANCE);
-			}
-		}
-		return invocation.proceed();
-
-	}
-
-	private Field getVersionField(Class<?> parameterClass) {
-		if (parameterClass != Object.class) {
-			for (Field field : parameterClass.getDeclaredFields()) {
-				if (field.isAnnotationPresent(Version.class)) {
-					field.setAccessible(true);
-					return field;
-				}
-			}
-			return getVersionField(parameterClass.getSuperclass());
-		}
-		return null;
-
-	}
-
-	private void processChangeSql(MappedStatement ms, BoundSql boundSql, LockerCache lockerCache) throws Exception {
-		Object parameterObject = boundSql.getParameterObject();
-		if (parameterObject instanceof ParamMap) {
-			ParamMap<?> paramMap = (ParamMap<?>) parameterObject;
-			parameterObject = paramMap.get("et");
-			EntityWrapper<?> entityWrapper = (EntityWrapper<?>) paramMap.get("ew");
-			if (entityWrapper != null) {
-				Object entity = entityWrapper.getEntity();
-				if (entity != null && lockerCache.field.get(entity) == null) {
-					changSql(ms, boundSql, parameterObject, lockerCache);
-				}
-			}
-		} else if(!(parameterObject instanceof DefaultSqlSession.StrictMap)) {
-			//如果是有逻辑删,且DELETE传入字段为ID或IDS的话,MYBATIS就会使用StrictMap
-			//这里是判断如量不为ParamMap县城不为StrictMap类型就进行乐观锁
-			changSql(ms, boundSql, parameterObject, lockerCache);
-		}
-	}
-
-	@SuppressWarnings("unchecked")
-	private void changSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, LockerCache lockerCache)
-			throws Exception {
-		Field versionField = lockerCache.field;
-		String versionColumn = lockerCache.column;
-		final Object versionValue = versionField.get(parameterObject);
-		if (versionValue != null) {// 先判断传参是否携带version,没带跳过插件
-			Configuration configuration = ms.getConfiguration();
-			// 给字段赋新值
-			lockerCache.versionHandler.plusVersion(parameterObject, versionField, versionValue);
-			// 处理where条件,添加?
-			Update jsqlSql = (Update) CCJSqlParserUtil.parse(boundSql.getSql());
-			BinaryExpression expression = (BinaryExpression) jsqlSql.getWhere();
-			if (expression != null && !expression.toString().contains(versionColumn)) {
-				EqualsTo equalsTo = new EqualsTo();
-				equalsTo.setLeftExpression(new Column(versionColumn));
-				equalsTo.setRightExpression(RIGHT_EXPRESSION);
-				jsqlSql.setWhere(new AndExpression(equalsTo, expression));
-				List<ParameterMapping> parameterMappings = new LinkedList<>(boundSql.getParameterMappings());
-				parameterMappings.add(jsqlSql.getExpressions().size(), getVersionMappingInstance(configuration));
-				MetaObject boundSqlMeta = configuration.newMetaObject(boundSql);
-				boundSqlMeta.setValue("sql", jsqlSql.toString());
-				boundSqlMeta.setValue("parameterMappings", parameterMappings);
-			}
-			// 设置参数
-			boundSql.setAdditionalParameter("originVersionValue", versionValue);
-		}
-	}
-	
-	private volatile ParameterMapping parameterMapping;
-	
-	private ParameterMapping getVersionMappingInstance(Configuration configuration) {
-		if (parameterMapping == null) {
-			synchronized (OptimisticLockerInterceptor.class) {
-				if (parameterMapping == null) {
-					parameterMapping = new ParameterMapping.Builder(configuration, "originVersionValue",
-							new UnknownTypeHandler(configuration.getTypeHandlerRegistry())).build();
-				}
-			}
-		}
-		return parameterMapping;
-	}
-
-	@Override
-	public Object plugin(Object target) {
-		if (target instanceof StatementHandler) {
-			return Plugin.wrap(target, this);
-		}
-		return target;
-	}
-
-	@Override
-	public void setProperties(Properties properties) {
-		String versionHandlers = properties.getProperty("versionHandlers");
-		if (StringUtils.isNotEmpty(versionHandlers)) {
-			for (String handlerClazz : versionHandlers.split(",")) {
-				try {
-					registerHandler(Class.forName(handlerClazz));
-				} catch (Exception e) {
-					throw ExceptionFactory.wrapException("乐观锁插件自定义处理器注册失败", e);
-				}
-			}
-		}
-	}
-
-	/**
-	 * 注册处理器
-	 */
-	private static void registerHandler(Class<?> versionHandlerClazz) throws Exception {
-		ParameterizedType parameterizedType = (ParameterizedType) versionHandlerClazz.getGenericInterfaces()[0];
-		Object versionInstance = versionHandlerClazz.newInstance();
-		if (!(versionInstance instanceof VersionHandler)) {
-			throw new TypeException("参数未实现VersionHandler,不能注入");
-		} else {
-			Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
-			if (actualTypeArguments.length == 0) {
-				throw new IllegalArgumentException("处理器泛型未定义");
-			} else if (Object.class.equals(actualTypeArguments[0])) {
-				throw new IllegalArgumentException("处理器泛型不能为Object");
-			} else {
-				typeHandlers.put(actualTypeArguments[0], (VersionHandler<?>) versionInstance);
-			}
-		}
-	}
-
-	// *****************************基本类型处理器*****************************
-	private static class IntegerTypeHandler implements VersionHandler<Integer> {
-
-		public void plusVersion(Object paramObj, Field field, Integer versionValue) throws Exception {
-			field.set(paramObj, versionValue + 1);
-		}
-	}
-
-	private static class LongTypeHandler implements VersionHandler<Long> {
-
-		public void plusVersion(Object paramObj, Field field, Long versionValue) throws Exception {
-			field.set(paramObj, versionValue + 1);
-		}
-	}
-
-	// ***************************** 时间类型处理器*****************************
-	private static class DateTypeHandler implements VersionHandler<Date> {
-
-		public void plusVersion(Object paramObj, Field field, Date versionValue) throws Exception {
-			field.set(paramObj, new Date());
-		}
-	}
-
-	private static class TimestampTypeHandler implements VersionHandler<Timestamp> {
-
-		public void plusVersion(Object paramObj, Field field, Timestamp versionValue) throws Exception {
-			field.set(paramObj, new Timestamp(new Date().getTime()));
-		}
-	}
-
-	/**
-	 * 缓存对象
-	 */
-	@SuppressWarnings("rawtypes")
-	private static class LockerCache {
-
-		public static final LockerCache INSTANCE = new LockerCache();
-
-		private boolean lock;
-		private String column;
-		private Field field;
-		private VersionHandler versionHandler;
-
-		public LockerCache() {
-		}
-
-		LockerCache(Boolean lock, String column, Field field, VersionHandler versionHandler) {
-			this.lock = lock;
-			this.column = column;
-			this.field = field;
-			this.versionHandler = versionHandler;
-		}
-	}
+        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
+public class OptimisticLockerInterceptor implements Interceptor {
+
+    private final Map<Class<?>, EntityField> versionFieldCache = new HashMap<>();
+    private final Map<Class<?>, List<EntityField>> entityFieldsCache = new HashMap<>();
+
+    private static final String MP_OPTLOCK_VERSION_ORIGINAL = "MP_OPTLOCK_VERSION_ORIGINAL";
+    private static final String MP_OPTLOCK_VERSION_COLUMN = "MP_OPTLOCK_VERSION_COLUMN";
+    private static final String NAME_ENTITY = "et";
+    private static final String NAME_ENTITY_WRAPPER = "ew";
+    private static final String PARAM_UPDATE_METHOD_NAME = "update";
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Object intercept(Invocation invocation) throws Throwable {
+        Object[] args = invocation.getArgs();
+        MappedStatement ms = (MappedStatement) args[0];
+        if(SqlCommandType.UPDATE.compareTo(ms.getSqlCommandType())!=0){
+            return invocation.proceed();
+        }
+        Object param = args[1];
+        if(param instanceof MapperMethod.ParamMap){
+            MapperMethod.ParamMap map = (MapperMethod.ParamMap) param;
+            Wrapper ew = null;
+            if(map.containsKey(NAME_ENTITY_WRAPPER)){//mapper.update(updEntity, EntityWrapper<>(whereEntity);
+                ew = (Wrapper) map.get(NAME_ENTITY_WRAPPER);
+            }//else updateById(entity) -->> change updateById(entity) to updateById(@Param("et") entity)
+            Object et = map.get(NAME_ENTITY);
+            if(ew!=null){
+                Object entity = ew.getEntity();
+                if(entity!=null){
+                    EntityField ef = getVersionField(entity.getClass());
+                    Field versionField = ef==null?null:ef.getField();
+                    if (versionField != null) {
+                        Object originalVersionVal = versionField.get(entity);
+                        if(originalVersionVal!=null){
+                            versionField.set(et, getUpdatedVersionVal(originalVersionVal));
+                        }
+                    }
+                }
+            }else{
+                String methodId = ms.getId();
+                String updateMethodName = methodId.substring(ms.getId().lastIndexOf(".")+1);
+                if(PARAM_UPDATE_METHOD_NAME.equals(updateMethodName)){//update(entity, null) -->> update all. ignore version
+                    return invocation.proceed();
+                }
+                EntityField entityField = getVersionField(et.getClass());
+                Field versionField = entityField==null?null:entityField.getField();
+                Object originalVersionVal;
+                if(versionField!=null && (originalVersionVal=versionField.get(et))!=null) {
+                    TableInfo tableInfo = TableInfoHelper.getTableInfo(et.getClass());
+                    Map<String,Object> entityMap = new HashMap<>();
+                    List<EntityField> fields = getEntityFields(et.getClass());
+                    for(EntityField ef : fields){
+                        Field fd = ef.getField();
+                        if(fd.isAccessible()) {
+                            entityMap.put(fd.getName(), fd.get(et));
+                            if (ef.isVersion()) {
+                                versionField = fd;
+                            }
+                        }
+                    }
+                    String versionPropertyName = versionField.getName();
+                    List<TableFieldInfo> fieldList = tableInfo.getFieldList();
+                    String versionColumnName = entityField.getColumnName();
+                    if(versionColumnName==null) {
+                        for (TableFieldInfo tf : fieldList) {
+                            if (versionPropertyName.equals(tf.getProperty())) {
+                                versionColumnName = tf.getColumn();
+                            }
+                        }
+                    }
+                    if (versionColumnName != null) {
+                        entityField.setColumnName(versionColumnName);
+                        entityMap.put(versionField.getName(), getUpdatedVersionVal(originalVersionVal));
+                        entityMap.put(MP_OPTLOCK_VERSION_ORIGINAL, originalVersionVal);
+                        entityMap.put(MP_OPTLOCK_VERSION_COLUMN, versionColumnName);
+                        map.put(NAME_ENTITY, entityMap);
+                    }
+                }
+            }
+        }
+        return invocation.proceed();
+    }
+
+    /**
+     * This method provides the control for version value.<BR>
+     * Returned value type must be the same as original one.
+     *
+     * @param originalVersionVal
+     * @return updated version val
+     */
+    protected Object getUpdatedVersionVal(Object originalVersionVal){
+        Class<?> versionValClass = originalVersionVal.getClass();
+        if(long.class.equals(versionValClass)){
+            return ((long)originalVersionVal)+1;
+        }else if(Long.class.equals(versionValClass)){
+            return ((Long)originalVersionVal)+1;
+        }else if(int.class.equals(versionValClass)){
+            return ((int)originalVersionVal)+1;
+        }else if(Integer.class.equals(versionValClass)){
+            return ((Integer)originalVersionVal)+1;
+        }else if(Date.class.equals(versionValClass)){
+            return new Date();
+        }else if(Timestamp.class.equals(versionValClass)){
+            return new Timestamp(System.currentTimeMillis());
+        }else{
+            return originalVersionVal;//not supported type, return original val.
+        }
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        if (target instanceof Executor) {
+            return Plugin.wrap(target, this);
+        }
+        return target;
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+
+    }
+
+    private EntityField getVersionField(Class<?> parameterClass) {
+        synchronized (parameterClass.getName()) {
+            if (versionFieldCache.containsKey(parameterClass)) {
+                return versionFieldCache.get(parameterClass);
+            }else{
+                EntityField field = getVersionFieldRegular(parameterClass);
+                versionFieldCache.put(parameterClass, field);
+                return field;
+            }
+        }
+    }
+
+    private EntityField getVersionFieldRegular(Class<?> parameterClass){
+        if (parameterClass != Object.class) {
+            for (Field field : parameterClass.getDeclaredFields()) {
+                if (field.isAnnotationPresent(Version.class)) {
+                    field.setAccessible(true);
+                    return new EntityField(field, true);
+                }
+            }
+            return getVersionFieldRegular(parameterClass.getSuperclass());
+        }
+        return null;
+    }
+
+    private List<EntityField> getEntityFields(Class<?> parameterClass){
+        if(entityFieldsCache.containsKey(parameterClass)){
+            return entityFieldsCache.get(parameterClass);
+        }else{
+            List<EntityField> fields = getFieldsFromClazz(parameterClass, null);
+            entityFieldsCache.put(parameterClass, fields);
+            return fields;
+        }
+    }
+
+    private List<EntityField> getFieldsFromClazz(Class<?> parameterClass, List<EntityField> fieldList){
+        if(fieldList==null){
+            fieldList = new ArrayList<>();
+        }
+        List<Field> fields = ReflectionKit.getFieldList(parameterClass);
+        for(Field field:fields){
+            field.setAccessible(true);
+            if (field.isAnnotationPresent(Version.class)) {
+                fieldList.add(new EntityField(field, true));
+            }else{
+                fieldList.add(new EntityField(field, false));
+            }
+        }
+        return fieldList;
+    }
+
+}
+class EntityField{
+
+    private Field field;
+    private boolean version;
+    private String columnName;
+
+    public EntityField(Field field, boolean version) {
+        this.field = field;
+        this.version = version;
+    }
+
+    public Field getField() {
+        return field;
+    }
+
+    public void setField(Field field) {
+        this.field = field;
+    }
+
+    public boolean isVersion() {
+        return version;
+    }
+
+    public void setVersion(boolean version) {
+        this.version = version;
+    }
+
+    public String getColumnName() {
+        return columnName;
+    }
+
+    public void setColumnName(String columnName) {
+        this.columnName = columnName;
+    }
+}
 
-}

+ 4 - 1
mybatis-plus/src/main/java/com/baomidou/mybatisplus/service/impl/ServiceImpl.java

@@ -19,6 +19,7 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.ibatis.binding.MapperMethod;
 import org.apache.ibatis.logging.Log;
 import org.apache.ibatis.logging.LogFactory;
 import org.apache.ibatis.session.SqlSession;
@@ -247,7 +248,9 @@ public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
             int size = entityList.size();
             String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID);
             for (int i = 0; i < size; i++) {
-                batchSqlSession.update(sqlStatement, entityList.get(i));
+                MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
+                param.put("et",entityList.get(i));
+                batchSqlSession.update(sqlStatement, param);
                 if (i >= 1 && i % batchSize == 0) {
                     batchSqlSession.flushStatements();
                 }

+ 36 - 9
mybatis-plus/src/main/java/com/baomidou/mybatisplus/toolkit/ReflectionKit.java

@@ -15,15 +15,26 @@
  */
 package com.baomidou.mybatisplus.toolkit;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
 import com.baomidou.mybatisplus.entity.TableFieldInfo;
 import com.baomidou.mybatisplus.entity.TableInfo;
 import com.baomidou.mybatisplus.enums.FieldStrategy;
 import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
-import org.apache.ibatis.logging.Log;
-import org.apache.ibatis.logging.LogFactory;
-
-import java.lang.reflect.*;
-import java.util.*;
 
 
 /**
@@ -106,10 +117,7 @@ public class ReflectionKit {
             return false;
         }
         Class<?> cls = bean.getClass();
-        TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
-        if (null == tableInfo) {
-            throw new MybatisPlusException(String.format("Error: Could Not find %s in TableInfo Cache. ", cls.getSimpleName()));
-        }
+        TableInfo tableInfo = getTableInfoAsSuperClass(cls);
         boolean result = false;
         List<TableFieldInfo> fieldList = tableInfo.getFieldList();
         for (TableFieldInfo tableFieldInfo : fieldList) {
@@ -231,4 +239,23 @@ public class ReflectionKit {
         return fieldList;
     }
 
+    /**
+     * 递归自身的class,获取TableInfo
+     *
+     * @param cls
+     * @return TableInfo
+     * @throws MybatisPlusException
+     */
+    private static TableInfo getTableInfoAsSuperClass(Class<?> cls) {
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
+        if (tableInfo == null) {
+            if (Object.class.equals(cls)) {
+                throw new MybatisPlusException(String.format("Error: Could Not find %s in TableInfo Cache. ", cls.getSimpleName()));
+            } else {
+                tableInfo = getTableInfoAsSuperClass(cls.getSuperclass());
+            }
+        }
+        return tableInfo;
+    }
+
 }

+ 31 - 26
mybatis-plus/src/main/java/com/baomidou/mybatisplus/toolkit/TableInfoHelper.java

@@ -15,17 +15,13 @@
  */
 package com.baomidou.mybatisplus.toolkit;
 
-import com.baomidou.mybatisplus.annotations.KeySequence;
-import com.baomidou.mybatisplus.annotations.TableField;
-import com.baomidou.mybatisplus.annotations.TableId;
-import com.baomidou.mybatisplus.annotations.TableName;
-import com.baomidou.mybatisplus.entity.GlobalConfiguration;
-import com.baomidou.mybatisplus.entity.TableFieldInfo;
-import com.baomidou.mybatisplus.entity.TableInfo;
-import com.baomidou.mybatisplus.enums.IdType;
-import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
-import com.baomidou.mybatisplus.mapper.IKeyGenerator;
-import com.baomidou.mybatisplus.mapper.SqlRunner;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.ibatis.executor.keygen.KeyGenerator;
 import org.apache.ibatis.executor.keygen.NoKeyGenerator;
@@ -40,12 +36,17 @@ import org.apache.ibatis.scripting.LanguageDriver;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.SqlSessionFactory;
 
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import com.baomidou.mybatisplus.annotations.KeySequence;
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+import com.baomidou.mybatisplus.entity.GlobalConfiguration;
+import com.baomidou.mybatisplus.entity.TableFieldInfo;
+import com.baomidou.mybatisplus.entity.TableInfo;
+import com.baomidou.mybatisplus.enums.IdType;
+import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.mapper.IKeyGenerator;
+import com.baomidou.mybatisplus.mapper.SqlRunner;
 
 /**
  * <p>
@@ -83,16 +84,17 @@ public class TableInfoHelper {
      * <p>
      * 获取所有实体映射表信息
      * <p>
+     *
      * @return
      */
     public static List<TableInfo> getTableInfos() {
-    	List<TableInfo> tableInfos = new ArrayList<TableInfo>();
-    	for (Map.Entry<String, TableInfo> entry : tableInfoCache.entrySet()) {  
-    		tableInfos.add(entry.getValue());  
-    	} 
+        List<TableInfo> tableInfos = new ArrayList<TableInfo>();
+        for (Map.Entry<String, TableInfo> entry : tableInfoCache.entrySet()) {
+            tableInfos.add(entry.getValue());
+        }
         return tableInfos;
     }
-    
+
     /**
      * <p>
      * 实体类反射获取表信息【初始化】
@@ -102,11 +104,14 @@ public class TableInfoHelper {
      * @return
      */
     public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
-        TableInfo ti = tableInfoCache.get(clazz.getName());
-        if (ti != null) {
-            return ti;
+        TableInfo tableInfo = tableInfoCache.get(clazz.getName());
+        if (StringUtils.checkValNotNull(tableInfo)) {
+            if (StringUtils.checkValNotNull(builderAssistant)) {
+                tableInfo.setConfigMark(builderAssistant.getConfiguration());
+            }
+            return tableInfo;
         }
-        TableInfo tableInfo = new TableInfo();
+        tableInfo = new TableInfo();
         GlobalConfiguration globalConfig;
         if (null != builderAssistant) {
             tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());

+ 7 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/GlobalConfigurationTest.java

@@ -98,4 +98,11 @@ public class GlobalConfigurationTest {
         session.rollback();
         sqlSession.commit();
     }
+
+
+    @org.junit.Test
+    public void testStringFormat(){
+        String str = "'%s'";
+        System.out.println(String.format(str,"abc"));
+    }
 }

+ 119 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserAddrJoinTest.java

@@ -0,0 +1,119 @@
+package com.baomidou.mybatisplus.test.h2;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.baomidou.mybatisplus.plugins.Page;
+import com.baomidou.mybatisplus.test.h2.entity.mapper.H2UserMapper;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2Addr;
+
+/**
+ * <p>
+ * Mybatis Plus H2 Junit Test
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:h2/spring-test-h2.xml"})
+public class H2UserAddrJoinTest {
+
+    @Autowired
+    private H2UserMapper userMapper;
+
+    @BeforeClass
+    public static void initDB() throws SQLException, IOException {
+        @SuppressWarnings("resource")
+        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:h2/spring-test-h2.xml");
+        DataSource ds = (DataSource) context.getBean("dataSource");
+        try (Connection conn = ds.getConnection()) {
+            Statement stmt = conn.createStatement();
+            stmt.execute(readFile("user.ddl.sql"));
+            stmt.execute("truncate table h2user");
+            stmt.execute(readFile("addr.ddl.sql"));
+            stmt.execute("truncate table h2address");
+            insertUsers(stmt);
+            insertAddr(stmt);
+            conn.commit();
+        }
+    }
+
+    private static void insertUsers(Statement stmt) throws SQLException, IOException {
+        String filename = "user.insert.sql";
+        String filePath = H2UserAddrJoinTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if(line.isEmpty()){
+                    continue;
+                }
+                stmt.execute(line.replace(";", ""));
+            }
+        }
+    }
+    private static void insertAddr(Statement stmt) throws SQLException, IOException {
+        String filename = "addr.insert.sql";
+        String filePath = H2UserAddrJoinTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if(line.isEmpty()){
+                    continue;
+                }
+                stmt.execute(line.replace(";", ""));
+            }
+        }
+    }
+
+    private static String readFile(String filename) {
+        StringBuilder builder = new StringBuilder();
+        String filePath = H2UserAddrJoinTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null)
+                builder.append(line).append(" ");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return builder.toString();
+    }
+
+
+    @Test
+    public void testJoinTableWithoutPagination(){
+        List<H2Addr> addrList = userMapper.getAddrListByUserId(101L);
+        Assert.assertEquals(5, addrList.size());
+    }
+    @Test
+    public void testJoinTableWithPagination(){
+        List<H2Addr> addrList = userMapper.getAddrListByUserId(101L, new Page<H2Addr>(0,3));
+        Assert.assertEquals(3, addrList.size());
+    }
+
+
+
+}

+ 210 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserDateVersionTest.java

@@ -0,0 +1,210 @@
+package com.baomidou.mybatisplus.test.h2;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.sql.DataSource;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.baomidou.mybatisplus.test.h2.entity.mapper.H2UserDateVersionMapper;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserDateVersion;
+
+/**
+ * <p>
+ * Mybatis Plus H2 Junit Test
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:h2/spring-test-h2.xml"})
+public class H2UserDateVersionTest {
+
+    @Autowired
+    private H2UserDateVersionMapper userMapper;
+
+    @BeforeClass
+    public static void initDB() throws SQLException, IOException {
+        @SuppressWarnings("resource")
+        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:h2/spring-test-h2.xml");
+        DataSource ds = (DataSource) context.getBean("dataSource");
+        try (Connection conn = ds.getConnection()) {
+            String createTableSql = readFile("user.ddl.sql");
+            Statement stmt = conn.createStatement();
+            stmt.execute(createTableSql);
+            stmt.execute("truncate table h2user");
+            insertUsers(stmt);
+            conn.commit();
+        }
+    }
+
+    private static void insertUsers(Statement stmt) throws SQLException, IOException {
+        String filename = "user.insert.sql";
+        String filePath = H2UserDateVersionTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stmt.execute(line.replace(";", ""));
+            }
+        }
+    }
+
+    private static String readFile(String filename) {
+        StringBuilder builder = new StringBuilder();
+        String filePath = H2UserDateVersionTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null)
+                builder.append(line).append(" ");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return builder.toString();
+    }
+
+
+    @Test
+    public void testUpdateByIdNoDateVersion(){
+        Long id = 991L;
+        H2UserDateVersion user = new H2UserDateVersion();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userMapper.insertAllColumn(user);
+
+        H2UserDateVersion userDB = userMapper.selectById(id);
+        Assert.assertEquals(null, userDB.getTestDate());
+
+        userDB.setName("991");
+        userMapper.updateById(userDB);
+
+        userDB = userMapper.selectById(id);
+        Assert.assertEquals("991", userDB.getName());
+    }
+
+
+    @Test
+    public void testUpdateByEntityWrapperNoDateVersion(){
+        Long id = 992L;
+        H2UserDateVersion user = new H2UserDateVersion();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userMapper.insertAllColumn(user);
+
+        H2UserDateVersion userDB = userMapper.selectById(id);
+
+        H2UserDateVersion updUser = new H2UserDateVersion();
+        updUser.setName("999");
+
+        userMapper.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userMapper.selectById(id);
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByIdWithDateVersion(){
+        Long id = 994L;
+        H2UserDateVersion user = new H2UserDateVersion();
+        user.setId(id);
+        user.setName("994");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_MONTH,-1);
+        user.setTestDate(cal.getTime());
+        userMapper.insertAllColumn(user);
+
+        System.out.println("before update: testDate="+user.getTestDate());
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
+        H2UserDateVersion userDB = userMapper.selectById(id);
+
+        Assert.assertNotNull(userDB.getTestDate());
+        String originalDateVersionStr =sdf.format(cal.getTime());
+        Assert.assertEquals(originalDateVersionStr, sdf.format(userDB.getTestDate()));
+
+        userDB.setName("991");
+        userMapper.updateById(userDB);
+        userDB = userMapper.selectById(id);
+        Assert.assertEquals("991", userDB.getName());
+        Date versionDate = userDB.getTestDate();
+        System.out.println("after update: testDate="+versionDate);
+        String versionDateStr = sdf.format(versionDate);
+        Assert.assertEquals(sdf.format(new Date()), versionDateStr);
+
+        Assert.assertNotEquals(originalDateVersionStr, versionDateStr);
+
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperWithDateVersion(){
+        Long id = 993L;
+        H2UserDateVersion user = new H2UserDateVersion();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_MONTH,-1);
+        user.setTestDate(cal.getTime());
+        userMapper.insertAllColumn(user);
+
+        H2UserDateVersion userDB = userMapper.selectById(id);
+
+        H2UserDateVersion updUser = new H2UserDateVersion();
+        updUser.setName("999");
+        userDB.setVersion(null);
+        userMapper.update(updUser, new EntityWrapper<>(userDB));
+
+        System.out.println("before update: testDate="+userDB.getTestDate());
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
+
+        userDB = userMapper.selectById(id);
+        Assert.assertEquals("999", userDB.getName());
+
+        Date versionDate = userDB.getTestDate();
+        System.out.println("after update: testDate="+versionDate);
+        String versionDateStr = sdf.format(versionDate);
+        Assert.assertEquals(sdf.format(new Date()), versionDateStr);
+    }
+
+
+}

+ 311 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserNoOptLockTest.java

@@ -0,0 +1,311 @@
+package com.baomidou.mybatisplus.test.h2;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import javax.sql.DataSource;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.baomidou.mybatisplus.plugins.Page;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2User;
+import com.baomidou.mybatisplus.test.h2.entity.service.IH2UserService;
+
+/**
+ * <p>
+ * Mybatis Plus H2 Junit Test
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:h2/spring-test-no-opt-lock-h2.xml"})
+public class H2UserNoOptLockTest {
+
+    @Autowired
+    private IH2UserService userService;
+
+    @BeforeClass
+    public static void initDB() throws SQLException, IOException {
+        @SuppressWarnings("resource")
+        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:h2/spring-test-h2.xml");
+        DataSource ds = (DataSource) context.getBean("dataSource");
+        try (Connection conn = ds.getConnection()) {
+            String createTableSql = readFile("user.ddl.sql");
+            Statement stmt = conn.createStatement();
+            stmt.execute(createTableSql);
+            stmt.execute("truncate table h2user");
+            insertUsers(stmt);
+            conn.commit();
+        }
+    }
+
+    private static void insertUsers(Statement stmt) throws SQLException, IOException {
+        String filename = "user.insert.sql";
+        String filePath = H2UserNoOptLockTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stmt.execute(line.replace(";", ""));
+            }
+        }
+    }
+
+    private static String readFile(String filename) {
+        StringBuilder builder = new StringBuilder();
+        String filePath = H2UserNoOptLockTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null)
+                builder.append(line).append(" ");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return builder.toString();
+    }
+
+    @Test
+    public void testInsert() {
+        H2User user = new H2User();
+        user.setAge(1);
+        user.setPrice(new BigDecimal("9.99"));
+        userService.insert(user);
+        Assert.assertNotNull(user.getId());
+        user.setDesc("Caratacus");
+        userService.insertOrUpdate(user);
+        H2User userFromDB = userService.selectById(user.getId());
+        Assert.assertEquals("Caratacus", userFromDB.getDesc());
+    }
+
+    @Test
+    public void testDelete() {
+        H2User user = new H2User();
+        user.setAge(1);
+        user.setPrice(new BigDecimal("9.99"));
+        userService.insert(user);
+        Long userId = user.getId();
+        Assert.assertNotNull(userId);
+        userService.deleteById(userId);
+        Assert.assertNull(userService.selectById(userId));
+    }
+
+    @Test
+    public void testSelectByid() {
+        Long userId = 101L;
+        Assert.assertNotNull(userService.selectById(userId));
+    }
+
+    @Test
+    public void testSelectOne() {
+        H2User user = new H2User();
+        user.setId(105L);
+        EntityWrapper<H2User> ew = new EntityWrapper<>(user);
+        H2User userFromDB = userService.selectOne(ew);
+        Assert.assertNotNull(userFromDB);
+    }
+
+    @Test
+    public void testSelectList() {
+        H2User user = new H2User();
+        EntityWrapper<H2User> ew = new EntityWrapper<>(user);
+        List<H2User> list = userService.selectList(ew);
+        Assert.assertNotNull(list);
+        Assert.assertNotEquals(0, list.size());
+    }
+
+    @Test
+    public void testSelectPage() {
+        Page<H2User> page = userService.selectPage(new Page<H2User>(1, 3));
+        Assert.assertEquals(3, page.getRecords().size());
+    }
+
+    @Test
+    public void testUpdateByIdOptLock(){
+        Long id = 991L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        userDB.setName("991");
+        userService.updateById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("991", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateAllColumnByIdOptLock(){
+        Long id = 997L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        userDB.setName("991");
+        userService.updateAllColumnById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("991", userDB.getName());
+
+        userDB.setName("990");
+        userService.updateById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("990", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperOptLock(){
+        Long id = 992L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2User updUser = new H2User();
+        updUser.setName("999");
+
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperOptLockWithoutVersion(){
+        Long id = 993L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2User updUser = new H2User();
+        updUser.setName("999");
+        userDB.setVersion(null);
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateBatch(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long, Integer> userVersionMap = new HashMap<>();
+        for(H2User u:list){
+            userVersionMap.put(u.getId(),u.getVersion());
+        }
+
+        Assert.assertTrue(userService.updateBatchById(list));
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User user:list){
+            Assert.assertEquals(userVersionMap.get(user.getId()).intValue(), user.getVersion().intValue());
+        }
+
+    }
+
+    @Test
+    public void testUpdateInLoop(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long,Integer> versionBefore = new HashMap<>();
+        Map<Long,String> nameExpect = new HashMap<>();
+        for (H2User h2User : list) {
+            Long id = h2User.getId();
+            Integer versionVal = h2User.getVersion();
+            versionBefore.put(id, versionVal);
+            String randomName = h2User.getName()+"_"+new Random().nextInt(10);
+            nameExpect.put(id, randomName);
+            h2User.setName(randomName);
+            userService.updateById(h2User);
+        }
+
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User u:list){
+            Assert.assertEquals(u.getName(), nameExpect.get(u.getId()));
+            Assert.assertEquals(versionBefore.get(u.getId()).intValue(), u.getVersion().intValue());
+        }
+    }
+    @Test
+    public void testUpdateAllColumnInLoop(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long,Integer> versionBefore = new HashMap<>();
+        Map<Long,String> nameExpect = new HashMap<>();
+        for (H2User h2User : list) {
+            Long id = h2User.getId();
+            Integer versionVal = h2User.getVersion();
+            versionBefore.put(id, versionVal);
+            String randomName = h2User.getName()+"_"+new Random().nextInt(10);
+            nameExpect.put(id, randomName);
+            h2User.setName(randomName);
+            userService.updateAllColumnById(h2User);
+        }
+
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User u:list){
+            Assert.assertEquals(u.getName(), nameExpect.get(u.getId()));
+            Assert.assertEquals(versionBefore.get(u.getId()).intValue(), u.getVersion().intValue());
+        }
+    }
+
+}

+ 236 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserNoVersionTest.java

@@ -0,0 +1,236 @@
+package com.baomidou.mybatisplus.test.h2;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.baomidou.mybatisplus.plugins.Page;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserNoVersion;
+import com.baomidou.mybatisplus.test.h2.entity.service.IH2UserNoVersionService;
+
+/**
+ * <p>
+ * Mybatis Plus H2 Junit Test
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:h2/spring-test-h2.xml"})
+public class H2UserNoVersionTest {
+
+    @Autowired
+    private IH2UserNoVersionService userService;
+
+    @BeforeClass
+    public static void initDB() throws SQLException, IOException {
+        @SuppressWarnings("resource")
+        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:h2/spring-test-h2.xml");
+        DataSource ds = (DataSource) context.getBean("dataSource");
+        try (Connection conn = ds.getConnection()) {
+            String createTableSql = readFile("user.ddl.sql");
+            Statement stmt = conn.createStatement();
+            stmt.execute(createTableSql);
+            stmt.execute("truncate table h2user");
+            insertUsers(stmt);
+            conn.commit();
+        }
+    }
+
+    private static void insertUsers(Statement stmt) throws SQLException, IOException {
+        String filename = "user.insert.sql";
+        String filePath = H2UserNoVersionTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stmt.execute(line.replace(";", ""));
+            }
+        }
+    }
+
+    private static String readFile(String filename) {
+        StringBuilder builder = new StringBuilder();
+        String filePath = H2UserNoVersionTest.class.getClassLoader().getResource("").getPath() + "/h2/" + filename;
+        try (
+                BufferedReader reader = new BufferedReader(new FileReader(filePath))
+        ) {
+            String line;
+            while ((line = reader.readLine()) != null)
+                builder.append(line).append(" ");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return builder.toString();
+    }
+
+    @Test
+    public void testInsert() {
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setAge(1);
+        user.setPrice(new BigDecimal("9.99"));
+        userService.insert(user);
+        Assert.assertNotNull(user.getId());
+        user.setDesc("Caratacus");
+        userService.insertOrUpdate(user);
+        H2UserNoVersion userFromDB = userService.selectById(user.getId());
+        Assert.assertEquals("Caratacus", userFromDB.getDesc());
+    }
+
+    @Test
+    public void testDelete() {
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setAge(1);
+        user.setPrice(new BigDecimal("9.99"));
+        userService.insert(user);
+        Long userId = user.getId();
+        Assert.assertNotNull(userId);
+        userService.deleteById(userId);
+        Assert.assertNull(userService.selectById(userId));
+    }
+
+    @Test
+    public void testSelectByid() {
+        Long userId = 101L;
+        Assert.assertNotNull(userService.selectById(userId));
+    }
+
+    @Test
+    public void testSelectOne() {
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setId(105L);
+        EntityWrapper<H2UserNoVersion> ew = new EntityWrapper<>(user);
+        H2UserNoVersion userFromDB = userService.selectOne(ew);
+        Assert.assertNotNull(userFromDB);
+    }
+
+    @Test
+    public void testSelectList() {
+        H2UserNoVersion user = new H2UserNoVersion();
+        EntityWrapper<H2UserNoVersion> ew = new EntityWrapper<>(user);
+        List<H2UserNoVersion> list = userService.selectList(ew);
+        Assert.assertNotNull(list);
+        Assert.assertNotEquals(0, list.size());
+    }
+
+    @Test
+    public void testSelectPage() {
+        Page<H2UserNoVersion> page = userService.selectPage(new Page<H2UserNoVersion>(1, 3));
+        Assert.assertEquals(3, page.getRecords().size());
+    }
+
+    @Test
+    public void testUpdateById(){
+        Long id = 991L;
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2UserNoVersion userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        userDB.setName("991");
+        userService.updateById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("991", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapper(){
+        Long id = 992L;
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2UserNoVersion userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2UserNoVersion updUser = new H2UserNoVersion();
+        updUser.setName("999");
+
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapper2(){
+        Long id = 993L;
+        H2UserNoVersion user = new H2UserNoVersion();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2UserNoVersion userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2UserNoVersion updUser = new H2UserNoVersion();
+        updUser.setName("999");
+        userDB.setVersion(null);
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateBatch(){
+        List<H2UserNoVersion> list = userService.selectList(new EntityWrapper<H2UserNoVersion>());
+        Map<Long, Integer> userVersionMap = new HashMap<>();
+        for(H2UserNoVersion u:list){
+            userVersionMap.put(u.getId(),u.getVersion());
+        }
+        userService.updateBatchById(list);
+
+        list = userService.selectList(new EntityWrapper<H2UserNoVersion>());
+        for(H2UserNoVersion user:list){
+            Assert.assertEquals(userVersionMap.get(user.getId()).intValue(), user.getVersion().intValue());
+        }
+    }
+
+
+}

+ 229 - 5
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/H2UserTest.java

@@ -7,7 +7,10 @@ import java.math.BigDecimal;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Random;
 
 import javax.sql.DataSource;
 
@@ -97,11 +100,6 @@ public class H2UserTest {
         Assert.assertEquals("Caratacus", userFromDB.getDesc());
     }
 
-    @Test
-    public void testUpdate() {
-
-    }
-
     @Test
     public void testDelete() {
         H2User user = new H2User();
@@ -143,4 +141,230 @@ public class H2UserTest {
         Page<H2User> page = userService.selectPage(new Page<H2User>(1, 3));
         Assert.assertEquals(3, page.getRecords().size());
     }
+
+    @Test
+    public void testUpdateByIdOptLock(){
+        Long id = 991L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        userDB.setName("991");
+        userService.updateById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(2, userDB.getVersion().intValue());
+        Assert.assertEquals("991", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateAllColumnByIdOptLock(){
+        Long id = 997L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("991");
+        user.setAge(91);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        userDB.setName("991");
+        userService.updateAllColumnById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(2, userDB.getVersion().intValue());
+        Assert.assertEquals("991", userDB.getName());
+
+        userDB.setName("990");
+        userService.updateById(userDB);
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(3, userDB.getVersion().intValue());
+        Assert.assertEquals("990", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperOptLock(){
+        Long id = 992L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2User updUser = new H2User();
+        updUser.setName("999");
+
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(2, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperOptLockWithoutVersionVal(){
+        Long id = 993L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+
+        H2User updUser = new H2User();
+        updUser.setName("999");
+        userDB.setVersion(null);
+        userService.update(updUser, new EntityWrapper<>(userDB));
+
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        Assert.assertEquals("999", userDB.getName());
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperNoEntity(){
+        Long id = 998L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        H2User updateUser = new H2User();
+        updateUser.setName("998");
+        boolean result = userService.update(updateUser, new EntityWrapper<H2User>());
+        Assert.assertTrue(result);
+        userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        EntityWrapper<H2User> param = new EntityWrapper<>();
+        param.eq("name","998");
+        List<H2User> userList = userService.selectList(param);
+        Assert.assertTrue(userList.size()>1);
+    }
+
+    @Test
+    public void testUpdateByEntityWrapperNull(){
+        Long id = 918L;
+        H2User user = new H2User();
+        user.setId(id);
+        user.setName("992");
+        user.setAge(92);
+        user.setPrice(BigDecimal.TEN);
+        user.setDesc("asdf");
+        user.setTestType(1);
+        user.setVersion(1);
+        userService.insertAllColumn(user);
+
+        H2User userDB = userService.selectById(id);
+        Assert.assertEquals(1, userDB.getVersion().intValue());
+        H2User updateUser = new H2User();
+        updateUser.setName("918");
+        updateUser.setVersion(1);
+        Assert.assertTrue(userService.update(updateUser,null));
+        EntityWrapper<H2User> ew = new EntityWrapper<>();
+        int count1 = userService.selectCount(ew);
+        ew.eq("name","918").eq("version",1);
+        int count2 = userService.selectCount(ew);
+        List<H2User> userList = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User u:userList){
+            System.out.println(u);
+        }
+        System.out.println("count1="+count1+", count2="+count2);
+        Assert.assertTrue(count2>0);
+        Assert.assertEquals(count1,count2);
+    }
+
+    @Test
+    public void testUpdateBatch(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long, Integer> userVersionMap = new HashMap<>();
+        for(H2User u:list){
+            userVersionMap.put(u.getId(),u.getVersion());
+        }
+
+        Assert.assertTrue(userService.updateBatchById(list));
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User user:list){
+            Assert.assertEquals(userVersionMap.get(user.getId())+1, user.getVersion().intValue());
+        }
+
+    }
+
+    @Test
+    public void testUpdateInLoop(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long,Integer> versionBefore = new HashMap<>();
+        Map<Long,String> nameExpect = new HashMap<>();
+        for (H2User h2User : list) {
+            Long id = h2User.getId();
+            Integer versionVal = h2User.getVersion();
+            versionBefore.put(id, versionVal);
+            String randomName = h2User.getName()+"_"+new Random().nextInt(10);
+            nameExpect.put(id, randomName);
+            h2User.setName(randomName);
+            userService.updateById(h2User);
+        }
+
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User u:list){
+            Assert.assertEquals(u.getName(), nameExpect.get(u.getId()));
+            Assert.assertEquals(versionBefore.get(u.getId())+1, u.getVersion().intValue());
+        }
+    }
+    @Test
+    public void testUpdateAllColumnInLoop(){
+        List<H2User> list = userService.selectList(new EntityWrapper<H2User>());
+        Map<Long,Integer> versionBefore = new HashMap<>();
+        Map<Long,String> nameExpect = new HashMap<>();
+        for (H2User h2User : list) {
+            Long id = h2User.getId();
+            Integer versionVal = h2User.getVersion();
+            versionBefore.put(id, versionVal);
+            String randomName = h2User.getName()+"_"+new Random().nextInt(10);
+            nameExpect.put(id, randomName);
+            h2User.setName(randomName);
+            userService.updateAllColumnById(h2User);
+        }
+
+        list = userService.selectList(new EntityWrapper<H2User>());
+        for(H2User u:list){
+            Assert.assertEquals(u.getName(), nameExpect.get(u.getId()));
+            Assert.assertEquals(versionBefore.get(u.getId())+1, u.getVersion().intValue());
+        }
+    }
+
 }

+ 13 - 3
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/config/MybatisPlusConfig.java

@@ -13,12 +13,15 @@ import org.springframework.core.io.ResourceLoader;
 import com.baomidou.mybatisplus.MybatisConfiguration;
 import com.baomidou.mybatisplus.MybatisXMLLanguageDriver;
 import com.baomidou.mybatisplus.entity.GlobalConfiguration;
+import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
+import com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor;
 import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
+import com.baomidou.mybatisplus.plugins.PerformanceInterceptor;
 import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
 
 /**
  * <p>
- * TODO class
+ * Mybatis Plus Config
  * </p>
  *
  * @author Caratacus
@@ -40,8 +43,11 @@ public class MybatisPlusConfig {
         sqlSessionFactory.setConfiguration(configuration);
         PaginationInterceptor pagination = new PaginationInterceptor();
         pagination.setDialectType("h2");
+        OptimisticLockerInterceptor optLock = new OptimisticLockerInterceptor();
         sqlSessionFactory.setPlugins(new Interceptor[]{
-                pagination
+                pagination,
+                optLock,
+                new PerformanceInterceptor()
         });
         sqlSessionFactory.setGlobalConfig(globalConfiguration);
         return sqlSessionFactory.getObject();
@@ -49,6 +55,10 @@ public class MybatisPlusConfig {
 
     @Bean
     public GlobalConfiguration globalConfiguration() {
-        return new GlobalConfiguration();
+        GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
+        conf.setLogicDeleteValue("D");
+        conf.setLogicNotDeleteValue("A");
+        conf.setIdType(2);
+        return conf;
     }
 }

+ 56 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/config/MybatisPlusNoOptLockConfig.java

@@ -0,0 +1,56 @@
+package com.baomidou.mybatisplus.test.h2.config;
+
+import javax.sql.DataSource;
+
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.type.JdbcType;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+
+import com.baomidou.mybatisplus.MybatisConfiguration;
+import com.baomidou.mybatisplus.MybatisXMLLanguageDriver;
+import com.baomidou.mybatisplus.entity.GlobalConfiguration;
+import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
+import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
+
+/**
+ * <p>
+ * Mybatis Plus Config without OptimisLock
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+@Configuration
+@MapperScan("com.baomidou.mybatisplus.test.h2.entity.mapper")
+public class MybatisPlusNoOptLockConfig {
+
+    @Bean("mybatisSqlSession")
+    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ResourceLoader resourceLoader, GlobalConfiguration globalConfiguration) throws Exception {
+        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
+        sqlSessionFactory.setDataSource(dataSource);
+//        sqlSessionFactory.setConfigLocation(resourceLoader.getResource("classpath:mybatis-config.xml"));
+        sqlSessionFactory.setTypeAliasesPackage("com.baomidou.mybatisplus.test.h2.entity.persistent");
+        MybatisConfiguration configuration = new MybatisConfiguration();
+        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
+        configuration.setJdbcTypeForNull(JdbcType.NULL);
+        sqlSessionFactory.setConfiguration(configuration);
+        PaginationInterceptor pagination = new PaginationInterceptor();
+        pagination.setDialectType("h2");
+        sqlSessionFactory.setPlugins(new Interceptor[]{
+                pagination,
+        });
+        sqlSessionFactory.setGlobalConfig(globalConfiguration);
+        return sqlSessionFactory.getObject();
+    }
+
+    @Bean
+    public GlobalConfiguration globalConfiguration() {
+        GlobalConfiguration globalConfiguration = new GlobalConfiguration();
+        globalConfiguration.setIdType(2);
+        return globalConfiguration;
+    }
+}

+ 16 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserDateVersionMapper.java

@@ -0,0 +1,16 @@
+package com.baomidou.mybatisplus.test.h2.entity.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserDateVersion;
+
+/**
+ * <p>
+ * H2User without version column
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+public interface H2UserDateVersionMapper extends BaseMapper<H2UserDateVersion> {
+
+}

+ 19 - 1
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserMapper.java

@@ -1,11 +1,17 @@
 package com.baomidou.mybatisplus.test.h2.entity.mapper;
 
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
 import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.baomidou.mybatisplus.plugins.Page;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2Addr;
 import com.baomidou.mybatisplus.test.h2.entity.persistent.H2User;
 
 /**
  * <p>
- * TODO class
  * </p>
  *
  * @author Caratacus
@@ -13,4 +19,16 @@ import com.baomidou.mybatisplus.test.h2.entity.persistent.H2User;
  */
 public interface H2UserMapper extends BaseMapper<H2User> {
 
+    @Select(
+            "select a.addr_id as addrId, a.addr_name as addrName from h2address a" +
+                    " join h2user u on u.test_id=a.test_id and u.test_id=#{userId}"
+    )
+    public List<H2Addr> getAddrListByUserId(@Param("userId") Long userId);
+
+    @Select(
+            "select a.addr_id as addrId, a.addr_name as addrName from h2address a" +
+                    " join h2user u on u.test_id=a.test_id and u.test_id=#{userId}"
+    )
+    public List<H2Addr> getAddrListByUserId(@Param("userId") Long userId, Page<H2Addr> page);
+
 }

+ 16 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/mapper/H2UserNoVersionMapper.java

@@ -0,0 +1,16 @@
+package com.baomidou.mybatisplus.test.h2.entity.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserNoVersion;
+
+/**
+ * <p>
+ * H2User without version column
+ * </p>
+ *
+ * @author Caratacus
+ * @date 2017/4/1
+ */
+public interface H2UserNoVersionMapper extends BaseMapper<H2UserNoVersion> {
+
+}

+ 59 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2Addr.java

@@ -0,0 +1,59 @@
+package com.baomidou.mybatisplus.test.h2.entity.persistent;
+
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+
+/**
+ * <p>
+ * h2address entity.
+ * </p>
+ *
+ * @author yuxiaobin
+ * @date 2017/5/25
+ */
+@TableName("h2address")
+public class H2Addr {
+
+    @TableId("addr_id")
+    private Long addrId;
+
+    @TableField("addr_name")
+    private String addrName;
+
+    @TableField("test_id")
+    private Long testId;
+
+    public Long getAddrId() {
+        return addrId;
+    }
+
+    public void setAddrId(Long addrId) {
+        this.addrId = addrId;
+    }
+
+    public String getAddrName() {
+        return addrName;
+    }
+
+    public void setAddrName(String addrName) {
+        this.addrName = addrName;
+    }
+
+    public Long getTestId() {
+        return testId;
+    }
+
+    public void setTestId(Long testId) {
+        this.testId = testId;
+    }
+
+    @Override
+    public String toString() {
+        return "H2Addr{" +
+                "addrId=" + addrId +
+                ", addrName='" + addrName + '\'' +
+                ", testId=" + testId +
+                '}';
+    }
+}

+ 184 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2UserDateVersion.java

@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2011-2014, hubin (jobob@qq.com).
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 com.baomidou.mybatisplus.test.h2.entity.persistent;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+import com.baomidou.mybatisplus.annotations.Version;
+import com.baomidou.mybatisplus.enums.FieldStrategy;
+
+/**
+ * <p>
+ * 测试用户类
+ * </p>
+ *
+ * @author hubin sjy
+ */
+/* 表名 value 注解【 驼峰命名可无 】, resultMap 注解测试【 映射 xml 的 resultMap 内容 】 */
+@TableName("h2user")
+public class H2UserDateVersion implements Serializable {
+
+    /* 表字段注解,false 表中不存在的字段,可无该注解 默认 true */
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    /* 主键ID 注解,value 字段名,type 用户输入ID */
+    @TableId(value = "test_id")
+    private Long id;
+
+    /* 测试忽略验证 */
+    private String name;
+
+    private Integer age;
+
+    /*BigDecimal 测试*/
+    private BigDecimal price;
+
+    /* 测试下划线字段命名类型, 字段填充 */
+    @TableField(value = "test_type", validate = FieldStrategy.IGNORED)
+    private Integer testType;
+
+    private String desc;
+
+    private Integer version;
+
+    @TableField(value = "test_date")
+    @Version
+    private Date testDate;
+
+    public H2UserDateVersion() {
+
+    }
+
+    public H2UserDateVersion(String name) {
+        this.name = name;
+    }
+
+    public H2UserDateVersion(Integer testType) {
+        this.testType = testType;
+    }
+
+    public H2UserDateVersion(String name, Integer age) {
+        this.name = name;
+        this.age = age;
+    }
+
+    public H2UserDateVersion(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public H2UserDateVersion(Long id, Integer age) {
+        this.id = id;
+        this.age = age;
+    }
+
+    public H2UserDateVersion(Long id, String name, Integer age, Integer testType) {
+        this.id = id;
+        this.name = name;
+        this.age = age;
+        this.testType = testType;
+    }
+
+    public H2UserDateVersion(String name, Integer age, Integer testType) {
+        this.name = name;
+        this.age = age;
+        this.testType = testType;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public Integer getAge() {
+        return age;
+    }
+
+    public void setAge(Integer age) {
+        this.age = age;
+    }
+
+    public Integer getTestType() {
+        return testType;
+    }
+
+    public void setTestType(Integer testType) {
+        this.testType = testType;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    public Integer getVersion() {
+        return version;
+    }
+
+    public void setVersion(Integer version) {
+        this.version = version;
+    }
+
+    public Date getTestDate() {
+        return testDate;
+    }
+
+    public void setTestDate(Date testDate) {
+        this.testDate = testDate;
+    }
+
+    @Override
+    public String toString() {
+        return "H2UserDateVersion{" +
+                "id=" + id +
+                ", name='" + name + '\'' +
+                ", age=" + age +
+                ", price=" + price +
+                ", testType=" + testType +
+                ", desc='" + desc + '\'' +
+                ", version=" + version +
+                ", testDate=" + testDate +
+                '}';
+    }
+}

+ 170 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/persistent/H2UserNoVersion.java

@@ -0,0 +1,170 @@
+/**
+ * Copyright (c) 2011-2014, hubin (jobob@qq.com).
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 com.baomidou.mybatisplus.test.h2.entity.persistent;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+import com.baomidou.mybatisplus.enums.FieldStrategy;
+
+/**
+ * <p>
+ * 测试用户类
+ * </p>
+ *
+ * @author hubin sjy
+ */
+/* 表名 value 注解【 驼峰命名可无 】, resultMap 注解测试【 映射 xml 的 resultMap 内容 】 */
+@TableName("h2user")
+public class H2UserNoVersion implements Serializable {
+
+    /* 表字段注解,false 表中不存在的字段,可无该注解 默认 true */
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    /* 主键ID 注解,value 字段名,type 用户输入ID */
+    @TableId(value = "test_id")
+    private Long id;
+
+    /* 测试忽略验证 */
+    private String name;
+
+    private Integer age;
+
+    /*BigDecimal 测试*/
+    private BigDecimal price;
+
+    /* 测试下划线字段命名类型, 字段填充 */
+    @TableField(value = "test_type", validate = FieldStrategy.IGNORED)
+    private Integer testType;
+
+    private String desc;
+
+    private Integer version;
+
+
+    public H2UserNoVersion() {
+
+    }
+
+    public H2UserNoVersion(String name) {
+        this.name = name;
+    }
+
+    public H2UserNoVersion(Integer testType) {
+        this.testType = testType;
+    }
+
+    public H2UserNoVersion(String name, Integer age) {
+        this.name = name;
+        this.age = age;
+    }
+
+    public H2UserNoVersion(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public H2UserNoVersion(Long id, Integer age) {
+        this.id = id;
+        this.age = age;
+    }
+
+    public H2UserNoVersion(Long id, String name, Integer age, Integer testType) {
+        this.id = id;
+        this.name = name;
+        this.age = age;
+        this.testType = testType;
+    }
+
+    public H2UserNoVersion(String name, Integer age, Integer testType) {
+        this.name = name;
+        this.age = age;
+        this.testType = testType;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public Integer getAge() {
+        return age;
+    }
+
+    public void setAge(Integer age) {
+        this.age = age;
+    }
+
+    public Integer getTestType() {
+        return testType;
+    }
+
+    public void setTestType(Integer testType) {
+        this.testType = testType;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    public Integer getVersion() {
+        return version;
+    }
+
+    public void setVersion(Integer version) {
+        this.version = version;
+    }
+
+    @Override
+    public String toString() {
+        return "H2User{" +
+                "id=" + id +
+                ", name='" + name + '\'' +
+                ", age=" + age +
+                ", price=" + price +
+                ", testType=" + testType +
+                ", desc='" + desc + '\'' +
+                ", version=" + version +
+                '}';
+    }
+}

+ 8 - 11
mybatis-plus/src/main/java/com/baomidou/mybatisplus/plugins/VersionHandler.java → mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/service/IH2UserNoVersionService.java

@@ -13,22 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.baomidou.mybatisplus.plugins;
+package com.baomidou.mybatisplus.test.h2.entity.service;
 
-import java.lang.reflect.Field;
+import com.baomidou.mybatisplus.service.IService;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserNoVersion;
 
 /**
  * <p>
- * 乐观锁处理器,底层接口
+ * Service层测试
  * </p>
  *
- * @author TaoYu 小锅盖
- * @since 2017-04-08
+ * @author hubin
+ * @date 2017-01-30
  */
-public interface VersionHandler<T> {
+public interface IH2UserNoVersionService extends IService<H2UserNoVersion> {
 
-    /**
-     * 根据类型,使得对象对应的字段+1
-     */
-    void plusVersion(Object paramObj, Field field, T versionValue) throws Exception;
-}
+}

+ 36 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/entity/service/impl/H2UserNoVersionServiceImpl.java

@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2011-2014, hubin (jobob@qq.com).
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 com.baomidou.mybatisplus.test.h2.entity.service.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.service.impl.ServiceImpl;
+import com.baomidou.mybatisplus.test.h2.entity.mapper.H2UserNoVersionMapper;
+import com.baomidou.mybatisplus.test.h2.entity.persistent.H2UserNoVersion;
+import com.baomidou.mybatisplus.test.h2.entity.service.IH2UserNoVersionService;
+
+/**
+ * <p>
+ * Service层测试
+ * </p>
+ *
+ * @author hubin
+ * @date 2017-01-30
+ */
+@Service
+public class H2UserNoVersionServiceImpl extends ServiceImpl<H2UserNoVersionMapper, H2UserNoVersion> implements IH2UserNoVersionService {
+
+}

+ 14 - 22
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/plugins/optimisticLocker/OptimisticLockerInterceptorTest.java

@@ -4,7 +4,6 @@ import java.io.Reader;
 import java.sql.Connection;
 import java.sql.Timestamp;
 import java.util.Date;
-import java.util.Objects;
 import java.util.Random;
 
 import org.apache.ibatis.io.Resources;
@@ -23,7 +22,6 @@ import com.baomidou.mybatisplus.mapper.EntityWrapper;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity.DateVersionUser;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity.IntVersionUser;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity.LongVersionUser;
-import com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity.StringVersionUser;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity.TimestampVersionUser;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.mapper.DateVersionUserMapper;
 import com.baomidou.mybatisplus.test.plugins.optimisticLocker.mapper.IntVersionUserMapper;
@@ -56,7 +54,7 @@ public class OptimisticLockerInterceptorTest {
 		SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession();
 		Connection conn = session.getConnection();
 		Reader reader = Resources
-				.getResourceAsReader("com/baomidou/mybatisplus/test/plugins/optimisticLocker/CreateDB.sql");
+				.getResourceAsReader("h2/optlock/CreateDB.sql");
 		ScriptRunner runner = new ScriptRunner(conn);
 		runner.setLogWriter(null);
 		runner.runScript(reader);
@@ -136,18 +134,6 @@ public class OptimisticLockerInterceptorTest {
 		Assert.assertTrue(timestampVersionUserMapper.selectById(15L).getVersion().after(originVersion));
 	}
 
-	@Test
-	public void stringVersionTest() {
-		// 查询数据
-		StringVersionUser versionUser = stringersionUserMapper.selectById(1);
-		String originVersion = versionUser.getTt();
-		// 更新数据
-		versionUser.setName("苗神");
-		stringersionUserMapper.updateById(versionUser);
-		Assert.assertEquals(stringersionUserMapper.selectById(1).getTt(),
-				String.valueOf(Long.parseLong(originVersion) + 1));
-	}
-
 	@Test
 	public void multiThreadVersionTest() {
 		final Random random = new Random();
@@ -178,25 +164,31 @@ public class OptimisticLockerInterceptorTest {
 	@Test
 	public void multiParamVersionTest() {
 		// 查询数据
-		IntVersionUser versionUser = intVersionUserMapper.selectById(2);
+		Long id = 2L;
+		IntVersionUser versionUser = intVersionUserMapper.selectById(id);
 		Integer originVersion = versionUser.getVersion();
 		// null条件
+		versionUser.setVersion(null);
 		intVersionUserMapper.update(versionUser, null);
-		Assert.assertTrue(Objects.equals(intVersionUserMapper.selectById(2).getVersion(), originVersion));
+		versionUser = intVersionUserMapper.selectById(id);
+		Assert.assertEquals(originVersion.intValue(), versionUser.getVersion().intValue());
 		// 空条件
+		versionUser.setVersion(null);
 		intVersionUserMapper.update(versionUser, new EntityWrapper<IntVersionUser>());
-		Assert.assertTrue(Objects.equals(intVersionUserMapper.selectById(2).getVersion(), originVersion));
+		Assert.assertEquals(intVersionUserMapper.selectById(id).getVersion(), originVersion);
 		// 正常查询不带version
 		IntVersionUser wrapper = new IntVersionUser();
 		wrapper.setName("lisi");
 		intVersionUserMapper.update(versionUser, new EntityWrapper<>(wrapper));
-		Assert.assertTrue(intVersionUserMapper.selectById(2).getVersion() == originVersion + 1);
+		versionUser = intVersionUserMapper.selectById(id);
+		Assert.assertEquals(versionUser.getVersion(), originVersion);
 		// 原始条件带version按原始逻辑走
 		IntVersionUser wrapper2 = new IntVersionUser();
 		wrapper2.setName("lisi");
-		wrapper2.setVersion(originVersion + 1);
-		intVersionUserMapper.update(versionUser, new EntityWrapper<>(wrapper2));
-		Assert.assertTrue(intVersionUserMapper.selectById(1).getVersion() == originVersion + 1);
+		wrapper2.setId(id);
+		wrapper2.setVersion(versionUser.getVersion() + 1);
+		int effRow = intVersionUserMapper.update(versionUser, new EntityWrapper<>(wrapper2));
+		Assert.assertEquals(0, effRow);
 	}
 
 	@Test // FIXME这个测试应该归属逻辑删除里

+ 0 - 13
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/plugins/optimisticLocker/StringTypeHandler.java

@@ -1,13 +0,0 @@
-package com.baomidou.mybatisplus.test.plugins.optimisticLocker;
-
-import java.lang.reflect.Field;
-
-import com.baomidou.mybatisplus.plugins.VersionHandler;
-
-public class StringTypeHandler implements VersionHandler<String> {
-
-    public void plusVersion(Object paramObj, Field field, String versionValue) throws Exception {
-        field.set(paramObj, String.valueOf(Long.parseLong(versionValue) + 1));
-    }
-
-}

+ 6 - 0
mybatis-plus/src/test/resources/h2/addr.ddl.sql

@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS  h2address (
+	addr_id BIGINT(20) NOT NULL ,
+	addr_name VARCHAR(30) NULL DEFAULT NULL ,
+	test_id BIGINT(20) NOT NULL ,
+	PRIMARY KEY (addr_id)
+)

+ 5 - 0
mybatis-plus/src/test/resources/h2/addr.insert.sql

@@ -0,0 +1,5 @@
+insert into h2address(addr_id,addr_name,test_id) values(1,'Suzhou', 101);
+insert into h2address(addr_id,addr_name,test_id) values(2,'Beijing', 101);
+insert into h2address(addr_id,addr_name,test_id) values(3,'Shanghai', 101);
+insert into h2address(addr_id,addr_name,test_id) values(4,'Guangzhou', 101);
+insert into h2address(addr_id,addr_name,test_id) values(5,'Hangzhou', 101);

+ 28 - 0
mybatis-plus/src/test/resources/h2/optlock/CreateDB.sql

@@ -0,0 +1,28 @@
+DROP TABLE
+IF EXISTS version_user;
+
+CREATE TABLE version_user (
+	id bigint (11) NOT NULL ,
+	NAME VARCHAR (20),
+	age INT (11),
+	version INT (11),
+	isDelete INT (11) DEFAULT 0,
+	PRIMARY KEY (`id`)
+) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
+
+insert into version_user (id,name,version) values(1,'zhangsan',15);
+insert into version_user (id,name,version) values(2,'lisi',109);
+insert into version_user (id,name,version) values(3,'wangwu',null);
+
+DROP TABLE
+IF EXISTS time_version_user;
+
+CREATE TABLE time_version_user (
+	id bigint (11) NOT NULL ,
+	NAME VARCHAR (20),
+	version datetime,
+	PRIMARY KEY (`id`)
+) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
+
+DROP TABLE
+IF EXISTS string_version_user;

+ 35 - 0
mybatis-plus/src/test/resources/h2/optlock/mybatis-config.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-config.dtd">
+
+<configuration>
+	<plugins>
+		<plugin interceptor="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"></plugin>
+		<plugin interceptor="com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor"></plugin>
+	</plugins>
+
+	<environments default="development">
+		<environment id="development">
+			<transactionManager type="JDBC">
+				<property name="" value="" />
+			</transactionManager>
+			<dataSource type="UNPOOLED">
+				<property name="driver" value="com.mysql.jdbc.Driver" />
+				<property name="url" value="jdbc:mysql://localhost:3306/mybatis-plus" />
+				<property name="username" value="root" />
+				<property name="password" value="!Ty5546380" />
+			</dataSource>
+
+		</environment>
+
+	</environments>
+
+	<mappers>
+		<mapper class="com.baomidou.mybatisplus.test.plugin.OptimisticLocker.mapper.IntVersionUserMapper" />
+		<mapper class="com.baomidou.mybatisplus.test.plugin.OptimisticLocker.mapper.LongVersionUserMapper" />
+		<mapper class="com.baomidou.mybatisplus.test.plugin.OptimisticLocker.mapper.DateVersionUserMapper" />
+		<mapper class="com.baomidou.mybatisplus.test.plugin.OptimisticLocker.mapper.TimestampVersionUserMapper" />
+	</mappers>
+
+</configuration>

+ 67 - 68
mybatis-plus/src/test/resources/h2/spring-jdbc.xml

@@ -1,75 +1,74 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
-	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
+<beans xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
-	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
-	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
+	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
 
-	<!-- 加载配置文件 -->
-	<context:property-placeholder location="classpath:properties/jdbc-h2.properties" />
+    <!-- 加载配置文件 -->
+    <context:property-placeholder location="classpath:properties/jdbc-h2.properties"/>
 
-	<!-- 数据库连接池 -->
-	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
-		destroy-method="close">
-		<property name="url" value="${jdbc.url}" />
-		<property name="username" value="${jdbc.username}" />
-		<property name="password" value="${jdbc.password}" />
-		<property name="driverClassName" value="${jdbc.driver}" />
-		<!-- 初始化连接大小 -->
-		<property name="initialSize" value="0" />
-		<!-- 连接池最大使用连接数量 -->
-		<property name="maxActive" value="20" />
-		<!-- 连接池最大空闲 -->
-		<property name="maxIdle" value="20" />
-		<!-- 连接池最小空闲 -->
-		<property name="minIdle" value="0" />
-		<!-- 获取连接最大等待时间 -->
-		<property name="maxWait" value="60000" />
-		<property name="testOnBorrow" value="false" />
-		<property name="testOnReturn" value="false" />
-		<property name="testWhileIdle" value="true" />
-		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
-		<property name="timeBetweenEvictionRunsMillis" value="60000" />
-		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
-		<property name="minEvictableIdleTimeMillis" value="25200000" />
-		<!-- 打开removeAbandoned功能 -->
-		<property name="removeAbandoned" value="true" />
-		<!-- 1800秒,也就是30分钟 -->
-		<property name="removeAbandonedTimeout" value="1800" />
-		<!-- 关闭abanded连接时输出错误日志 -->
-		<property name="logAbandoned" value="true" />
-		<!-- 监控数据库 -->
-		<property name="filters" value="stat" />
-	</bean>
+    <!-- 数据库连接池 -->
+    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
+          destroy-method="close">
+        <property name="url" value="${jdbc.url}"/>
+        <property name="username" value="${jdbc.username}"/>
+        <property name="password" value="${jdbc.password}"/>
+        <property name="driverClassName" value="${jdbc.driver}"/>
+        <!-- 初始化连接大小 -->
+        <property name="initialSize" value="0"/>
+        <!-- 连接池最大使用连接数量 -->
+        <property name="maxActive" value="20"/>
+        <!-- 连接池最大空闲 -->
+        <property name="maxIdle" value="20"/>
+        <!-- 连接池最小空闲 -->
+        <property name="minIdle" value="0"/>
+        <!-- 获取连接最大等待时间 -->
+        <property name="maxWait" value="60000"/>
+        <property name="testOnBorrow" value="false"/>
+        <property name="testOnReturn" value="false"/>
+        <property name="testWhileIdle" value="true"/>
+        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
+        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
+        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
+        <property name="minEvictableIdleTimeMillis" value="25200000"/>
+        <!-- 打开removeAbandoned功能 -->
+        <property name="removeAbandoned" value="true"/>
+        <!-- 1800秒,也就是30分钟 -->
+        <property name="removeAbandonedTimeout" value="1800"/>
+        <!-- 关闭abanded连接时输出错误日志 -->
+        <property name="logAbandoned" value="true"/>
+        <!-- 监控数据库 -->
+        <property name="filters" value="stat"/>
+    </bean>
 
-	<!-- 事务管理器 -->
-	<bean id="transactionManager"
-		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
-		<!-- 数据源 -->
-		<property name="dataSource" ref="dataSource" />
-	</bean>
+    <!-- 事务管理器 -->
+    <bean id="transactionManager"
+          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+        <!-- 数据源 -->
+        <property name="dataSource" ref="dataSource"/>
+    </bean>
 
-	<!-- 通知 -->
-	<tx:advice id="txAdvice" transaction-manager="transactionManager">
-		<tx:attributes>
-			<!-- 传播行为 -->
-			<tx:method name="save*" propagation="REQUIRED" />
-			<tx:method name="insert*" propagation="REQUIRED" />
-			<tx:method name="add*" propagation="REQUIRED" />
-			<tx:method name="create*" propagation="REQUIRED" />
-			<tx:method name="delete*" propagation="REQUIRED" />
-			<tx:method name="update*" propagation="REQUIRED" />
-			<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
-			<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
-			<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
-		</tx:attributes>
-	</tx:advice>
-	<!-- 切面 -->
-	<aop:config>
-		<aop:advisor advice-ref="txAdvice"
-			pointcut="execution(* com.baomidou.*.service.*.*.*(..))" />
-	</aop:config>
+    <!-- 通知 -->
+    <tx:advice id="txAdvice" transaction-manager="transactionManager">
+        <tx:attributes>
+            <!-- 传播行为 -->
+            <tx:method name="save*" propagation="REQUIRED"/>
+            <tx:method name="insert*" propagation="REQUIRED"/>
+            <tx:method name="add*" propagation="REQUIRED"/>
+            <tx:method name="create*" propagation="REQUIRED"/>
+            <tx:method name="delete*" propagation="REQUIRED"/>
+            <tx:method name="update*" propagation="REQUIRED"/>
+            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
+            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
+            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
+        </tx:attributes>
+    </tx:advice>
+    <!-- 切面 -->
+    <aop:config>
+        <aop:advisor advice-ref="txAdvice"
+                     pointcut="execution(* com.baomidou.*.service.*.*.*(..))"/>
+    </aop:config>
 </beans>

+ 9 - 8
mybatis-plus/src/test/resources/h2/spring-test-h2.xml

@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
-	xmlns:context="http://www.springframework.org/schema/context"
-	xmlns:mvc="http://www.springframework.org/schema/mvc"
-	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
-           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd  
-           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
-	<context:component-scan base-package="com.baomidou.mybatisplus.test.h2" />
+    <context:component-scan base-package="com.baomidou.mybatisplus.test.h2"/>
+
+    <bean name="/DBConfig" class="com.baomidou.mybatisplus.test.h2.config.DBConfig" />
+    <bean name="/MybatisPlusConfig" class="com.baomidou.mybatisplus.test.h2.config.MybatisPlusConfig" />
 
 </beans>

+ 13 - 0
mybatis-plus/src/test/resources/h2/spring-test-no-opt-lock-h2.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+    <context:component-scan base-package="com.baomidou.mybatisplus.test.h2"/>
+
+    <bean name="/DBConfig" class="com.baomidou.mybatisplus.test.h2.config.DBConfig" />
+    <bean name="/MybatisPlusConfig" class="com.baomidou.mybatisplus.test.h2.config.MybatisPlusNoOptLockConfig" />
+
+</beans>

+ 4 - 4
mybatis-plus/src/test/resources/h2/user.insert.sql

@@ -1,5 +1,5 @@
 insert into h2user(test_id, name, test_date, age, price, test_type, version) values (101,'Tomcat', '2017-1-1 1:1:1', 3, 9.99, 1, 1),
-insert into h2user(test_id, name, test_date, age, price, test_type, version) values (102,'Jerry', '2017-3-1 1:1:1', 2, 19.99, 1, 1);
-insert into h2user(test_id, name, test_date, age, price, test_type, version) values (103,'Bob', '2017-4-1 1:1:1', 13, 99.99, 1, 1);
-insert into h2user(test_id, name, test_date, age, price, test_type, version) values (104,'Joe', '2017-2-1 1:1:1', 18, 1.99, 1, 1);
-insert into h2user(test_id, name, test_date, age, price, test_type, version) values (105,'Tony', '2017-2-1 1:1:1', 18, 1.99, 1, 1);
+insert into h2user(test_id, name, test_date, age, price, test_type, version) values (102,'Jerry', '2017-3-1 1:1:1', 2, 19.99, 1, 2);
+insert into h2user(test_id, name, test_date, age, price, test_type, version) values (103,'Bob', '2017-4-1 1:1:1', 13, 99.99, 1, 3);
+insert into h2user(test_id, name, test_date, age, price, test_type, version) values (104,'Joe', '2017-2-1 1:1:1', 18, 1.99, 1, 4);
+insert into h2user(test_id, name, test_date, age, price, test_type, version) values (105,'Tony', '2017-2-1 1:1:1', 18, 1.99, 1, 5);

+ 1 - 7
mybatis-plus/src/test/resources/plugins/optimisticLockerInterceptor.xml

@@ -20,13 +20,7 @@
 			value="com.baomidou.mybatisplus.test.plugins.optimisticLocker.entity" />
 		<property name="plugins">
 			<array>
-				<bean class="com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor">
-					<property name="properties">
-						<value>
-							versionHandlers=com.baomidou.mybatisplus.test.plugins.optimisticLocker.StringTypeHandler
-						</value>
-					</property>
-				</bean>
+				<bean class="com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor"/>
 				<bean class="com.baomidou.mybatisplus.plugins.PerformanceInterceptor" />
 			</array>
 		</property>

+ 2 - 1
mybatis-plus/src/test/resources/properties/jdbc-h2.properties

@@ -1,4 +1,5 @@
 jdbc.driver=org.h2.Driver
-jdbc.url=jdbc:h2:~/test
+jdbc.url=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+#jdbc.url=jdbc:h2:~/test
 jdbc.username=sa
 jdbc.password=