3.2 MyBatis 插件 (Interceptor)


文档摘要

3.2 MyBatis 插件 (Interceptor) 3.2 MyBatis 插件 (Interceptor) MyBatis 插件(Interceptor)是 MyBatis 框架提供的一种强大的扩展机制,允许开发者在 SQL 执行过程中的特定点进行拦截和修改,从而实现自定义的行为。 插件可以拦截 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 这四个核心接口的方法,从而可以对 SQL 语句、参数、结果集等进行修改和增强。 3.2.1 插件的作用 MyBatis 插件的主要作用包括: SQL 语句改写: 动态修改 SQL 语句,例如添加分页、数据权限过滤等。

3.2 MyBatis 插件 (Interceptor)

3.2 MyBatis 插件 (Interceptor)

MyBatis 插件(Interceptor)是 MyBatis 框架提供的一种强大的扩展机制,允许开发者在 SQL 执行过程中的特定点进行拦截和修改,从而实现自定义的行为。 插件可以拦截 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 这四个核心接口的方法,从而可以对 SQL 语句、参数、结果集等进行修改和增强。

3.2.1 插件的作用

MyBatis 插件的主要作用包括:

  • SQL 语句改写: 动态修改 SQL 语句,例如添加分页、数据权限过滤等。

  • 参数修改: 修改 SQL 语句的参数,例如加密、解密、类型转换等。

  • 结果集处理: 对查询结果进行处理,例如数据脱敏、缓存等。

  • 性能监控: 记录 SQL 执行时间、慢 SQL 告警等。

  • 自定义逻辑: 在 SQL 执行前后执行自定义的业务逻辑。

3.2.2 插件的原理

MyBatis 插件的原理是基于 Java 的动态代理机制。 MyBatis 在创建 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 对象时,会判断是否有插件配置,如果有,则会使用 Plugin.wrap() 方法将这些对象包装成代理对象。 当调用这些代理对象的方法时,会先执行插件的 intercept() 方法,然后再执行原始对象的方法。

以下是插件工作流程的简化图:

3.2.3 插件的开发

开发 MyBatis 插件需要实现 org.apache.ibatis.plugin.Interceptor 接口,并使用 @Intercepts@Signature 注解来指定要拦截的方法。

  • @Intercepts 注解:用于声明要拦截的目标接口和方法。

  • @Signature 注解:用于指定要拦截的接口、方法名和参数类型。

以下是一个简单的 MyBatis 插件示例,用于在 SQL 执行前打印 SQL 语句:

import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.util.Properties; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class SqlStatementInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类) while (metaObject.hasGetter("h")) { Object object = metaObject.getValue("h"); metaObject = SystemMetaObject.forObject(object); } // 分离最后一个代理对象的目标类 while (metaObject.hasGetter("target")) { Object object = metaObject.getValue("target"); metaObject = SystemMetaObject.forObject(object); } String sql = (String) metaObject.getValue("delegate.boundSql.sql"); System.out.println("执行 SQL: " + sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以通过配置文件配置插件的属性 String prop1 = properties.getProperty("prop1"); String prop2 = properties.getProperty("prop2"); System.out.println("prop1 = " + prop1); System.out.println("prop2 = " + prop2); } }

代码解释:

  1. @Intercepts@Signature 注解: @Intercepts 注解声明该插件要拦截的目标是 StatementHandler 接口的 prepare 方法,该方法有两个参数,类型分别为 ConnectionInteger

  2. intercept() 方法: 该方法是插件的核心方法,用于拦截目标方法并执行自定义逻辑。

    • invocation.getTarget():获取被拦截的目标对象,这里是 StatementHandler 对象。

    • MetaObject.forObject(statementHandler): 使用 MyBatis 的反射工具类 MetaObject 将目标对象包装起来,方便获取和设置对象的属性值。

    • 循环剥离代理对象,获取最原始的StatementHandler对象,因为目标类可能被多个拦截器拦截,从而形成多次代理。

    • metaObject.getValue("delegate.boundSql.sql"):通过 MetaObject 获取 SQL 语句。delegate 字段指向 StatementHandler 中的 delegate 属性,boundSql 属性是 delegate 对象中的一个属性,sql 属性是 BoundSql 对象中的 SQL 语句。

    • System.out.println("执行 SQL: " + sql):打印 SQL 语句。

    • invocation.proceed():调用原始方法,让 SQL 语句继续执行。

  3. plugin() 方法: 该方法用于将插件应用到目标对象上。 Plugin.wrap(target, this) 方法会根据目标对象是否已经实现了接口来决定是使用 JDK 动态代理还是 CGLIB 代理。

  4. setProperties() 方法: 该方法用于设置插件的属性,可以通过 MyBatis 的配置文件来配置插件的属性。

