插件原理

MybatisPlus的插件是基于mybatis的intercepter扩展来的,其本质是在mybatis的一个插件维护一组自己的插件组,并挨个执行

其主类为MybatisPlusInterceptor

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
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
@Signature(type = StatementHandler.class, method = "getBoundSql", args = {}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class MybatisPlusInterceptor implements Interceptor {
@Setter
private List<InnerInterceptor> interceptors = new ArrayList<>();

@Override
public Object intercept(Invocation invocation) throws Throwable {
//覆写Mybatis的逻辑,然后抽象出自己的接口InnerInterceptor,具体的插件就只需要实现InnerInterceptor即可
}
@Override
public Object plugin(Object target) {

}
public void addInnerInterceptor(InnerInterceptor innerInterceptor) {
this.interceptors.add(innerInterceptor);
}
}

从注解上可以看的出来,它将mybatis的所有预留方法都拦截了,然后通过一些列逻辑封装调用自己的接口InnerInterceptor的相关方法实现Mybatisplus自己的扩展

再来看InnerInterceptor接口

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface InnerInterceptor {

/**
* 判断是否执行 {@link Executor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)}
* 如果不执行query操作,则返回 {@link Collections#emptyList()}
* @param executor Executor(可能是代理对象)
* @return 新的 boundSql
*/
default boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
return true;
}

/**
* {@link Executor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)} 操作前置处理
* 改改sql啥的
* @param executor Executor(可能是代理对象)
*/
default void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// do nothing
}

/**
* 判断是否执行 {@link Executor#update(MappedStatement, Object)}
* <p>
* 如果不执行update操作,则影响行数的值为 -1
*
* @param executor Executor(可能是代理对象)
*/
default boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
return true;
}

/**
* {@link Executor#update(MappedStatement, Object)} 操作前置处理
* 改改sql啥的
* @param executor Executor(可能是代理对象)
*/
default void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
// do nothing
}

/**
* {@link StatementHandler#prepare(Connection, Integer)} 操作前置处理
* 改改sql啥的
* @param sh StatementHandler(可能是代理对象)
*/
default void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
// do nothing
}

/**
* {@link StatementHandler#getBoundSql()} 操作前置处理
* 只有 {@link BatchExecutor} 和 {@link ReuseExecutor} 才会调用到这个方法
* @param sh StatementHandler(可能是代理对象)
*/
default void beforeGetBoundSql(StatementHandler sh) {
// do nothing
}
default void setProperties(Properties properties) {
// do nothing
}
}

根据描述自定义自己的方法即可,中文注释就是好,一眼就看懂了

所以我们自定义插件主要关注InnerInterceptor相关的类和方法就好

插件使用

从以上知道,Mybatisplus主要是MybatisPlusInterceptor这个类扩展,那么我们扩展的时候首先要把它作为bean注入到容器里,然后再它身上添加InnerInterceptor

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class PluginConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
// 这里添加上自己的InnerInterceptor插件
//plusInterceptor.addInnerInterceptor(cusInnerInterceptor);
return plusInterceptor;
}
}

Mybatisplus自带提供了相应的插件,可以按需引入

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

分页插件

Mybatisplus的分页插件是逻辑分页,即把数据查询回来之后再分页,这样需要注意性能问题,因为实际上数据库查询阶段会查询所有的数据,在大量数据面前并不友好

1
2
//在MybatisPlusInterceptor创建并添加的时候加上分页插件即可
plusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());

租户插件

租户插件主要是用来改写SQL添加租户字段

主要实现TenantLineHandler添加租户相关定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class CustomTenantHandler implements TenantLineHandler {

@Override
public Expression getTenantId() {
// 假设有一个租户上下文,能够从中获取当前用户的租户
Long tenantId = TenantContextHolder.getCurrentTenantId();
// 返回租户ID的表达式,LongValue 是 JSQLParser 中表示 bigint 类型的 class
return new LongValue(tenantId);;
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 根据需要返回是否忽略该表,如租户本身的管理表肯定是不需要的
return false;
}
}

然后注入到插件主体中

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private CustomTenantHandler customTenantHandler;
//在主体插件加入拦截器中
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(customTenantHandler);
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}

多数据源插件