详解如何使用Mybatis的拦截器
MyBatis 拦截器是 MyBatis 提供的一个强大特性,它允许你在 MyBatis 执行其核心逻辑的关键节点插入自定义逻辑,从而改变 MyBatis 的默认行为。这类似于面向切面编程(AOP),允许开发者在不改变原有代码的情况下,增强或修改原有功能。
在 MyBatis 中,拦截器可以应用于以下四种类型的对象:
Executor:负责 MyBatis 中的 SQL 执行过程,拦截 Executor 可以在 SQL 执行前后添加自定义逻辑。
Executor 的核心职责
在 MyBatis 中,Executor
接口定义了数据库操作的核心方法,如 update
、query
、commit
、rollback
等。MyBatis 提供了几种 Executor
的实现,例如 SimpleExecutor
、ReuseExecutor
、BatchExecutor
等,它们各自有不同的特点和用途。
以 SimpleExecutor
为例,它是最基本的 Executor
实现,每次操作都会创建一个新的 Statement
对象。
源码解析
以下是 Executor
接口中 query
方法的一个简化版实现,展示了 MyBatis 如何执行一个查询操作:
public class SimpleExecutor extends BaseExecutor { public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 准备 SQL Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } }
这段代码展示了查询操作的基本流程:创建 StatementHandler
,准备和参数化 Statement
,执行查询,最后关闭 Statement
。
拦截器的实现和应用
拦截器允许开发者在 MyBatis 核心操作执行的关键节点插入自定义逻辑。拦截器需要实现 Interceptor
接口,并定义要拦截的目标和方法。
当 MyBatis 初始化时,它会检测配置中的拦截器,并在执行相应操作时调用这些拦截器。拦截器中的 intercept
方法将在目标方法执行时被调用。
public class ExampleExecutorInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在 SQL 执行前的逻辑 System.out.println("Before executing query"); // 执行原有逻辑 Object returnValue = invocation.proceed(); // 在 SQL 执行后的逻辑 System.out.println("After executing query"); return returnValue; } }
在这个例子中,intercept
方法实现了在查询执行前后打印消息的逻辑。invocation.proceed()
是关键,它触发了原本的操作(如查询)。通过这种方式,开发者可以在不修改原有代码的基础上增强或改变 MyBatis 的行为。
ParameterHandler:负责 MyBatis 中的参数处理,通过拦截 ParameterHandler,可以在 SQL 语句绑定参数前后添加自定义逻辑。
ParameterHandler 的工作原理
ParameterHandler
的主要职责是为 SQL 语句绑定正确的参数值。在执行 SQL 之前,MyBatis 会通过 ParameterHandler
接口的实现类来遍历方法传入的参数,并将它们设置到 JDBC 的 PreparedStatement
中。
MyBatis 默认提供了 DefaultParameterHandler
类作为 ParameterHandler
接口的实现,用于处理参数的设置工作。
源码解析
下面是一个简化的 ParameterHandler
使用示例,演示了如何在 MyBatis 中处理参数绑定:
public class DefaultParameterHandler implements ParameterHandler { private final MappedStatement mappedStatement; private final Object parameterObject; private final BoundSql boundSql; private final TypeHandlerRegistry typeHandlerRegistry; public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { this.mappedStatement = mappedStatement; this.parameterObject = parameterObject; this.boundSql = boundSql; this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); } @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } }
在上述代码中,setParameters
方法是核心,它负责将参数值绑定到 PreparedStatement
。首先,它会遍历所有的 ParameterMapping
,这些映射信息定义了如何从参数对象中获取具体的值。然后,它使用相应的 TypeHandler
来处理参数值的类型转换,并将转换后的值设置到 PreparedStatement
中。
拦截器的应用
通过拦截 ParameterHandler
的 setParameters
方法,可以在参数绑定前后插入自定义逻辑。例如,可以在参数绑定之前对参数进行日志记录,或者对参数值进行额外的处理。
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) }) public class ExampleParameterHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); // 在参数绑定前的自定义逻辑 System.out.println("Before parameter binding"); // 继续执行原逻辑 Object result = invocation.proceed(); // 在参数绑定后的自定义逻辑 System.out.println("After parameter binding"); return result; } }
在这个例子中,intercept
方法会在 setParameters
被调用时执行,允许开发者在参数被绑定到 SQL 语句之前和之后执行自定义代码。
ResultSetHandler:负责处理 JDBC 返回的 ResultSet 结果集,拦截 ResultSetHandler 可以在结果集处理前后添加自定义逻辑。
ResultSetHandler 的工作原理
当 MyBatis 执行查询操作后,会得到一个 ResultSet
,ResultSetHandler
的职责就是遍历这个 ResultSet
,并将其行转换为 Java 对象。MyBatis 中默认的 ResultSetHandler
实现是 DefaultResultSetHandler
。
源码解析
下面是 DefaultResultSetHandler
处理结果集的一个简化版示例,帮助理解其工作机制:
public class DefaultResultSetHandler implements ResultSetHandler { private final MappedStatement mappedStatement; private final RowBounds rowBounds; public DefaultResultSetHandler(MappedStatement mappedStatement, RowBounds rowBounds) { this.mappedStatement = mappedStatement; this.rowBounds = rowBounds; } @Override public <E> List<E> handleResultSets(Statement stmt) throws SQLException { ResultSet rs = stmt.getResultSet(); List<E> resultList = new ArrayList<>(); while (rs.next()) { E resultObject = getRowValue(rs); resultList.add(resultObject); } return resultList; } private <E> E getRowValue(ResultSet rs) throws SQLException { // 实际的结果对象映射逻辑 // 这里通常会涉及到 ResultMap 的处理 return ...; } }
在这个简化的例子中,handleResultSets
方法遍历 ResultSet
,对每一行调用 getRowValue
方法将其转换为一个 Java 对象,最终返回一个对象列表。
拦截器的应用
通过拦截 ResultSetHandler
的 handleResultSets
方法,开发者可以在结果集被处理成 Java 对象前后插入自定义逻辑。这可以用于额外的结果处理,比如对查询结果的后处理或审计日志记录等。
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class) }) public class ExampleResultSetHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在结果集处理前的逻辑 System.out.println("Before handling result sets"); // 执行原有逻辑 Object result = invocation.proceed(); // 在结果集处理后的逻辑 System.out.println("After handling result sets"); return result; } }
在这个示例中,intercept
方法在 handleResultSets
被调用时执行,允许在结果集转换为 Java 对象的前后执行自定义代码。
StatementHandler:负责对 JDBC Statement 的操作,通过拦截 StatementHandler,可以在 SQL 语句被执行前后添加自定义逻辑。
StatementHandler 的工作原理
StatementHandler
主要有三个实现类:SimpleStatementHandler
、PreparedStatementHandler
和 CallableStatementHandler
,分别对应于 JDBC 的 Statement
、PreparedStatement
和 CallableStatement
。这些类处理 SQL 的不同执行方式,其中 PreparedStatementHandler
是最常用的,因为它支持参数化的 SQL 语句,有助于提高性能和安全性。
源码解析
以下是 StatementHandler
接口的一个简化版实现(PreparedStatementHandler
),演示了 MyBatis 如何准备和执行 SQL 语句:
public class PreparedStatementHandler implements StatementHandler { private final MappedStatement mappedStatement; private final Object parameterObject; private final BoundSql boundSql; private final ParameterHandler parameterHandler; public PreparedStatementHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { this.mappedStatement = mappedStatement; this.parameterObject = parameterObject; this.boundSql = boundSql; this.parameterHandler = mappedStatement.getConfiguration().newParameterHandler(mappedStatement, parameterObject, boundSql); } @Override public PreparedStatement prepare(Connection connection) throws SQLException { String sql = boundSql.getSql(); PreparedStatement pstmt = connection.prepareStatement(sql); parameterHandler.setParameters(pstmt); return pstmt; } @Override public int update(PreparedStatement pstmt) throws SQLException { pstmt.execute(); return pstmt.getUpdateCount(); } @Override public <E> List<E> query(PreparedStatement pstmt, ResultHandler resultHandler) throws SQLException { pstmt.execute(); return resultSetHandler.handleResultSets(pstmt); } }
在这个简化的实现中,prepare
方法负责创建 PreparedStatement
并通过 ParameterHandler
设置参数。update
和 query
方法则用于执行 SQL 语句并处理执行结果。
拦截器的应用
通过拦截 StatementHandler
的方法,可以在 SQL 语句执行的关键节点加入自定义逻辑。例如,可以拦截 prepare
方法,在 SQL 语句被准备之前或之后添加逻辑:
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class ExampleStatementHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在 SQL 语句准备前的逻辑 System.out.println("Before preparing the SQL statement"); // 执行原有逻辑 Object result = invocation.proceed(); // 在 SQL 语句准备后的逻辑 System.out.println("After preparing the SQL statement"); return result; } }
在这个示例中,intercept
方法会在 prepare
方法执行时被调用,允许开发者在 SQL 语句被准备和执行前后插入自定义代码。
实际案例
让我们在一个 Spring Boot 项目中结合 MyBatis 的 Executor、ParameterHandler、ResultSetHandler、和 StatementHandler 构建一个实际案例。我们将创建一个简单的用户管理系统,其中包含用户的添加、查询功能,并通过拦截器在关键节点添加日志记录、性能监控等自定义逻辑。
步骤 1: 定义实体和映射文件
首先,定义一个 User
实体:
public class User { private Integer id; private String name; private String email; // Getters and setters... }
然后,创建一个 MyBatis 映射文件 UserMapper.xml
:
<mapper namespace="com.example.mybatisdemo.mapper.UserMapper"> <insert id="insertUser" parameterType="User"> INSERT INTO users (name, email) VALUES (#{name}, #{email}) </insert> <select id="getUserById" parameterType="int" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> </mapper>
步骤 2: 定义 Mapper 接口
@Mapper public interface UserMapper { void insertUser(User user); User getUserById(int id); }
步骤 3: 定义拦截器
- Executor 拦截器 - 记录执行时间:
@Intercepts({ @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}) }) public class ExecutorInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); Object result = invocation.proceed(); long endTime = System.currentTimeMillis(); System.out.println("Execution time: " + (endTime - startTime) + "ms"); return result; } }
- ParameterHandler 拦截器 - 记录参数信息:
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}) }) public class ParameterHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); System.out.println("Parameters: " + parameterHandler.getParameterObject()); return invocation.proceed(); } }
- ResultSetHandler 拦截器 - 在结果集后打印日志:
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) public class ResultSetHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object result = invocation.proceed(); System.out.println("Result: " + result); return result; } }
- StatementHandler 拦截器 - 修改 SQL 语句:
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class StatementHandlerInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); System.out.println("Original SQL: " + sql); // 此处可根据需要修改 sql // String modifiedSql = sql.replace(...); return invocation.proceed(); } }
步骤 4: 注册拦截器
在 Spring Boot 配置类中注册拦截器:
@Configuration public class MyBatisConfig { @Bean public ExecutorInterceptor executorInterceptor() { return new ExecutorInterceptor(); } @Bean public ParameterHandlerInterceptor parameterHandlerInterceptor() { return new ParameterHandlerInterceptor(); } @Bean public ResultSetHandlerInterceptor resultSetHandlerInterceptor() { return new ResultSetHandlerInterceptor(); } @Bean public StatementHandlerInterceptor statementHandlerInterceptor() { return new StatementHandlerInterceptor(); } }
步骤 5: 使用 Mapper 进行操作
在你的服务层或控制器中,使用 UserMapper
来执行
数据库操作:
@Service public class UserService { @Autowired private UserMapper userMapper; public void createUser(User user) { userMapper.insertUser(user); } public User getUser(int id) { return userMapper.getUserById(id); } }
当你执行 createUser
或 getUser
方法时,你的拦截器将会被触发,你可以看到控制台上打印的相关信息,以及 MyBatis 如何处理 SQL 操作以及如何通过拦截器介入这些过程。
以上就是详解如何使用Mybatis的拦截器的详细内容,更多关于Mybatis拦截器使用的资料请关注脚本之家其它相关文章!
相关文章
java并发编程专题(八)----(JUC)实例讲解CountDownLatch
这篇文章主要介绍了java CountDownLatch的相关资料,文中示例代码非常详细,帮助大家理解和学习,感兴趣的朋友可以了解下2020-07-07
最新评论