Spring Boot 集成Mybatis-Plus(三)

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

前言

上一张讲解了mybatis-plus高级特性的条件构造器,通过条件构造器可以实现复杂的条件查询,本文将讲解Mybatis-plus的相关插件。

常用插件

图片.png

分页插件

mybatis-plus提供了分页功能,需要开启分页插件才能生效。

    @Bean
    public MybatisPlusInterceptor paginationInterceptor() 
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        return interceptor;
    }

  /**
     * 分页插件
     * @return
     */
    @Bean
    public PaginationInnerInterceptor paginationInnerInterceptor() 
    {
        PaginationInnerInterceptor paginationInnerInterceptor =new PaginationInnerInterceptor();
        //设置数据库类型为mysql
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        return paginationInnerInterceptor;
    }
复制代码

注意:Mybatis-plus3.2以后的版本需要添加插件到MybatisPlusInterceptor中,否则会失效。

SQl性能检测插件

简介

主要针对不规范的SQL进行拦截,防止全表扫描,从而影响性能。

示例

插件配置

  //添加插件
  
     @Bean
    public MybatisPlusInterceptor paginationInterceptor() 
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        
        //乐观锁
        interceptor.addInnerInterceptor(optimisticLockerInterceptor());
        
        //防全表更新与删除插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        
        //不规范SQL检测插件
        interceptor.addInnerInterceptor(illegalSQLInnerInterceptor());
        
        return interceptor;
    }

   /**
     * Sql性能检测插件
     * @return
     */
    @Bean
    public IllegalSQLInnerInterceptor illegalSQLInnerInterceptor() 
    {
        return new IllegalSQLInnerInterceptor();
    }
复制代码

示例代码

   @RequestMapping("/illegalSQLInnerInterceptor")
    public String illegalSQLInnerInterceTest()
    {
       List<TUser> tUsers = userService.getUserList();
       return "成功";
    }
复制代码

测试结果

### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件] with root cause
com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件
复制代码

可以看出针对不规范的SQL进行拦截。

忽略配置

如果需要对某个SQL语句进行忽略只需要配置illegalSql=1即可。

   @InterceptorIgnore(illegalSql = "1")
    List<TUser> getUserList();
复制代码

总结

虽然此插件可以一定程度上规范SQL书写,也可以拓展自己的SQL规范逻辑,但是此插件还是需要根据实际的生产环境视情况进行配置。

全局拦截插件

简介

由于程序的bug可能导致把整个表都更新或者删除,在生产环境中这是十分危险的事情。Mybatis-plus的针对update、delete语句进行了拦截,防止恶意的全表更新和全表删除。

示例

插件配置

@Bean
    public MybatisPlusInterceptor paginationInterceptor() 
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        //乐观锁
        interceptor.addInnerInterceptor(optimisticLockerInterceptor());
        
        //防全表更新与删除插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        return interceptor;
    }

/**
     * 防全表更新与删除插件
     * @return
     */
    @Bean
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor() 
    {
        return new BlockAttackInnerInterceptor();
    }
复制代码

示例代码

  @RequestMapping("/blockAttackInnerTest")
    public String blockAttackInnerTest()
    {
       UpdateWrapper<TUser> updateWrapper =new UpdateWrapper<>();
       //修改字段
       updateWrapper.set("email", "[email protected]");
       //全表更新
       userService.update(null,updateWrapper);
       return "成功";
    }
复制代码

测试结果

### Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
### The error may exist in com/skywares/mybatisplus/mapper/UserMapper.java (best guess)
复制代码

执行全表更新或者全表删除时后端直接报错,提示信息为禁止表更新操作。

乐观锁插件

简介

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。

示例

插件配置

  @Bean
   public MybatisPlusInterceptor paginationInterceptor() 
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        //添加乐观锁
        interceptor.addInnerInterceptor(optimisticLockerInterceptor());
        return interceptor;
    }

 /**
     * 乐观锁插件
     * @return
     */
    @Bean
    public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }
复制代码

实体中添加@Version

@Version
    private int version;
复制代码

示例代码

    @RequestMapping("/optimisticLockerTest")
    public String optimisticLockerTest()
    {
       //先查询version字段信息
       TUser user= userService.getById(262010);
       UpdateWrapper<TUser> updateWrapper =new UpdateWrapper<>();
       //修改字段
       updateWrapper.set("email", "[email protected]");
       updateWrapper.eq("oid", 262010);
       //在进行更新
       userService.update(user,updateWrapper);
       return "成功";
    }
复制代码

测试结果

==>  Preparing: UPDATE t_user SET name=?, age=?, address=?, version=?, email=? WHERE deleteFlag=0 AND (oid = ? AND version = ?)
==> Parameters: 张三1(String), 1(Integer), 广东省:1(String), 3(Integer), [email protected](String), 262010(Integer), 2(Integer)
<==    Updates: 1
复制代码

总结

Mybatis-plus的乐观锁只能用于updateById(id) 与 update(entity, wrapper) 方法,且更新之前需要先查询verison信息,否则乐观锁会失效。

