一、MyBatis 的插件机制
MyBatis Plus的插件机制也是基于MyBatis的插件机制;MyBatis通过插件Interceptor可以拦截四大组件相关方法的执行,完成相关数据的动态改变。这里所提到的MyBatis中的四大组件既:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
这四个组件在创建时都会执行interceptorChain.pluginAll()方法,该方法会循环调用拦截器列表中每一个拦截器的plugin()方法,该方法会为四大组件创建并返回代理对象,从而可以通过代理对象进行方法拦截,达到增强目标方法的目的
以StatementHandler为例,BaseStatementHandler抽象类实现类StatementHandler接口,
BaseStatmentHandler包含了一个构造方法,构造方法中包含了parameterHandler属性,该属性通过newParameterHandler()方法创建
这里就是调用了pluginAll()方法
循环所有的拦截器,调用拦截器的plugin()方法,返回代理对象
创建工程
拷贝mybatis-plus-mpg项目重命名为mybatis-plus-interceptor
二、MyBatis Plus PaginationInnerInterceptor插件
MP的分页插件是PaginationInnerInterceptor,该接口实现了InnerInterceptor接口,MyBatisPlusInterceptor实现了Interceptor接口,MyBatisPlusInterceptor接口包含了一个InnerInterceptorList属性
并且实现类Interceptor接口的plugin()方法,plugin()方法中有调用了wrap()方法,该方法通过反射生成代理对象
这也就是为什么配置分页插件时要先配置一个InnerInterceptor
代码语言:javascript复制<!--配置分页插件-->
<bean id="paginationInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
<property name="dbType" value="MYSQL"></property>
</bean>
再配置一个Interceptor,并引用上面配置的InnerInterceptor
代码语言:javascript复制<!--配置拦截器-->
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors" ref="paginationInnerInterceptor"></property>
</bean>
最后再将Interceptor配置到SQLSessionFactoryBean中
代码语言:javascript复制<!--替换为MyBatis-Plus的MyBatisSqlSessionFactoryBean,原来是MyBatis的SqlSessionFactoryBean-->
<bean id="mybatisSqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<!--设置使用分页插件,否则分页会失效-->
<property name="plugins" ref="mybatisPlusInterceptor"></property>
</bean>
在TeslaMapperTest中增加方法
代码语言:javascript复制@Test
public void testPage(){
Page<Tesla> page = new Page<>(2, 4);
Page<Tesla> teslaPage = teslaMapper.selectPage(page, null);
System.out.println("获取总记录数:" teslaPage.getRecords().size());
}
selectPage()方法会返回一个Page对象,这个Page对象中包含分页相关的信息
代码语言:javascript复制System.out.println("----Page对象的属性和方法----");
System.out.println("获取总记录数:" teslaPage.getTotal());
System.out.println("获取当前页面的记录:" teslaPage.getRecords());
System.out.println("是否有下一页:" teslaPage.hasNext());
System.out.println("是否有上一页:" teslaPage.hasPrevious());
System.out.println("总页数为:" teslaPage.getPages());
System.out.println("当前页数为:" teslaPage.getCurrent());
System.out.println("当前页记录数为:" teslaPage.getSize());
三、MyBatis Plus BlockAttackInnerInterceptor 防止全表更新与删除
在MP 3.4.0 版本之后SqlExplainInterceptor插件被删除,BlockAttackInnerInterceptor可以替代SQLExplainInterceptor来实现防止全表更新与删除的功能,具体可以参考官网 MyBatis Plus 插件主体
BlockAttackInnerInterceptor的作用是分析DELETE和UPDATE语句防止全表更新或者全表删除,适用于MySQL 5.6 版本以上,并且只建议在开发环境使用,不建议在生产环境使用
配置 BlockAttackInnerInterceptor 插件
配置interceptors拦截器列表,将分页插件和BlockAttackInnerInterceptor一起配置到mybatisPlusInterceptor的interceptors属性中,通过list标签来配置
代码语言:javascript复制<!--配置拦截器-->
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<!--配置分页插件-->
<bean id="paginationInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
<property name="dbType" value="MYSQL"></property>
</bean>
<!--防止全表更新删除插件-->
<bean id="blockAttackInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor">
</bean>
</list>
</property>
</bean>
测试插件
代码语言:javascript复制@Test
public void testBlockAttackInnerInterceptor(){
// 全表删除
teslaMapper.delete(null);
}
SQL执行失败,成功阻止了全表删除操作,也可以参考官方文档中BlockAttackInnerInterceptor 的使用方式
四、MyBatis Plus IllegalSQLInnerInterceptor 不规范SQL拦截器插件
IllegalSQLInnerInterceptor插件可以对不规范的SQL进行拦截
代码语言:javascript复制<!--配置拦截器-->
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<bean id="illegalSQLInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor">
</bean>
</list>
</property>
</bean>
再次执行testPage()方法
此时会报错,因为SQL语句中没有WHERE关键字
该插件会拦截的SQL语句类型为:
- 必须使用到索引,包含left join连接字段,符合索引最左原则
- 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据
- 如果检查到使用了索引,SQL性能基本不会太差
- SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做,SQL尽量单表执行的好处:
- 查询条件简单、易于开理解和维护
- 扩展性极强;(可为分库分表做准备)
- 缓存利用率高
- where条件为空、!=、包含not、or、子查询,都会拦截
五、MyBatis Plus OptimisticLockerInnerInterceptor 乐观锁插件
什么是乐观锁? 乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);
如果想实现如下需求,既当要更新一条记录时,希望这条记录没有被别人更新
乐观锁的实现原理:
- 取出记录时,获取当前的version
- 更新时带上这个version
- 执行更新时 version在原来的基础上 1
- 如果version不一致,更新失败
配置乐观锁插件
代码语言:javascript复制<!--配置拦截器-->
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<!--其他插件配置省略-->
<!--乐观锁插件-->
<bean id="optimisticLockerInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor">
</bean>
</list>
</property>
</bean>
给Tesla实体类添加Integer类型的version字段,并添加@Version注解,数据库表中添加对应的version中字段
TeslaMapperTest中增加测试代码
代码语言:javascript复制@Test
public void testOptimisticLockerInnerInterceptor(){
// 更新操作
Tesla tesla = new Tesla();
tesla.setId(1166057518);
tesla.setName("Model 3 2022");
tesla.setPrice(280000.00);
tesla.setVehicleType("四门轿车");
tesla.setFactory("上海工厂");
tesla.setVersion(1);
teslaMapper.updateById(tesla);
}
更新条件多了一个version字段,并且更新之后数据库中version字段从1变为2,
代码语言:javascript复制tesla.setFactory("上海工厂");
重新setFactory之后再次执行测试,此时代码中的version字段和数据库中的字段已经不一致
更新函数为0,更新失败