MyBatis-Plus开发模式 为什么开发MyBatis-Plus MyBatis的缺点
自动化程度不高,需要写SQL语句操作业务数据
对字段名称的识别校验不友好,易出错不好排查
通过XML配置映射字段和属性,影响开发效率 MyBatis-Plus的特点
不使用SQL,少量代码即可实现CRUD
支持Lambda,字段强校验
内置代码生成器,自动生成各层框架代码
内置分页,性能分析,全局拦截插件
MyBtisPlus-ActiveRecord模式 一般不使用,而是分层model和mapper,service分开
MyBatisPlus-整体架构
查询
Id生成策略
分页
逻辑删除
客服系统案例演进 升级策略 由Mybatis 升级成Mybatis-Plus相关改造点
依赖 1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> </dependency>
配置 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 spring: application: name: customer-service jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai datasource: dynamic: primary: master druid: initial-size: 3 min-idle: 3 max-active: 40 max-wait: 60000 datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/customer_system?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root mybatis-plus: mapper-locations: classpath:mapperXml/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 逻辑删除字段名 logic-delete-field: is_deleted # 逻辑删除字面值:未删除为0 logic-not-delete-value: 0 # 逻辑删除字面值:删除为1 logic-delete-value: 1
model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @TableName("customer_staff") public class CustomerStaff implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private Long id; private String name; /** * 是否删除,1=删除,0=未删除 */ @TableLogic private Boolean isDeleted; }
mapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface CustomerStaffMapper extends BaseMapper<CustomerStaff> { default CustomerStaff findCustomerStaffByPhoneNumber(String phoneNumber) { LambdaQueryWrapper<CustomerStaff> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(CustomerStaff::getPhone, phoneNumber); queryWrapper.eq(CustomerStaff::getIsDeleted, false); return selectOne(queryWrapper); } }
service 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 public class CustomerStaffServiceImpl extends ServiceImpl<CustomerStaffMapper, CustomerStaff> implements ICustomerStaffService { public List<CustomerStaff> findCustomerStaffs() { LambdaQueryWrapper<CustomerStaff> queryWrapper = new LambdaQueryWrapper<>(); return baseMapper.selectList(queryWrapper); } public PageObject<CustomerStaff> findCustomerStaffs(Long pageSize, Long pageIndex) { return getCustomerStaffPageObject(null, pageSize, pageIndex); } private PageObject<CustomerStaff> getCustomerStaffPageObject(String staffName, Long pageSize, Long pageIndex) { LambdaQueryWrapper<CustomerStaff> queryWrapper = new LambdaQueryWrapper<>(); // queryWrapper.eq(CustomerStaff::getIsDeleted, false); if(!Objects.isNull(staffName)) { queryWrapper.like(CustomerStaff::getStaffName, staffName); } queryWrapper.orderByDesc(CustomerStaff::getCreateTime); IPage<CustomerStaff> page = new Page<>(pageIndex, pageSize); IPage<CustomerStaff> pagedResult = baseMapper.selectPage(page, queryWrapper); PageObject<CustomerStaff> pagedObject = new PageObject<CustomerStaff>(); pagedObject.buildPage(pagedResult.getRecords(), pagedResult.getTotal(), pagedResult.getCurrent(), pagedResult.getSize()); return pagedObject; } @Override public Boolean deleteCustomerStaffById(Long staffId) { //通过更新操作实现逻辑删除 // CustomerStaff customerStaff = new CustomerStaff(); // customerStaff.setId(staffId); // customerStaff.setIsDeleted(true); // // return updateById(customerStaff); //通过逻辑删除为来进行逻辑删除 return this.removeById(staffId); } }
分页配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //1 创建MybatisPlusInterceptor拦截器对象 MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor(); //2 添加分页拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; } }
MyBatis-Plus SQL执行流程 MyBatisMapper获取流程 代码流程 最终通过创建了一个代理类MapperProxy, 代理对象MapperProxy内部创建MapperMethod实例通过SqlSession进一步完成sql执行操作
MapperMethod 内部使用SqlSession
SqlSession获取过程
SqlSession执行流程
MappedStatement sql语句封装对象
Executor 执行器
BaseExecutor 一级缓存
CachingExecutor 二级缓存
Executor继承关系图
https://docs.qq.com/flowchart/DRUhkS1V6YmVZb1ZZ
二级缓存建议关闭。
1 2 3 4 mybatis: configuration: cache-enabled: false local-cache-scope: session
使用二级缓存的异常场景:两个线程都有自己的二级、一级缓存,线程1更新操作并清空二级缓存,此时线程2可能查找到一级缓存的脏数据。
读源码时的疑问 BaseExecutor为什么先put一个枚举值,再执行查询,再删除k/v,再缓存查询结果呢?如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
执行流程总结 处理流程总结:
SqlSessionFactoryBean用来生成SqlSessionFactory
MapperFactoryBean初始化时会生成一个线程安全的SqlSession对象。
MapperFactoryBean继承自SqlSessionDaoSupport,SqlSessionDaoSupport包含SqlSessionTemplate对象,SqlSessionTemplate包含一个SqlSessionProxy对象SqlSessionProxy创建时会保存到ThreadLocal中以保证线程安全。
SqlSessionTemplate(SqlSession)初始化代理对象时,调用openSessionFromDataSource方法,初始化excutor,开启事务,返回SqlSession对象
SqlSession.getMapper()时获取到的是mapper代理对象
configuration.getMapper(SqlSession);
MapperProxy包含一个MapperMethod类,用来执行sql语句。
MapperMethod内部通过SqlSession执行SQL语句。
SqlSession通过Executor对象执行
Executor分为BaseExcutor和CachingExcutor
Executor内部使用 StatementHandler、ResultHandler执行sql语句,处理结果
问题