詳解如何使用Mybatis的攔截器
MyBatis 攔截器是 MyBatis 提供的一個強大特性,它允許你在 MyBatis 執(zhí)行其核心邏輯的關鍵節(jié)點插入自定義邏輯,從而改變 MyBatis 的默認行為。這類似于面向切面編程(AOP),允許開發(fā)者在不改變原有代碼的情況下,增強或修改原有功能。
在 MyBatis 中,攔截器可以應用于以下四種類型的對象:
Executor:負責 MyBatis 中的 SQL 執(zhí)行過程,攔截 Executor 可以在 SQL 執(zhí)行前后添加自定義邏輯。
Executor 的核心職責
在 MyBatis 中,Executor
接口定義了數據庫操作的核心方法,如 update
、query
、commit
、rollback
等。MyBatis 提供了幾種 Executor
的實現,例如 SimpleExecutor
、ReuseExecutor
、BatchExecutor
等,它們各自有不同的特點和用途。
以 SimpleExecutor
為例,它是最基本的 Executor
實現,每次操作都會創(chuàng)建一個新的 Statement
對象。
源碼解析
以下是 Executor
接口中 query
方法的一個簡化版實現,展示了 MyBatis 如何執(zhí)行一個查詢操作:
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; } }
這段代碼展示了查詢操作的基本流程:創(chuàng)建 StatementHandler
,準備和參數化 Statement
,執(zhí)行查詢,最后關閉 Statement
。
攔截器的實現和應用
攔截器允許開發(fā)者在 MyBatis 核心操作執(zhí)行的關鍵節(jié)點插入自定義邏輯。攔截器需要實現 Interceptor
接口,并定義要攔截的目標和方法。
當 MyBatis 初始化時,它會檢測配置中的攔截器,并在執(zhí)行相應操作時調用這些攔截器。攔截器中的 intercept
方法將在目標方法執(zhí)行時被調用。
public class ExampleExecutorInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在 SQL 執(zhí)行前的邏輯 System.out.println("Before executing query"); // 執(zhí)行原有邏輯 Object returnValue = invocation.proceed(); // 在 SQL 執(zhí)行后的邏輯 System.out.println("After executing query"); return returnValue; } }
在這個例子中,intercept
方法實現了在查詢執(zhí)行前后打印消息的邏輯。invocation.proceed()
是關鍵,它觸發(fā)了原本的操作(如查詢)。通過這種方式,開發(fā)者可以在不修改原有代碼的基礎上增強或改變 MyBatis 的行為。
ParameterHandler:負責 MyBatis 中的參數處理,通過攔截 ParameterHandler,可以在 SQL 語句綁定參數前后添加自定義邏輯。
ParameterHandler 的工作原理
ParameterHandler
的主要職責是為 SQL 語句綁定正確的參數值。在執(zhí)行 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"); // 繼續(xù)執(zhí)行原邏輯 Object result = invocation.proceed(); // 在參數綁定后的自定義邏輯 System.out.println("After parameter binding"); return result; } }
在這個例子中,intercept
方法會在 setParameters
被調用時執(zhí)行,允許開發(fā)者在參數被綁定到 SQL 語句之前和之后執(zhí)行自定義代碼。
ResultSetHandler:負責處理 JDBC 返回的 ResultSet 結果集,攔截 ResultSetHandler 可以在結果集處理前后添加自定義邏輯。
ResultSetHandler 的工作原理
當 MyBatis 執(zhí)行查詢操作后,會得到一個 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
方法,開發(fā)者可以在結果集被處理成 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"); // 執(zhí)行原有邏輯 Object result = invocation.proceed(); // 在結果集處理后的邏輯 System.out.println("After handling result sets"); return result; } }
在這個示例中,intercept
方法在 handleResultSets
被調用時執(zhí)行,允許在結果集轉換為 Java 對象的前后執(zhí)行自定義代碼。
StatementHandler:負責對 JDBC Statement 的操作,通過攔截 StatementHandler,可以在 SQL 語句被執(zhí)行前后添加自定義邏輯。
StatementHandler 的工作原理
StatementHandler
主要有三個實現類:SimpleStatementHandler
、PreparedStatementHandler
和 CallableStatementHandler
,分別對應于 JDBC 的 Statement
、PreparedStatement
和 CallableStatement
。這些類處理 SQL 的不同執(zhí)行方式,其中 PreparedStatementHandler
是最常用的,因為它支持參數化的 SQL 語句,有助于提高性能和安全性。
源碼解析
以下是 StatementHandler
接口的一個簡化版實現(PreparedStatementHandler
),演示了 MyBatis 如何準備和執(zhí)行 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
方法負責創(chuàng)建 PreparedStatement
并通過 ParameterHandler
設置參數。update
和 query
方法則用于執(zhí)行 SQL 語句并處理執(zhí)行結果。
攔截器的應用
通過攔截 StatementHandler
的方法,可以在 SQL 語句執(zhí)行的關鍵節(jié)點加入自定義邏輯。例如,可以攔截 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"); // 執(zhí)行原有邏輯 Object result = invocation.proceed(); // 在 SQL 語句準備后的邏輯 System.out.println("After preparing the SQL statement"); return result; } }
在這個示例中,intercept
方法會在 prepare
方法執(zhí)行時被調用,允許開發(fā)者在 SQL 語句被準備和執(zhí)行前后插入自定義代碼。
實際案例
讓我們在一個 Spring Boot 項目中結合 MyBatis 的 Executor、ParameterHandler、ResultSetHandler、和 StatementHandler 構建一個實際案例。我們將創(chuàng)建一個簡單的用戶管理系統(tǒng),其中包含用戶的添加、查詢功能,并通過攔截器在關鍵節(jié)點添加日志記錄、性能監(jiān)控等自定義邏輯。
步驟 1: 定義實體和映射文件
首先,定義一個 User
實體:
public class User { private Integer id; private String name; private String email; // Getters and setters... }
然后,創(chuàng)建一個 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 攔截器 - 記錄執(zhí)行時間:
@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
來執(zhí)行
數據庫操作:
@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); } }
當你執(zhí)行 createUser
或 getUser
方法時,你的攔截器將會被觸發(fā),你可以看到控制臺上打印的相關信息,以及 MyBatis 如何處理 SQL 操作以及如何通過攔截器介入這些過程。
以上就是詳解如何使用Mybatis的攔截器的詳細內容,更多關于Mybatis攔截器使用的資料請關注腳本之家其它相關文章!