动态表名插件

简介

用户数据量大的表,通过日期进行了水平分表,需要通过日期参数,动态的查询数据,Mybatis-plus可以通过此插件解析替换设定表名为处理器的返回表名。

插件配置

    @Bean
    public MybatisPlusInterceptor paginationInterceptor() 
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //动态表名
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
   }

    @Bean
    public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() 
    {
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor =new DynamicTableNameInnerInterceptor();
        //
        TableNameHandler tableNameHandler=new MyTableNameHandler();
        dynamicTableNameInnerInterceptor.setTableNameHandler(tableNameHandler);
        return dynamicTableNameInnerInterceptor;
    }
复制代码

动态表名规则处理类

public class MyTableNameHandler implements TableNameHandler
{
    private Logger logger = LoggerFactory.getLogger(MyTableNameHandler.class);
    @Override
    public String dynamicTableName(String sql, String tableName)
    {
        logger.info("dynamicTableName sql:{},tableName:{}",sql,tableName);
        
        //如果参数为空,则以不要动态动态查询表名
        Map<String, Object> paramMap = RequestDataHelper.getRequestData();
        if(paramMap==null || paramMap.isEmpty())
        {
            logger.info("dynamicTableName paramMap is null");
            return tableName;
        }
        
        // 获取参数方法
        paramMap.forEach((k, v) -> logger.info(k + "----" + v));

        int random = (int) paramMap.get("tableNo");
        String tableNo = "_1";
        if (random % 2 == 1) 
        {
            tableNo = "_2";
        }
        String queryTableName=tableName + tableNo;
        logger.info("---------> queryTableName:{}",queryTableName);
        return queryTableName;
    }
}

复制代码

参数存储

public class RequestDataHelper
{
    /**
     * 请求参数存取
     */
    private static final ThreadLocal<Map<String, Object>> REQUEST_DATA = new ThreadLocal<>();

    /**
     * 设置请求参数
     *
     * @param requestData 请求参数 MAP 对象
     */
    public static void setRequestData(Map<String, Object> requestData) {
        REQUEST_DATA.set(requestData);
    }


    /**
     * 获取请求参数
     *
     * @return 请求参数 MAP 对象
     */
    public static Map<String, Object> getRequestData() 
    {
        return REQUEST_DATA.get();
    }
}
复制代码

示例

 @RequestMapping("/dynamicTableNameInner")
    public String dynamicTableNameInner()
    {
        for (int i = 0; i < 3; i++) 
        {
            Map<String, Object> tableNameMap=new HashMap<String, Object>();
            int tableNo = new Random().nextInt(10);
            tableNameMap.put("tableNo", tableNo);
            RequestDataHelper.setRequestData(tableNameMap);
            TUser user = userService.getById(1);
            System.err.println(user.getName());
        }
       return "成功";
    }
    
复制代码

动态传递参数,通过规则获取表名,如果参数为空,则不需要动态获取表名。

测试结果

 [http-nio-9090-exec-1] INFO  [] c.s.m.config.MyTableNameHandler - ---------> queryTableName:t_user_1
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@33c47a4b] will not be managed by Spring
==>  Preparing: SELECT oid AS id,name,age,address,deleteFlag,version FROM t_user_1 WHERE oid=? AND deleteFlag=0
==> Parameters: 1(Integer)
<==    Columns: id, name, age, address, deleteFlag, version
<==        Row: 1, user_1, 28, null, 0, 0
<==      Total: 1
Closing non transactional SqlSession 
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f105f02] was not registered for synchronization because synchronization is not active
 [http-nio-9090-exec-1] INFO  [] c.s.m.config.MyTableNameHandler - dynamicTableName sql:SELECT oid AS id,name,age,address,deleteFlag,version FROM t_user WHERE oid=?  AND deleteFlag=0,tableName:t_user
[http-nio-9090-exec-1] INFO  [] c.s.m.config.MyTableNameHandler - tableNo----1
 [http-nio-9090-exec-1] INFO  [] c.s.m.config.MyTableNameHandler - ---------> queryTableName:t_user_2
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@33c47a4b] will not be managed by Spring
==>  Preparing: SELECT oid AS id,name,age,address,deleteFlag,version FROM t_user_2 WHERE oid=? AND deleteFlag=0
==> Parameters: 1(Integer)
<==    Columns: id, name, age, address, deleteFlag, version
<==        Row: 1, user_2, 28, null, 0, 0
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f105f02]
复制代码

通过执行结果可以看出,执行SQL语句动态的获取表名t_user_1、t_user_2查询对应的数据。

总结

本文讲解了Mybatis-plus的相关插件,需要注意插件的加载顺序,动态表名、分页,乐观锁、Sql 性能规范,虽然提供了很多插件,但是还是根据项目的实际情况来选择使用相关插件,对于多租户插后面会专门讲解,Sql性能规范建议大家少用。

猜你喜欢

转载自juejin.im/post/7090837136076718110