Data Access 之 MyBatis Plus(四)- MyBatis Plus Plugin

2022-08-19 17:52:32 浏览数 (1)

一、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,更新失败

0 人点赞