1. Mybatis-Plus 插件系统概述
Mybatis-Plus 提供了一个简单而强大的插件机制,允许开发者在 MyBatis 执行 SQL 的过程中插入自定义逻辑。通过插件机制,用户可以实现对 SQL 执行过程的拦截和修改。Mybatis-Plus 插件基于 MyBatis 的拦截器模式进行实现,能够在 MyBatis 执行 SQL 语句前、后进行自定义操作。
插件机制的核心是 Interceptor
接口,开发者可以通过实现该接口,自定义拦截 SQL 执行的行为。Mybatis-Plus 内置了多个常用插件,例如分页插件、性能分析插件等。插件的配置和使用非常简单,能够提升开发效率,减少重复性代码。
2. 常见插件:分页插件,性能分析插件
分页插件
分页插件是 Mybatis-Plus 中最常用的插件之一,它通过拦截 SQL 查询语句,自动为查询添加分页条件。只需在配置中启用分页插件,并在查询时传入分页参数,就能够快速实现分页查询。
配置分页插件的代码如下:
@Configuration public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
在查询时,使用
Page
对象作为分页参数:Page<User> page = new Page<>(1, 10); // 第1页,每页10条 IPage<User> userPage = userMapper.selectPage(page, null);
性能分析插件
性能分析插件用于输出 SQL 执行的性能日志。通过该插件,可以记录每个 SQL 的执行时间,并警告执行时间超过设定阈值的 SQL 语句。它的配置方式如下:
@Configuration public class MybatisPlusConfig { @Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor = new PerformanceInterceptor(); interceptor.setMaxTime(1000); // 设置最大执行时间,超过则警告 interceptor.setFormat(true); // SQL 格式化输出 return interceptor; } }
3. 如何实现自定义插件
要实现自定义插件,首先需要实现 MyBatis 的 Interceptor
接口,该接口包含一个 intercept
方法,用于定义拦截逻辑。在 intercept
方法中,可以通过 Invocation
对象来获取当前的 SQL 语句,并进行自定义操作。
以下是一个自定义插件的实现示例:
public class MyCustomPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取执行的 SQL 语句
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
// 在此处修改 SQL,例如增加特定的查询条件
String sql = boundSql.getSql();
String modifiedSql = sql + " WHERE is_active = 1"; // 假设为筛选活跃用户
// 设置修改后的 SQL
Field field = BoundSql.class.getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, modifiedSql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 通过 MyBatis 的插件机制包装目标对象
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 可以根据需要在此配置插件的属性
}
}
然后,需要将自定义插件配置到 MyBatis 中:
@Configuration
public class MybatisPlusConfig {
@Bean
public MyCustomPlugin myCustomPlugin() {
return new MyCustomPlugin();
}
}
4. 插件的开发与使用场景
插件开发
插件的开发通常基于以下几个步骤:
- 实现
Interceptor
接口,重写intercept
、plugin
和setProperties
方法。 - 在
intercept
方法中编写 SQL 拦截逻辑,修改或记录 SQL 执行信息。 - 使用
Plugin.wrap
方法将插件与目标对象进行绑定,以便 MyBatis 能够正确调用插件。 - 配置插件,将其注册到 MyBatis 的插件列表中。
- 实现
插件的使用场景
- 动态 SQL 扩展:可以利用插件动态地添加查询条件,例如自动增加租户字段、权限过滤等。
- SQL 性能监控:通过性能分析插件可以检测 SQL 执行的性能,帮助开发者优化查询。
- 审计日志:通过插件记录每次 SQL 执行的信息,可以实现数据库的审计功能。
- 数据加密解密:在插件中进行数据加密解密处理,保证数据的安全性。
5. SaaS 系统的应用场景:租户数据隔离插件
在 SaaS 系统中,通常会有多个租户(客户),而每个租户的数据必须进行隔离,避免数据泄漏或混合。这时,Mybatis-Plus 插件机制可以发挥重要作用,通过自定义插件来自动为每个 SQL 查询添加租户隔离条件。
假设系统中的每个租户都有一个唯一的 tenant_id
,我们可以通过自定义插件,在所有的查询中自动添加租户 ID 条件,从而实现数据的隔离。以下是一个租户数据隔离插件的示例:
public class TenantDataIsolationPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取执行的 SQL 语句
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
// 获取当前租户的 tenant_id,假设通过 ThreadLocal 存储
String tenantId = TenantContext.getTenantId();
// 为每个 SQL 增加租户字段条件
String sql = boundSql.getSql();
// 如果查询的表没有 tenant_id 字段,不需要添加租户过滤条件
if (!isTenantIdRequired(ms)) {
return invocation.proceed(); // 跳过插件处理,直接执行查询
}
String modifiedSql = sql + " AND tenant_id = '" + tenantId + "'";
// 设置修改后的 SQL
Field field = BoundSql.class.getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, modifiedSql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 通过 MyBatis 的插件机制包装目标对象
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 可以根据需要在此配置插件的属性
}
/**
* 判断当前查询的表是否需要租户隔离。
* 例如,如果表中没有 tenant_id 字段,则不需要隔离。
*/
private boolean isTenantIdRequired(MappedStatement ms) {
String tableName = ms.getSqlCommandType().name();
// 假设查询表的名字中包含 "no_tenant" 时,表示不需要隔离
return !tableName.contains("no_tenant");
}
}
在这个插件中,我们假设每次操作的 tenant_id
是通过 TenantContext.getTenantId()
方法获取的(可以利用 ThreadLocal
存储每个线程的租户 ID)。插件会在每个 SQL 查询中自动加上租户条件 AND tenant_id = '当前租户ID'
,但如果查询的表没有 tenant_id
字段,或者表名包含 no_tenant
(例如某些不需要数据隔离的表),插件将跳过租户隔离的处理,直接执行查询。
配置插件:
@Configuration
public class MybatisPlusConfig {
@Bean
public TenantDataIsolationPlugin tenantDataIsolationPlugin() {
return new TenantDataIsolationPlugin();
}
}
应用场景:
- 在 SaaS 系统中,每个租户的数据会存储在相同的数据库表中。通过该插件,开发者无需手动在每个查询中添加租户 ID 条件,系统自动为每个 SQL 查询注入当前租户的 ID,从而实现数据隔离。
- 对于一些不需要租户隔离的表(如系统表、公共配置表等),通过
isTenantIdRequired
方法判断,避免不必要的条件添加。 - 该插件不仅适用于查询,还可以用于增、删、改操作,确保所有操作都符合租户隔离的要求。
总结
Mybatis-Plus 插件机制通过提供灵活的拦截功能,可以有效地扩展 MyBatis 的功能。通过自定义插件,开发者能够实现如动态 SQL 扩展、SQL 性能监控、数据加密解密等功能。在 SaaS 系统中,使用插件实现租户数据隔离,是一种非常实用的解决方案,可以减少开发工作量,提高系统的可维护性和安全性。此外,针对不需要隔离的表,我们可以在插件中进行智能判断,避免无意义的条件添加,提升系统效率。