3.2.4 插件的配置

要使用 MyBatis 插件,需要在 MyBatis 的配置文件中进行配置。 在 <configuration> 标签下添加 <plugins> 标签,并在 <plugins> 标签中添加 <plugin> 标签,指定插件的类名。

<configuration> <plugins> <plugin interceptor="com.example.SqlStatementInterceptor"> <property name="prop1" value="value1"/> <property name="prop2" value="value2"/> </plugin> </plugins> </configuration>

配置解释:

  • interceptor 属性:指定插件的类名。

  • <property> 标签:用于配置插件的属性,可以在插件的 setProperties() 方法中获取这些属性。

3.2.5 拦截 Executor 示例:分页插件

一个常见的 MyBatis 插件应用场景是实现分页功能。 以下是一个简单的分页插件示例,用于在 SQL 语句中添加 LIMIT 子句:

import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.util.Properties; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class PaginationInterceptor implements Interceptor { private String dialect = "mysql"; // 数据库方言,默认为 MySQL @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); // 分离代理对象链 while (metaObject.hasGetter("h")) { Object object = metaObject.getValue("h"); metaObject = SystemMetaObject.forObject(object); } // 分离最后一个代理对象的目标类 while (metaObject.hasGetter("target")) { Object object = metaObject.getValue("target"); metaObject = SystemMetaObject.forObject(object); } String sql = (String) metaObject.getValue("delegate.boundSql.sql"); // 获取分页参数 Integer page = (Integer) metaObject.getValue("delegate.boundSql.parameterObject.page"); Integer pageSize = (Integer) metaObject.getValue("delegate.boundSql.parameterObject.pageSize"); if (page != null && pageSize != null) { // 构建分页 SQL String paginationSql = sql + " LIMIT " + (page - 1) * pageSize + ", " + pageSize; // 修改 SQL metaObject.setValue("delegate.boundSql.sql", paginationSql); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialect = properties.getProperty("dialect"); if (dialect != null && !dialect.isEmpty()) { this.dialect = dialect; } } }

代码解释:

  1. dialect 属性: 用于指定数据库方言,默认为 MySQL。

  2. intercept() 方法:

    • 获取 SQL 语句。

    • 从参数对象中获取分页参数 pagepageSize。 这里假设 page 和 pageSize 作为参数传递, 并且参数对象中存在 page 和 pageSize 属性

    • 如果 pagepageSize 不为空,则构建分页 SQL,并修改 SQL 语句。

  3. setProperties() 方法: 用于设置数据库方言,可以在 MyBatis 的配置文件中配置。

配置示例:

<configuration> <plugins> <plugin interceptor="com.example.PaginationInterceptor"> <property name="dialect" value="mysql"/> </plugin> </plugins> </configuration>

使用示例:

// 假设 UserMapper.xml 中有一个 selectUser 方法,用于查询用户列表 List<User> selectUser(@Param("page") int page, @Param("pageSize") int pageSize); // 调用示例 List<User> users = userMapper.selectUser(1, 10); // 查询第一页,每页 10 条数据

3.2.6 注意事项

  • 插件的顺序: 如果配置了多个插件,它们的执行顺序是按照配置的顺序执行的。

  • 插件的性能: 插件会增加 SQL 执行的开销,因此应该谨慎使用插件,避免过度使用。

  • 线程安全: 插件应该是线程安全的,避免在插件中访问共享的可变状态。

  • 避免过度拦截: 尽量精确地指定需要拦截的方法,避免不必要的拦截,提高性能。

  • 考虑数据库兼容性: 在编写 SQL 修改相关的插件时,要考虑不同数据库的 SQL 语法差异,可以使用数据库方言来解决兼容性问题。

3.2.7 总结

MyBatis 插件是一种强大的扩展机制,可以用于实现各种自定义的行为。 通过使用插件,可以灵活地修改 SQL 语句、参数和结果集,从而满足各种业务需求。 但是,插件也会增加 SQL 执行的开销,因此应该谨慎使用插件,避免过度使用。 同时,要确保插件的线程安全,并考虑数据库的兼容性。 通过合理地使用 MyBatis 插件,可以极大地提高 MyBatis 的灵活性和可扩展性。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U