万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • MyBatis

  • Spring-MyBatis

  • MyBatis-Plus

    • MyBaits Plus简介
    • MyBaits Plus快速入门
    • MyBaits Plus CURD
    • MyBaits Plus配置
    • Wrapper条件构造器
    • ActiveRecord
    • MyBaits Plus插件
      • MyBaits Plus插件
      • MyBatis的插件机制
      • MyBatis Plus插件
    • MyBaits Plus拓展
    • MyBaits Plus代码生成器
  • MyBatis
  • MyBatis-Plus
2022-12-31
目录

MyBaits Plus插件

# MyBaits Plus插件

MyBaits提供了插件机制,而MP提供了许多插件 (opens new window),对MyBatis进行增强。以下是3.4.0 版本+的配置方法。

# MyBatis的插件机制

MyBatis 允许你在已映射语句执⾏过程中的某⼀点进⾏拦截调⽤。默认情况下,MyBatis 允许使⽤插件来拦截的⽅法调⽤包括:

  • 执行器-Executor (update、query、commit、rollback等方法);
  • SQL语法构建器-StatementHandler (prepare、parameterize、batch、updates query等方法);
  • 参数处理器-ParameterHandler (getParameterObject、setParameters方法);
  • 结果集处理器-ResultSetHandler (handleResultSets、handleOutputParameters等方法);

简单来说,插件机制可以拦截执⾏器的⽅法、拦截参数的处理、拦截结果集的处理、拦截Sql语法构建的处理。详细的内容可以看这篇文章-MyBatis插件。

# MyBatis Plus插件

MP提供了许多插件 (opens new window),MybatisPlusInterceptor是插件的核心,他有个List<InnerInterceptor> interceptors属性,需要注入实现了InnerInterceptor的对象,目前提供的插件都将基于此接口来实现功能:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

我在使用的时候注意使用顺序,建议如下:

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

# 配置方式

以分页插件举例,更详细介绍可见官网分页插件 (opens new window)。

SpringBoot:

@Configuration
@MapperScan("org.example.hello.mp")
public class MybatisPlusConfig {

    /**
     * 新的分页插件
     * 一缓和二缓遵循MyBatis的规则, 需要设置 MybatisConfiguration#useDeprecatedExecutor = false, 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      	// 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      	// ... 加其他插件
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

Spring:


<bean id="configuration" class="com.baomidou.mybatisplus.core.MybatisConfiguration">
    <!-- 需配置该值为false,避免1或2级缓存可能出现问题,该属性会在旧插件移除后一同移除 -->
    <property name="useDeprecatedExecutor" value="false"/>
</bean>

<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
    <property name="interceptors">
        <list>
            <ref bean="paginationInnerInterceptor"/>
        </list>
    </property>
</bean>

<bean id="paginationInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
    <!-- 对于单一数据库类型来说, 都建议配置该值,避免每次分页都去抓取数据库类型 -->
    <constructor-arg name="dbType" value="MYSQL"/>
</bean>

# 多租户插件

  • 多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
  • 启用多租户后所有执行的method的sql都会进行处理.
  • 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)

使用方式如下:

  1. Entity增加租户字段,user表增加tenant_id.
 private Long tenantId;
  1. [可选的]实现TenantLineHandler接口
public interface TenantLineHandler {

    /**
     * 获取租户 ID 值表达式,只支持单个 ID 值
     * <p>
     *
     * @return 租户 ID 值表达式
     */
    Expression getTenantId();

    /**
     * 获取租户字段名
     * <p>
     * 默认字段名叫: tenant_id
     *
     * @return 租户字段名
     */
    default String getTenantIdColumn() {
        // 如果该字段你不是固定的,请使用 SqlInjectionUtils.check 检查安全性
        return "tenant_id";
    }

    /**
     * 根据表名判断是否忽略拼接多租户条件
     * <p>
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    default boolean ignoreTable(String tableName) {
        return false;
    }
}

这里我为了方便测试,实现了该接口:

public class MyTenantLineHandler implements TenantLineHandler {
    @Override
    public Expression getTenantId() {
        // 固定租户ID=1
        return new LongValue(1);
    }
}
  1. 配置进interceptor
@Configuration
@MapperScan("org.example.hello.mp")
public class MybatisPlusConfig {
		// ...
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 租户插件
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
        // 分页插件 ...
        return interceptor;
    }
		
    // ...
}

测试用例:初始化user部分数据的tenant_id = 1,然后开测。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MPPluginsTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testTenant() {
        List<User> allUser = userMapper.selectList(null);
        // SELECT tenant_id, id, name, age, email FROM user WHERE tenant_id = 1
        System.out.println(allUser);
    }
}

# 动态表名插件

  • 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换。详见官方示例 (opens new window)

# 乐观锁插件

当要更新一条记录的时候,希望这条记录没有被别人更新。乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

使用方式如下:

  1. Entity类字段加上@Version注解,标明为乐观锁版本字段。
@Version
private Integer version;

注意事项:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1(必须先查出version,否则不会执行+1)
  • newVersion 会回写到 entity 中
  • 仅支持 updateById(id) 与 update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
  1. 配置乐观锁插件到拦截链中

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 租户插件 ...
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 分页插件 ...
        return interceptor;
    }
    

测试用例:user表添加version字段,初始化值为1。

@Test
public void testUpdateVersion() {
    // SELECT id, name, age, email, tenant_id, version FROM user WHERE id = 2 AND tenant_id = 1
    User user = userMapper.selectById(2L);
    user.setName("Jack2");
    // 查询出来的user的version必须非空,不然不会+1
    // UPDATE user SET name = 'Jack2', age = 20, email = 'test2@baomidou.com', tenant_id = 1, version = 2 WHERE tenant_id = 1 AND id = 2 AND version = 1
    int i = userMapper.updateById(user);
    // 1
    System.out.println(i);
}

# SQL性能规范插件

由于开发人员水平参差不齐,即使订了开发规范很多人也不遵守,SQL是影响系统性能最重要的因素,所以拦截掉垃圾SQL语句。

个人建议是可以自己开发的时候使用,实际团队合作中可不开。

拦截SQL类型的场景
1.必须使用到索引,包含left join连接字段,符合索引最左原则
必须使用索引好处,
1.1 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据
1.2 如果检查到使用了索引,SQL性能基本不会太差
 
2.SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做
https://gaoxianglong.github.io/shark
SQL尽量单表执行的好处
2.1 查询条件简单、易于开理解和维护;
2.2 扩展性极强;(可为分库分表做准备)
2.3 缓存利用率高;
2.在字段上使用函数
 
3.where条件为空
4.where条件使用了 !=
5.where条件使用了 not 关键字
6.where条件使用了 or 关键字
7.where条件使用了 使用子查询

配置性能规范插件:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // ... 乐观锁插件、分页插件
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    // 性能规范插件
    interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
    return interceptor;
}

测试用例:注意先删除租户插件配置。

@Test
public void testIllegalSQL() {
    User user = new User();
    user.setName("illegal test");
    userMapper.update(user, null);
}
// Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件

# 防止全表更新与删除

阻断全表更新、删除的操作。

  1. 直接配置BlockAttackInnerInterceptor拦截器
 @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 防止全表更新与删除插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }

测试用例:注意先删除租户插件、性能规范插件配置。

@Test
public void testDelAndUpdateAll() {
    User user = new User();
    user.setName("illegal test");
    userMapper.update(user, null);
    // Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
    userMapper.delete(null);
    // Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
}
上次更新: 5/30/2023, 11:23:39 PM
MyBaits Plus拓展

MyBaits Plus拓展→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式