mybatis-plus特性
1.字段类型转换器 如果出现实体字段类型与数据库类型不一致情况,比如实体是String但是数据库是Date,或者实体中是对象,而数据库中是varchar存储json;则可以通过字段类型转换器进行统一转换,无需业务层在每次存取时进行手段转换。
1.实体上设置转换器类型(设置之后,在通过基础接口的处理都会进行转换)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @TableName(autoResultMap = true) public class User { private String id; private String name; private String mobile; private String password; @TableLogic private Integer deleteStatus; @TableField(typeHandler = JacksonTypeHandler.class) private UserExtraInfo userExtraInfo; }
2.第一步骤中的设置仅导致通过内置接口的处理会进行转换,但是通过自定义XML的脚本语句不会进行转换。 法一:在mapper语句上通过@Results设置对应的映射关系
1 2 3 4 5 6 7 8 9 @Mapper public interface UserMapper extends CustomerMapper <User > { @Results(value = { @Result(column = "user_extra_info", property = "userExtraInfo", typeHandler = JacksonTypeHandler.class) }) @Select("select * from user") List<User> testXmlSql () ; }
法二:需要在xml文件中配置resultMap结果集,并在结果集映射中设置对应的类型转换器。
1 2 3 4 5 @Mapper public interface UserMapper extends CustomerMapper <User > { List<User> testXmlSql () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <mapper namespace ="cn.com.xiaocainiaoya.springbootsimple.mapper.UserMapper" > <resultMap type ="cn.com.xiaocainiaoya.springbootsimple.entity.User" id ="procurementTemplateMap" > <result property ="id" column ="ID" /> <result property ="name" column ="NAME" /> <result property ="mobile" column ="MOBILE" /> <result property ="password" column ="PASSWORD" /> <result property ="deleteStatus" column ="DELETE_STATUS" /> <result property ="userExtraInfo" column ="USER_EXTRA_INFO" typeHandler = "com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" /> </resultMap > <select id ="testXmlSql" resultMap ="procurementTemplateMap" > select * from user </select > </mapper >
2.基础类扩展 直接通过继承mybatis-plus提供的三层基础接口,可以直接具备一些数据操作能力,比如通过deleteById可以直接通过id删除记录,无需编写SQL语句。同时mybatis-plus还提供了对基础层的扩展,用户可自定义扩展点后,业务处理再继承于自定义的扩展点,使得系统应用具备自定义扩展底层接口的能力。
上图中userFacade、userService、userMapper是业务层;customerService<User>、CustomerServiceImpl、CustomerMapper是自定义扩展层;IService<T>、ServiceImpl、BaseMapper是mybatis-plus的三次基础接口。
假设现在要扩展Insert ignore的插入语句
1.继承AbstractMethod编写具体SQL的拼写等处理: 可以参考原有的其他对AbstractMethod的实现,比如com.baomidou.mybatisplus.core.injector.methods。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class InsertIgnore extends AbstractMethod { @Override public MappedStatement injectMappedStatement (Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); CustomerSqlMethod sqlMethod = CustomerSqlMethod.INSERT_IGNORE_ONE; String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(null ), LEFT_BRACKET, RIGHT_BRACKET, null , COMMA); String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null ), LEFT_BRACKET, RIGHT_BRACKET, null , COMMA); String keyProperty = null ; String keyColumn = null ; if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(), tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this .addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn); } }
2.设置自定义SQL注入器并注入到Spring容器: 将步骤一中创建的具体SQL执行器注入到mybatis-plus相应处理中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class CustomerSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList (Class<?> mapperClass) { List<AbstractMethod> methodList = super .getMethodList(mapperClass); methodList.add(new InsertIgnore()); return methodList; } } @Bean public ISqlInjector iSqlInjector () { return new CustomerSqlInjector(); }
3.创建扩展的mapper层: 添加需要扩展的方法。
1 2 3 4 5 6 7 8 9 10 public interface CustomerMapper <T > extends BaseMapper <T > { int insertIgnore (T entity) ; }
4.创建扩展service层
1 2 3 4 5 6 7 8 9 10 public interface CustomerService <T > extends IService <T > { int insertIgnore (T entity) ; }
4.创建扩展serviceImpl层
1 2 3 4 5 6 7 public class CustomerServiceImpl <M extends CustomerMapper <T >, T > extends ServiceImpl <M , T > implements CustomerService <T > { @Override public int insertIgnore (T entity) { return baseMapper.insertIgnore(entity); } }
5.到这里,业务层只要继承于扩展层,就具备了自定扩展的处理能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Mapper public interface UserMapper extends CustomerMapper <User > {} public interface UserFacade extends CustomerService <User > { void insertUser () ; } @Service public class UserService extends CustomerServiceImpl <UserMapper , User > implements UserFacade { @Override public void insertUser () { UserExtraInfo userExtraInfo = UserExtraInfo.builder() .address("北京市" ) .city("北京" ) .build(); User user = User.builder() .id("12312311" ) .name("name" ) .mobile("mobile" ) .password("password" ) .deleteStatus(0 ) .userExtraInfo(userExtraInfo) .build(); insertIgnore(user); } }
3.逻辑删除 只需要设置@TableLogic就具备了逻辑删除的能力,但是这仅限制与通过mybatis-plus的基础接口处理数据,如果是通过xml手写SQL是无法自动补充逻辑删除条件。
插入语句不处理,也就是说默认值需要业务方插入或者通过数据库设置默认值来处理
查找、更新会追加查询条件
删除转为更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class User { private String id; private String name; private String mobile; private String password; @TableLogic private Integer deleteStatus; }
4.对第2点中的注入器分析 首先在mybatis对持久层进行解析时,需要对xml等进行解析,mybatis-plus对mybatis对xml解析进行了扩展,解析时会进入到MybatisMapperAnnotationBuilder#parse方法。每一个XxxMapper都会进入到这个解析方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Override public void parse () { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); String mapperName = type.getName(); assistant.setCurrentNamespace(mapperName); parseCache(); parseCacheRef(); InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type); for (Method method : type.getMethods()) { if (!canHaveStatement(method)) { continue ; } if (getAnnotationWrapper(method, false , Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null ) { parseResultMap(method); } try { InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method); SqlParserHelper.initSqlParserInfoCache(mapperName, method); parseStatement(method); } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MybatisMethodResolver(this , method)); } } try { if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { parserInjector(); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new InjectorResolver(this )); } } parsePendingMethods(); }
进入parserInjector()方法。
1 2 3 4 5 6 7 void parserInjector () { GlobalConfigUtils.getSqlInjector(configuration) .inspectInject(assistant, type); }
进入AbstractSqlInjector#inspectInject方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public void inspectInject (MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null ) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List<AbstractMethod> methodList = this .getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found." ); } mapperRegistryCache.add(className); } } }
自定义的CustomerSqlInjector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class CustomerSqlInjector extends DefaultSqlInjector { public List<AbstractMethod> getMethodList (Class<?> mapperClass) { List<AbstractMethod> methodList = super .getMethodList(mapperClass); methodList.add(new InsertIgnore()); return methodList; } } public class DefaultSqlInjector extends AbstractSqlInjector { @Override public List<AbstractMethod> getMethodList (Class<?> mapperClass) { return Stream.of( new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage() ).collect(toList()); } }
下面进入到具体注入器中,这里以mybatis-plus内置的一个但未启用的一个注入器InsertBatchSomeColumn为例。它是在com.baomidou.mybatisplus.extension.injector.methods目录下,这里内置了好几个未启用的注入器。InsertBatchSomeColumn是对批量插入的SQL注入器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @NoArgsConstructor @AllArgsConstructor @SuppressWarnings("serial") public class InsertBatchSomeColumn extends AbstractMethod { @Setter @Accessors(chain = true) private Predicate<TableFieldInfo> predicate; @SuppressWarnings("Duplicates") @Override public MappedStatement injectMappedStatement (Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); SqlMethod sqlMethod = SqlMethod.INSERT_ONE; List<TableFieldInfo> fieldList = tableInfo.getFieldList(); String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false ) + this .filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY); String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0 , insertSqlColumn.length() - 1 ) + RIGHT_BRACKET; String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false ) + this .filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY); insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0 , insertSqlProperty.length() - 1 ) + RIGHT_BRACKET; String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list" , null , ENTITY, COMMA); String keyProperty = null ; String keyColumn = null ; if (tableInfo.havePK()) { if (tableInfo.getIdType() == IdType.AUTO) { keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this .addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn); } @Override public String getMethod (SqlMethod sqlMethod) { return "insertBatchSomeColumn" ; } }
5.批量操作 在mybatis-plus中提供了一些批量操作,比如有updateBatchById、saveBatch、saveOrUpdateBatch等,这种批量方式与第4点中注入器的方式底层处理上是有本质不同的 。mybatis-plus提供的这些批量操作,本质上是对mybatis的批量操作再次封装,而mybatis的批量操作是对JDBC批处理进行封装。
这种批量操作实际上是在一个session会话中,批量的执行一堆语句。而第4点种的注入器的实现 是通过将批量插入的语句拼接成insert into tableName(field1, field2..) values(...)(....),这里只有一条插入语句。
如果从执行的效率出发,使用将批量插入的语句拼接为一条长语句