Mybatis Plus启动过程分析
MybatisPlus很多方法都是直接继承Mybatis的方法进行修改
MybatisConfiguration→MybatisMapperRegistry→AutoSqlInjector..injectSqlRunner→MybatisPlusAutoConfiguration..sqlSessionFactory→MybatisSqlSessionFactoryBean..buildSqlSessionFactory→XMLMapperBuilder..parse()→XMLMapperBuilder..configurationElement()→XMLMapperBuilder..buildStatementFromContext()→XMLStatementBuilder..parseStatementNode→MapperBuilderAssistant..addMappedStatement()→configuration.addMappedStatement()
MybatisConfiguration实例化,MybatisMapperRegistry实例化注入SqlRunner,MybatisPlusAutoConfiguration实例化并调用sqlSessionFactory()
MybatisPlusAutoConfiguration.sqlSessionFactory()
MybatisSqlSessionFactoryBean..buildSqlSessionFactory加载自定义MybatisXmlConfigBuilder/MybatisConfiguration并且调用xmlMapperBuilder.parse(){configurationElement();//解析xml},xmlMapperBuilder.parse()调用configurationElement()再调用buildStatementFromContext()解析xml中的sql成Statement,解析成功调用builderAssistant.addMappedStatement(),builderAssistant.addMappedStatement()再调用configuration.addMappedStatement(statement)添加到配置中
返回xmlMapperBuilder.parse(){bindMapperForNamespace();//绑定命名空间等},执行bindMapperForNamespace(){configuration.addMapper(boundType)}调用configuration.addMapper(boundType)调用 mapperRegistry.addMapper(type),mapperRegistry.addMapper(type)中调用MybatisMapperRegistry..addMapper(),MybatisMapperRegistry.addMapper()中调用MybatisMapperAnnotationBuilder..parse()解析无xml注解,注入扩展全局crud,MybatisMapperAnnotationBuilder.parse()调用loadXmlResource()如果已经加载xmlResource不进行再次加载
MybatisMapperAnnotationBuilder..parse()中GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type)注入动态CRUD
MybatisSqlSessionFactoryBean..buildSqlSessionFactory执行完初始化逐层返回
MybatisMapperRegistry替换原生MapperRegistry调用addMapper()解析Mapper对应的xml文件
MybatisPlus自动配置
1 | public class MybatisPlusAutoConfiguration { |
拷贝类 org.mybatis.spring.SqlSessionFactoryBean 修改方法 buildSqlSessionFactory()
1 | /** |
XMLMapperBuilder
1 | package org.apache.ibatis.builder.xml; |
Configuration配置信息
1 | public class Configuration { |
MybatisMapperRegistry Mapper注册
1 | package com.baomidou.mybatisplus; |
MybatisMapperAnnotationBuilder 没有XML配置文件注入基础CRUD方法
1 | /** |
Mybatis全局缓存工具类
1 | public class GlobalConfigUtils { |
自动接口注入器
1 | /** |
自动注入器
1 |
|
TableInfoHelper 实体反射辅助类
1 | package com.baomidou.mybatisplus.toolkit; |
MapperBuilderAssistant
1 | package org.apache.ibatis.builder; |
MapperProxyFactory 代理工厂
1 | public class MapperProxyFactory<T> { |
XMLStatementBuilder
1 | public class XMLStatementBuilder extends BaseBuilder { |
MybatisConfiguration
1 | public class MybatisConfiguration extends Configuration { |
MappedStatement
1 | package org.apache.ibatis.mapping; |
调用方法MappedStatement中的SQL过程
- 使用了动态代理,生成mapper接口的代理类实现
MapperRegistry本质上用于注册Mapper接口和获取MapperProxy类,MapperProxy并通过MapperProxyFactory进行实例化,然而在MapperProxy的invoke()方法中会调用Mapper接口与之对应的MapperMethod类中的execute()方法。MapperRegistry依赖于Mapper类路径,SqlSession作为入参,结合MapperProxy,MapperProxyFactory,ResolverUtil,MapperAnnotationBuilder等组件才完成了一次注册Mapper到SQL
- CglibAopProxy,spring使用的动态代理进入实现
- 调用过程ServiceImpl..selectPage()→org.apache.ibatis.binding.MapperProxy→MapperProxy..invoke()→MapperMethod..execute→execute..executeForMany→SqlSessionTemplate..selectList→SqlSessionTemplate..SqlSessionInterceptor..invoke()→Method..invoke()→DelegatingMethodAccessorImpl..invoke()→DefaultSqlSession..selectList()→BaseExecutor..query()→BaseExecutor..queryFromDatabase()→SimpleExecutor..doQuery()→Plugin..invoke()→Method..invoke()→DelegatingMethodAccessorImpl.invoke()→RoutingStatementHandler.query()→PreparedStatementHandler.query()PreparedStatementLogger..execute()→DruidPooledPreparedStatement.execute()→DefaultResultSetHandler..handleResultSets()
MapperProxy代理类
1 | public class MapperProxy<T> implements InvocationHandler, Serializable { |
1 | public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> { |
1 | public class MapperMethod { |
1 | package org.mybatis.spring; |
1 | public class DefaultSqlSession implements SqlSession { |
1 | public abstract class BaseExecutor implements Executor { |
1 | package org.apache.ibatis.executor; |
1 | public class Plugin implements InvocationHandler { |
1 | package org.apache.ibatis.executor.statement; |
1 | package org.apache.ibatis.executor.statement; |
1 | package org.apache.ibatis.logging.jdbc; |
1 | package com.alibaba.druid.pool; |
1 | package com.alibaba.druid.proxy.jdbc; |
1 | package com.alibaba.druid.proxy.jdbc; |
1 | public class ConnectionProxyImpl extends WrapperProxyImpl implements ConnectionProxy { |
1 | public class DruidPooledConnection extends PoolableWrapper implements PooledConnection, Connection { |
1 | public class DefaultResultSetHandler implements ResultSetHandler { // 结果处理 |
使用
mybatis-plus updateById无法更新空字符串或者null
最主要还是Integer类型会出现Null导致无法更新,所以最好设计的时候需要更新成空值的字段用char代替,然后个别必须使用Integer的接口单独使用完全更新模式处理。
方法零:
前端不传递值时(空值),服务端一般处理为不修改
方法一:
1 | #字段策略 0:"忽略判断,所有字段都更新和插入",1:"非 NULL 判断,只更新和插入非NULL值"),2:"非空判断,只更新和插入非NULL值且非空字符串" |
把field-strategy
设置成0
方法二:
@TableField(strategy = FieldStrategy.IGNORED)
在需要更新插入的字段上加注解,比如Integer类型的字段由于""
会被框架转换成null
,然后配置文件配置成field-strategy: 1
,这样会导致Integer字段无法更新成空
注意:如果设置成0
会导致界面没传的字段被更新成null
对于只传输一部分数据的接口只能后台先查询下旧数据再把新数据覆盖到对象里在进行更新然后配置文件配置成field-strategy: 0
,如果页面传入的值有Integer类型""
被转换成了null到底是要更新还是不更新,可能部分字段没传也是null,如果更新了还是会导致数据丢失,如果null不更新会导致Integer类型无法设置成空。估计只能放弃Integer类型,使用String类型,或者更新接口拆分出来再做一个只更新部分字段的接口,或者更新操作前端所有参数都传
使用Map没这种问题。
组合使用:
- 比如把
creator
这种创建者设置成@TableField(strategy = FieldStrategy.NOT_EMPTY)
,更新的时候前端可以不传,然后后端设置成空值进行屏蔽。默认策略设置成用field-strategy: 0
策略,这样需要特殊处理的地方单独加TableField
进行设置。保证前端更新的时候传输完整的对象。 - 或者默认设置成
field-strategy: 1
策略。对应整数类型设置成@TableField(strategy = FieldStrategy.IGNORED)
保证表单中的整数类型都要传输值。对应只想传部分值的接口单独加接口单独写update
- 直接使用策略2,不让清空。根据情况使用
updateAllColumnById
全部字段更新: 3.0已经移除,3.0使用UpdateWrapper
- 直接使用策略1,Integer类型与datetime的无法清空,不做处理
- 单独加接口,更新指定字段
1
2
3
4
5
6
7
8
9
10
11
12
13UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name","1");
User user = new User();
user.setAge(35);
Integer rows = userMapper.update(user, updateWrapper);
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name","1").set("age", 35);
Integer rows = userMapper.update(null, updateWrapper);
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(User::getRealName, "1").set(User::getAge, 34);
Integer rows = userMapper.update(null, lambdaUpdateWrapper);
通过注解+拦截器统一实现数据过滤
DataFilterAspect
数据过滤切面,对加注解的方法统一注入过滤SQL片段,还可配合addFilterIfNeed
方法public @interface DataFilter
@DataFilter(subDept = true, user = false)
伪删除、逻辑删除
- 配置文件:
1 | mybatis-plus: |
- 实体类添加逻辑删除注解系统就会自动使用逻辑删除:
1 |
|
主键策略配置
1 | public enum IdType { |
1 |
|
命名规范
同样是保存需要进行特殊处理的时候,使用自定义方法,都应该写在service层,你不要重写plus的方法。方法名统一成save+业务saveUser或者固定saveItem以此类推、updateItem、removeItem、getItem(当个)、listItem(listItems 复数,用不用复数要统一)。