詳解如何使用Mybatis的攔截器
MyBatis 攔截器是 MyBatis 提供的一個(gè)強(qiáng)大特性,它允許你在 MyBatis 執(zhí)行其核心邏輯的關(guān)鍵節(jié)點(diǎn)插入自定義邏輯,從而改變 MyBatis 的默認(rèn)行為。這類似于面向切面編程(AOP),允許開發(fā)者在不改變原有代碼的情況下,增強(qiáng)或修改原有功能。
在 MyBatis 中,攔截器可以應(yīng)用于以下四種類型的對象:
Executor:負(fù)責(zé) MyBatis 中的 SQL 執(zhí)行過程,攔截 Executor 可以在 SQL 執(zhí)行前后添加自定義邏輯。
Executor 的核心職責(zé)
在 MyBatis 中,Executor 接口定義了數(shù)據(jù)庫操作的核心方法,如 update、query、commit、rollback 等。MyBatis 提供了幾種 Executor 的實(shí)現(xiàn),例如 SimpleExecutor、ReuseExecutor、BatchExecutor 等,它們各自有不同的特點(diǎn)和用途。
以 SimpleExecutor 為例,它是最基本的 Executor 實(shí)現(xiàn),每次操作都會創(chuàng)建一個(gè)新的 Statement 對象。
源碼解析
以下是 Executor 接口中 query 方法的一個(gè)簡化版實(shí)現(xiàn),展示了 MyBatis 如何執(zhí)行一個(gè)查詢操作:
public class SimpleExecutor extends BaseExecutor {
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 準(zhǔn)備 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,準(zhǔn)備和參數(shù)化 Statement,執(zhí)行查詢,最后關(guān)閉 Statement。
攔截器的實(shí)現(xiàn)和應(yīng)用
攔截器允許開發(fā)者在 MyBatis 核心操作執(zhí)行的關(guān)鍵節(jié)點(diǎn)插入自定義邏輯。攔截器需要實(shí)現(xiàn) Interceptor 接口,并定義要攔截的目標(biāo)和方法。
當(dāng) MyBatis 初始化時(shí),它會檢測配置中的攔截器,并在執(zhí)行相應(yīng)操作時(shí)調(diào)用這些攔截器。攔截器中的 intercept 方法將在目標(biāo)方法執(zhí)行時(shí)被調(diào)用。
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;
}
}
在這個(gè)例子中,intercept 方法實(shí)現(xiàn)了在查詢執(zhí)行前后打印消息的邏輯。invocation.proceed() 是關(guān)鍵,它觸發(fā)了原本的操作(如查詢)。通過這種方式,開發(fā)者可以在不修改原有代碼的基礎(chǔ)上增強(qiáng)或改變 MyBatis 的行為。
ParameterHandler:負(fù)責(zé) MyBatis 中的參數(shù)處理,通過攔截 ParameterHandler,可以在 SQL 語句綁定參數(shù)前后添加自定義邏輯。
ParameterHandler 的工作原理
ParameterHandler 的主要職責(zé)是為 SQL 語句綁定正確的參數(shù)值。在執(zhí)行 SQL 之前,MyBatis 會通過 ParameterHandler 接口的實(shí)現(xiàn)類來遍歷方法傳入的參數(shù),并將它們設(shè)置到 JDBC 的 PreparedStatement 中。
MyBatis 默認(rèn)提供了 DefaultParameterHandler 類作為 ParameterHandler 接口的實(shí)現(xiàn),用于處理參數(shù)的設(shè)置工作。
源碼解析
下面是一個(gè)簡化的 ParameterHandler 使用示例,演示了如何在 MyBatis 中處理參數(shù)綁定:
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 方法是核心,它負(fù)責(zé)將參數(shù)值綁定到 PreparedStatement。首先,它會遍歷所有的 ParameterMapping,這些映射信息定義了如何從參數(shù)對象中獲取具體的值。然后,它使用相應(yīng)的 TypeHandler 來處理參數(shù)值的類型轉(zhuǎn)換,并將轉(zhuǎn)換后的值設(shè)置到 PreparedStatement 中。
攔截器的應(yīng)用
通過攔截 ParameterHandler 的 setParameters 方法,可以在參數(shù)綁定前后插入自定義邏輯。例如,可以在參數(shù)綁定之前對參數(shù)進(jìn)行日志記錄,或者對參數(shù)值進(jìn)行額外的處理。
@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();
// 在參數(shù)綁定前的自定義邏輯
System.out.println("Before parameter binding");
// 繼續(xù)執(zhí)行原邏輯
Object result = invocation.proceed();
// 在參數(shù)綁定后的自定義邏輯
System.out.println("After parameter binding");
return result;
}
}
在這個(gè)例子中,intercept 方法會在 setParameters 被調(diào)用時(shí)執(zhí)行,允許開發(fā)者在參數(shù)被綁定到 SQL 語句之前和之后執(zhí)行自定義代碼。
ResultSetHandler:負(fù)責(zé)處理 JDBC 返回的 ResultSet 結(jié)果集,攔截 ResultSetHandler 可以在結(jié)果集處理前后添加自定義邏輯。
ResultSetHandler 的工作原理
當(dāng) MyBatis 執(zhí)行查詢操作后,會得到一個(gè) ResultSet,ResultSetHandler 的職責(zé)就是遍歷這個(gè) ResultSet,并將其行轉(zhuǎn)換為 Java 對象。MyBatis 中默認(rèn)的 ResultSetHandler 實(shí)現(xiàn)是 DefaultResultSetHandler。
源碼解析
下面是 DefaultResultSetHandler 處理結(jié)果集的一個(gè)簡化版示例,幫助理解其工作機(jī)制:
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 {
// 實(shí)際的結(jié)果對象映射邏輯
// 這里通常會涉及到 ResultMap 的處理
return ...;
}
}
在這個(gè)簡化的例子中,handleResultSets 方法遍歷 ResultSet,對每一行調(diào)用 getRowValue 方法將其轉(zhuǎn)換為一個(gè) Java 對象,最終返回一個(gè)對象列表。
攔截器的應(yīng)用
通過攔截 ResultSetHandler 的 handleResultSets 方法,開發(fā)者可以在結(jié)果集被處理成 Java 對象前后插入自定義邏輯。這可以用于額外的結(jié)果處理,比如對查詢結(jié)果的后處理或?qū)徲?jì)日志記錄等。
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)
})
public class ExampleResultSetHandlerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在結(jié)果集處理前的邏輯
System.out.println("Before handling result sets");
// 執(zhí)行原有邏輯
Object result = invocation.proceed();
// 在結(jié)果集處理后的邏輯
System.out.println("After handling result sets");
return result;
}
}
在這個(gè)示例中,intercept 方法在 handleResultSets 被調(diào)用時(shí)執(zhí)行,允許在結(jié)果集轉(zhuǎn)換為 Java 對象的前后執(zhí)行自定義代碼。
StatementHandler:負(fù)責(zé)對 JDBC Statement 的操作,通過攔截 StatementHandler,可以在 SQL 語句被執(zhí)行前后添加自定義邏輯。
StatementHandler 的工作原理
StatementHandler 主要有三個(gè)實(shí)現(xiàn)類:SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler,分別對應(yīng)于 JDBC 的 Statement、PreparedStatement 和 CallableStatement。這些類處理 SQL 的不同執(zhí)行方式,其中 PreparedStatementHandler 是最常用的,因?yàn)樗С謪?shù)化的 SQL 語句,有助于提高性能和安全性。
源碼解析
以下是 StatementHandler 接口的一個(gè)簡化版實(shí)現(xiàn)(PreparedStatementHandler),演示了 MyBatis 如何準(zhǔn)備和執(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);
}
}
在這個(gè)簡化的實(shí)現(xiàn)中,prepare 方法負(fù)責(zé)創(chuàng)建 PreparedStatement 并通過 ParameterHandler 設(shè)置參數(shù)。update 和 query 方法則用于執(zhí)行 SQL 語句并處理執(zhí)行結(jié)果。
攔截器的應(yīng)用
通過攔截 StatementHandler 的方法,可以在 SQL 語句執(zhí)行的關(guān)鍵節(jié)點(diǎn)加入自定義邏輯。例如,可以攔截 prepare 方法,在 SQL 語句被準(zhǔn)備之前或之后添加邏輯:
@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 語句準(zhǔn)備前的邏輯
System.out.println("Before preparing the SQL statement");
// 執(zhí)行原有邏輯
Object result = invocation.proceed();
// 在 SQL 語句準(zhǔn)備后的邏輯
System.out.println("After preparing the SQL statement");
return result;
}
}
在這個(gè)示例中,intercept 方法會在 prepare 方法執(zhí)行時(shí)被調(diào)用,允許開發(fā)者在 SQL 語句被準(zhǔn)備和執(zhí)行前后插入自定義代碼。
實(shí)際案例
讓我們在一個(gè) Spring Boot 項(xiàng)目中結(jié)合 MyBatis 的 Executor、ParameterHandler、ResultSetHandler、和 StatementHandler 構(gòu)建一個(gè)實(shí)際案例。我們將創(chuàng)建一個(gè)簡單的用戶管理系統(tǒng),其中包含用戶的添加、查詢功能,并通過攔截器在關(guān)鍵節(jié)點(diǎn)添加日志記錄、性能監(jiān)控等自定義邏輯。
步驟 1: 定義實(shí)體和映射文件
首先,定義一個(gè) User 實(shí)體:
public class User {
private Integer id;
private String name;
private String email;
// Getters and setters...
}
然后,創(chuàng)建一個(gè) 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í)行時(shí)間:
@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 攔截器 - 記錄參數(shù)信息:
@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 攔截器 - 在結(jié)果集后打印日志:
@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);
// 此處可根據(jù)需要修改 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 進(jìn)行操作
在你的服務(wù)層或控制器中,使用 UserMapper 來執(zhí)行
數(shù)據(jù)庫操作:
@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);
}
}
當(dāng)你執(zhí)行 createUser 或 getUser 方法時(shí),你的攔截器將會被觸發(fā),你可以看到控制臺上打印的相關(guān)信息,以及 MyBatis 如何處理 SQL 操作以及如何通過攔截器介入這些過程。
以上就是詳解如何使用Mybatis的攔截器的詳細(xì)內(nèi)容,更多關(guān)于Mybatis攔截器使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
快速學(xué)會Dubbo的配置環(huán)境及相關(guān)配置
本文主要講解Dubbo的環(huán)境與配置,文中運(yùn)用大量代碼和圖片講解的非常詳細(xì),需要學(xué)習(xí)或用到相關(guān)知識的小伙伴可以參考這篇文章2021-09-09
JavaWeb實(shí)現(xiàn)文件上傳與下載的方法
這篇文章主要介紹了JavaWeb實(shí)現(xiàn)文件上傳與下載的方法的相關(guān)資料,需要的朋友可以參考下2016-01-01

