MyBatis攔截器的原理與使用
一、攔截對象和接口實現(xiàn)示例
MyBatis攔截器的作用是在于Dao到DB中間進行額外的處理。大部分情況下通過mybatis的xml配置sql都可以達到想要的DB操作效果,然而存在一些類似或者相同的查詢條件或者查詢要求,這些可以通過攔截器的實現(xiàn)可以提升開發(fā)效率,比如:分頁、插入和更新時間/人、數(shù)據(jù)權(quán)限、SQL監(jiān)控日志等。
- Mybatis支持四種對象攔截Executor、StatementHandler、PameterHandler和ResultSetHandler
- Executor:攔截執(zhí)行器的方法。
- StatementHandler:攔截Sql語法構(gòu)建的處理。
- ParameterHandler:攔截參數(shù)的處理。
- ResultHandler:攔截結(jié)果集的處理。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
public interface StatementHandler {
Statement prepare(Connection var1, Integer var2) throws SQLException;
void parameterize(Statement var1) throws SQLException;
void batch(Statement var1) throws SQLException;
int update(Statement var1) throws SQLException;
<E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
<E> Cursor<E> queryCursor(Statement var1) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}
public interface ResultHandler<T> {
void handleResult(ResultContext<? extends T> var1);
}
攔截的執(zhí)行順序是Executor->StatementHandler->ParameterHandler->ResultHandler
- MyBatis提供的攔截器接口:
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {}
}
Object intercept方法用于攔截器的實現(xiàn);
Object plugin方法用于判斷執(zhí)行攔截器的類型;
void setProperties方法用于獲取配置項的屬性。
- 攔截對象和攔截器接口的結(jié)合,自定義的攔截器類需要實現(xiàn)攔截器接口,并通過注解@Intercepts和參數(shù)@Signature來聲明要攔截的對象。
@Signature參數(shù)type是攔截對象,method是攔截的方法,即上面的四個類對應(yīng)的方法,args是攔截方法對應(yīng)的參數(shù)(方法存在重載因此需要指明參數(shù)個數(shù)和類型)
@Intercepts可以有多個@Signature,即一個攔截器實現(xiàn)類可以同時攔截多個對象及方法,示例如下:
- Executor->intercept
- StatementHandler->intercept
- ParameterHandler->intercept
- ResultHandler->intercept
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class SelectPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof Executor) {
System.out.println("SelectPlugin");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {}
}
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class StatementPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof StatementHandler) {
System.out.println("StatementPlugin");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {}
}
@Intercepts({@Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class})})
public class ParameterPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof ParameterHandler) {
System.out.println("ParameterPlugin");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof ParameterHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {}
}
@Intercepts({@Signature(type = ResultHandler.class,method = "handleResult",args = {ResultContext.class})})
public class ResultPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof ResultHandler) {
System.out.println("ResultPlugin");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof ResultHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {}
}
二、攔截器注冊的三種方式
前面介紹了Mybatis的攔截對象及其接口的實現(xiàn)方式,那么在項目中如何注冊攔截器呢?本文中給出三種注冊方式。
1.XML注冊
xml注冊是最基本的方式,是通過在Mybatis配置文件中plugins元素來進行注冊的。一個plugin對應(yīng)著一個攔截器,在plugin元素可以指定property子元素,在注冊定義攔截器時把對應(yīng)攔截器的所有property通過Interceptor的setProperties方法注入給攔截器。因此攔截器注冊xml方式如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- ...... -->
<plugins>
<plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
<property name="prop1" value="prop1"/>
<property name="prop2" value="prop2"/>
</plugin>
</plugins>
<!-- ...... -->
</configuration>
2.配置類注冊
配置類注冊是指通過Mybatis的配置類中聲明注冊攔截器,配置類注冊也可以通過Properties類給Interceptor的setProperties方法注入?yún)?shù)。具體參考如下:
@Configuration
public class MyBatisConfig {
@Bean
public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
UpdatePlugin executorInterceptor = new UpdatePlugin();
Properties properties = new Properties();
properties.setProperty("prop1", "value1");
// 給攔截器添加自定義參數(shù)
executorInterceptor.setProperties(properties);
sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);
sqlSessionFactory.getConfiguration().addInterceptor(new StatementPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ResultPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ParameterPlugin());
// sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());
return "interceptor";
}
// 與sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());效果一致
@Bean
public SelectPlugin SelectInterceptor() {
SelectPlugin interceptor = new SelectPlugin();
Properties properties = new Properties();
// 調(diào)用properties.setProperty方法給攔截器設(shè)置自定義參數(shù)
interceptor.setProperties(properties);
return interceptor;
}
}
3.注解方式
通過@Component注解方式是最簡單的方式,在不需要轉(zhuǎn)遞自定義參數(shù)時可以使用,方便快捷。
@Component
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class SelectPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof Executor) {
System.out.println("SelectPlugin");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
三、ParameterHandler參數(shù)改寫-修改時間和修改人統(tǒng)一插入
針對具體的攔截器實現(xiàn)進行描述。日常編碼需求中會碰到修改時需要插入修改的時間和人員,如果要用xml的方式去寫非常麻煩,而通過攔截器的方式可以快速實現(xiàn)全局的插入修改時間和人員。先看代碼:
@Component
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
})
public class MyBatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 參數(shù)代理
if (invocation.getTarget() instanceof ParameterHandler) {
System.out.println("ParameterHandler");
// 自動添加操作員信息
autoAddOperatorInfo(invocation);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 自動添加操作員信息
*
* @param invocation 代理對象
* @throws Throwable 異常
*/
private void autoAddOperatorInfo(Invocation invocation) throws Throwable {
System.out.println("autoInsertCreatorInfo");
// 獲取代理的參數(shù)對象ParameterHandler
ParameterHandler ph = (ParameterHandler) invocation.getTarget();
// 通過MetaObject獲取ParameterHandler的反射內(nèi)容
MetaObject metaObject = MetaObject.forObject(ph,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 通過MetaObject反射的內(nèi)容獲取MappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
// 當(dāng)sql類型為INSERT或UPDATE時,自動插入操作員信息
if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT ||
mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
// 獲取參數(shù)對象
Object obj = ph.getParameterObject();
if (null != obj) {
// 通過反射獲取參數(shù)對象的屬性
Field[] fields = obj.getClass().getDeclaredFields();
// 遍歷參數(shù)對象的屬性
for (Field f : fields) {
// 如果sql是INSERT,且存在createdAt屬性
if ("createdAt".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
// 設(shè)置允許訪問反射屬性
f.setAccessible(true);
// 如果沒有設(shè)置createdAt屬性,則自動為createdAt屬性添加當(dāng)前的時間
if (null == f.get(obj)) {
// 設(shè)置createdAt屬性為當(dāng)前時間
f.set(obj, LocalDateTime.now());
}
}
// 如果sql是INSERT,且存在createdBy屬性
if ("createdBy".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
// 設(shè)置允許訪問反射屬性
f.setAccessible(true);
// 如果沒有設(shè)置createdBy屬性,則自動為createdBy屬性添加當(dāng)前登錄的人員
if (null == f.get(obj)) {
// 設(shè)置createdBy屬性為當(dāng)前登錄的人員
f.set(obj, 0);
}
}
// sql為INSERT或UPDATE時均需要設(shè)置updatedAt屬性
if ("updatedAt".equals(f.getName())) {
f.setAccessible(true);
if (null == f.get(obj)) {
f.set(obj, LocalDateTime.now());
}
}
// sql為INSERT或UPDATE時均需要設(shè)置updatedBy屬性
if ("updatedBy".equals(f.getName())) {
f.setAccessible(true);
if (null == f.get(obj)) {
f.set(obj, 0);
}
}
}
// 通過反射獲取ParameterHandler的parameterObject屬性
Field parameterObject = ph.getClass().getDeclaredField("parameterObject");
// 設(shè)置允許訪問parameterObject屬性
parameterObject.setAccessible(true);
// 將上面設(shè)置的新參數(shù)對象設(shè)置到ParameterHandler的parameterObject屬性
parameterObject.set(ph, obj);
}
}
}
}
攔截器的接口實現(xiàn)參考前文,這里著重介紹autoAddOperatorInfo方法里的相關(guān)類。
1.ParameterHandler
接口源碼:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}
提供兩個方法:
getParameterObject是獲取參數(shù)對象,可能存在null,需要注意null指針。
setParameters是控制如何設(shè)置SQL參數(shù),即sql語句中配置的java對象和jdbc類型對應(yīng)的關(guān)系,例如#{id,jdbcType=INTEGER},id默認(rèn)類型是javaType=class java.lang.Integer。
該接口有一個默認(rèn)的實現(xiàn)類,源碼如下:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
public Object getParameterObject() {
return this.parameterObject;
}
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
}
}
}
}
}
}
通過DefaultParameterHandler實現(xiàn)類我們知道通過ParameterHandler可以獲取到哪些屬性和方法,其中包括我們下面一個重要的類MappedStatement。
2.MappedStatement
MyBatis的mapper文件中的每個select/update/insert/delete標(biāo)簽會被解析器解析成一個對應(yīng)的MappedStatement對象,也就是一個MappedStatement對象描述一條SQL語句。MappedStatement對象屬性如下:
// mapper配置文件名
private String resource;
// mybatis的全局信息,如jdbc
private Configuration configuration;
// 節(jié)點的id屬性加命名空間,如:com.example.mybatis.dao.UserMapper.selectByExample
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
// sql語句的類型:select、update、delete、insert
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
在本例中通過MappedStatement對象的sqlCommandType來判斷當(dāng)前的sql類型是insert、update來進行下一步的操作。
四、通過StatementHandler改寫SQL
StatementHandler是用于封裝JDBC Statement操作,負(fù)責(zé)對JDBC Statement的操作,如設(shè)置參數(shù),并將Statement結(jié)果集轉(zhuǎn)換成List集合。
實現(xiàn)代碼如下:
刪除注解標(biāo)記
@Target({ElementType.METHOD}) //表示注解的使用范圍
@Retention(RetentionPolicy.RUNTIME) //注解的保存時間
@Documented //文檔顯示
public @interface DeletedAt {
boolean has() default true;
}
Dao層添加刪除注解,為false時不添加刪除標(biāo)志
@Mapper
public interface AdminProjectDao {
@DeletedAt(has = false)
List<AdminProjectPo> selectProjects(AdminProjectPo po);
}
攔截器通過刪除注解標(biāo)記判斷是否添加刪除標(biāo)志
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class MyBatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (invocation.getTarget() instanceof StatementHandler) {
System.out.println("StatementHandler");
checkHasDeletedAtField(invocation);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 檢查查詢是否需要添加刪除標(biāo)志字段
*
* @param invocation 代理對象
* @throws Throwable 異常
*/
private void checkHasDeletedAtField(Invocation invocation) throws Throwable {
System.out.println("checkHasDeletedAtField");
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 通過MetaObject訪問對象的屬性
MetaObject metaObject = MetaObject.forObject(
statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 獲取成員變量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 如果sql類型是查詢
if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
// 獲取刪除注解標(biāo)志
DeletedAt annotation = null;
String id = mappedStatement.getId();
String className = id.substring(0, id.lastIndexOf("."));
String methodName = id.substring(id.lastIndexOf(".") + 1);
Class<?> aClass = Class.forName(className);
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
declaredMethod.setAccessible(true);
//方法名相同,并且注解是DeletedAt
if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(DeletedAt.class)) {
annotation = declaredMethod.getAnnotation(DeletedAt.class);
}
}
// 如果注解不存在或者注解為true(默認(rèn)為true) 則為mysql語句增加刪除標(biāo)志
if (annotation == null || annotation.has()) {
BoundSql boundSql = statementHandler.getBoundSql();
//獲取到原始sql語句
String sql = boundSql.getSql();
//通過反射修改sql語句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
String newSql = sql.replaceAll("9=9", "9=9 and deleted_at is null ");
field.set(boundSql, newSql);
}
}
}
}
在SQL語句替換上需要能識別到要被替換的內(nèi)容,因此在xml的sql語句中加入特殊標(biāo)志"9=9",該標(biāo)志不影響原來SQL的執(zhí)行結(jié)果,不同的過濾條件可以設(shè)置不同的標(biāo)志,是一個比較巧妙的替換方式。
以上就是MyBatis攔截器的原理與使用的詳細(xì)內(nèi)容,更多關(guān)于MyBatis攔截器的資料請關(guān)注腳本之家其它相關(guān)文章!
- mybatisplus 的SQL攔截器實現(xiàn)關(guān)聯(lián)查詢功能
- Mybatis自定義攔截器和插件開發(fā)詳解
- mybatis 自定義實現(xiàn)攔截器插件Interceptor示例
- mybatis 通過攔截器打印完整的sql語句以及執(zhí)行結(jié)果操作
- Mybatis Plugin攔截器開發(fā)過程詳解
- 簡單了解mybatis攔截器實現(xiàn)原理及實例
- mybatis攔截器實現(xiàn)通用權(quán)限字段添加的方法
- Mybatis中攔截器的簡單實現(xiàn)方法
- mybatis攔截器與分頁插件實例教程
- Mybatis Interceptor 攔截器的實現(xiàn)
- MyBatis攔截器實現(xiàn)分頁功能的實現(xiàn)方法
相關(guān)文章
java向文件中追加內(nèi)容與讀寫文件內(nèi)容源碼實例代碼
這篇文章主要介紹了java向文件中追加內(nèi)容與讀寫文件內(nèi)容源碼實例代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04
Java 模擬數(shù)據(jù)庫連接池的實現(xiàn)代碼
這篇文章主要介紹了Java 模擬數(shù)據(jù)庫連接池的實現(xiàn),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
Eclipse+Java+Swing實現(xiàn)圖書管理系統(tǒng)(詳細(xì)代碼)
這篇文章主要介紹了Eclipse+Java+Swing實現(xiàn)圖書管理系統(tǒng)并附上詳細(xì)代碼,需要的小伙伴可以參考一下,希望對你有所幫助2022-01-01
Android開發(fā)Kotlin實現(xiàn)圓弧計步器示例詳解
這篇文章主要為大家介紹了Android開發(fā)Kotlin繪制圓弧計步器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
Mybatis RowBounds 限制查詢條數(shù)的實現(xiàn)代碼
Oracle 數(shù)據(jù)庫查詢增加RowBounds限制查詢條數(shù),默認(rèn)是0到1000條。下面給大家分享Mybatis RowBounds 限制查詢條數(shù)的實現(xiàn)代碼,需要的朋友參考下吧2016-11-11

