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(...)(....)
,这里只有一条插入语句。
如果从执行的效率出发,使用将批量插入的语句拼接为一条长语句