mybatis自定義攔截器(一)基本使用
mybatis自定義攔截器(二)對象詳解
1. 攔截器注解
1. mybatis自定義攔截器實現(xiàn)步驟:
- 實現(xiàn)
org.apache.ibatis.plugin.Interceptor
接口多搀。 - 添加攔截器注解
org.apache.ibatis.plugin.Intercepts
康铭。 - 配置文件中添加攔截器从藤。
2. 在mybatis中可被攔截的類型有四種(按照攔截順序):
- Executor:攔截執(zhí)行器的方法夷野。
- ParameterHandler:攔截參數(shù)的處理悯搔。
- ResultHandler:攔截結(jié)果集的處理妒貌。
- StatementHandler:攔截Sql語法構(gòu)建的處理。
1. 不同攔截類型執(zhí)行順序:
2. 多個插件攔截的順序?
需要注意的是平匈,因為攔截器Aa和攔截器Bb均是攔截的StatementHandler對象增炭,所以攔截器B在此獲取StatementHandler的時候隙姿,獲取的是代理對象输玷。
3. 多個插件plugin()和intercept()方法的執(zhí)行順序
先執(zhí)行每個插件的plugin方法欲鹏,若是@Intercepts注解標明需要攔截該對象赔嚎,那么生成類型對象的代理對象胧弛。(即使該插件需要攔截該類型對象结缚,但是依舊會執(zhí)行下一個插件的plugin方法)。知道執(zhí)行完畢所有的plugin方法沉馆。在執(zhí)行每個Intercept方法斥黑。
3. 攔截器注解的作用:
自定義攔截器必須使用mybatis提供的注解來聲明我們要攔截的類型對象锌奴。
Mybatis插件都要有Intercepts [in特賽婆斯]
注解來指定要攔截哪個對象哪個方法鹿蜀。我們知道,Plugin.wrap方法會返回四大接口對象的代理對象颠焦,會攔截所有的方法往枣。在代理對象執(zhí)行對應方法的時候分冈,會調(diào)用InvocationHandler處理器的invoke方法雕沉。
4. 攔截器注解的規(guī)則:
具體規(guī)則如下:
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
- @Intercepts:標識該類是一個攔截器坡椒;
- @Signature:指明自定義攔截器需要攔截哪一個類型,哪一個方法幼衰;
2.1 type:對應四種類型中的一種渡嚣;
2.2 method:對應接口中的哪類方法(因為可能存在重載方法)识椰;
2.3 args:對應哪一個方法腹鹉;
5. 攔截器可攔截的方法:
攔截的類 | 攔截的方法 |
---|---|
Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
ParameterHandler | getParameterObject, setParameters |
StatementHandler | prepare, parameterize, batch, update, query |
ResultSetHandler | handleResultSets, handleOutputParameters |
2. 攔截器方法
2.1 官方插件開發(fā)方式
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理對象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法參數(shù)
// do something ...... 方法攔截前執(zhí)行代碼塊
Object result = invocation.proceed();
// do something .......方法攔截后執(zhí)行代碼塊
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
2.2 攔截器的方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
2.2.1 setProperties方法
如果我們的攔截器需要一些變量對象功咒,而且這個對象是支持可配置的力奋。
類似于Spring中的@Value("${}")從application.properties文件中獲取景殷。
使用方法:
<plugin interceptor="com.plugin.mybatis.MyInterceptor">
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</plugin>
方法中獲取參數(shù):properties.getProperty("username");
問題:但是為什么不直接使用@Value("${}") 獲取變量咐旧?
解答:因為mybatis框架本身就是一個可以獨立使用的框架绩蜻,沒有像Spring這種做了很多的依賴注入办绝。
2.2.2 plugin方法
這個方法的作用是就是讓mybatis判斷,是否要進行攔截,然后做出決定是否生成一個代理昔驱。
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
需要注意的是:每經(jīng)過一個攔截器對象都會調(diào)用插件的plugin方法骤肛,也就是說窍蓝,該方法會調(diào)用4次吓笙。根據(jù)@Intercepts注解來決定是否進行攔截處理。
問題1:
Plugin.wrap(target, this)
方法的作用絮蒿?
解答:判斷是否攔截這個類型對象(根據(jù)@Intercepts注解決定)土涝,然后決定是返回一個代理對象還是返回原對象但壮。
故我們在實現(xiàn)plugin方法時常侣,要判斷一下目標類型胳施,是本插件要攔截的對象時才執(zhí)行Plugin.wrap方法,否則的話鸟召,直接返回目標本身氨鹏。
問題2:攔截器代理對象可能經(jīng)過多層代理,如何獲取到真實的攔截器對象跟继?
/**
* <p>
* 獲得真正的處理對象,可能多層代理.
* </p>
*/
@SuppressWarnings("unchecked")
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
2.2.3 intercept(Invocation invocation)方法
我們知道舔糖,mybatis只能攔截四種類型的對象金吗。而intercept
方法便是處理攔截到的對象趣竣。比如我們要攔截StatementHandler#query(Statement st,ResultHandler rh)
方法,那么Invocation
就是這個對象卫袒,Invocation
中有三個參數(shù)单匣。
- target:StatementHandler户秤;
- method :query虎忌;
- args[]:Statement st,ResultHandler rh
org.apache.ibatis.reflection.SystemMetaObject#forObject
:方便的獲取對象中的值。
案例:將參數(shù)拼接到sql語句堪藐。
因為已經(jīng)執(zhí)行了ParameterHandler攔截器挑围,故Statement對象已經(jīng)是完全拼接好的SQL語句杉辙。
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
@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 MybatisLogInterceptor implements Interceptor {
private Properties properties;
private static final Logger logger = LoggerFactory.getLogger(MybatisLogInterceptor.class);
public Object intercept(Invocation invocation) throws Throwable {
long start = 0L;
String sqlId = "";
BoundSql boundSql = null;
Configuration configuration = null;
Object returnValue = null;
try {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
sqlId = mappedStatement.getId();
if(sqlId.contains("History") || sqlId.contains("Tmp")){
return invocation.proceed();
}
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
boundSql = mappedStatement.getBoundSql(parameter);
configuration = mappedStatement.getConfiguration();
start = System.currentTimeMillis();
} catch (Exception e) {
logger.debug("Mybatis攔截器前置處理異常 原因:", e);
logger.error("Mybatis攔截器前置處理異常 原因:" + e);
}
returnValue = invocation.proceed();
try {
long end = System.currentTimeMillis();
long time = (end - start);
String sql = getSql(configuration, boundSql, sqlId, time);
// if (time >= Config.SQL_WARN_TIME) {
// logger.warn(sql);
// } else {
// logger.info(sql);
// }
} catch (Exception e) {
logger.debug("Mybatis攔截器后置處理異常 原因:", e);
logger.error("Mybatis攔截器后置處理異常 原因:" + e);
}
return returnValue;
}
public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append("【sqlId】").append(sqlId);
str.append("【SQL耗時-").append(time).append("-毫秒】");
str.append("【SQL】").append(sql);
//logger.debug(SQLFormatter.format(str.toString()));
logger.debug(str.toString());
return str.toString();
}
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
value = value.replaceAll("\\\\", "\\\\\\\\");
value = value.replaceAll("\\$", "\\\\\\$");
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties0) {
this.properties = properties0;
}
}
2. MappedStatement.class
一個MappedStatement對象對應Mapper配置文件中的一個select/update/insert/delete節(jié)點岖食,主要描述的是一條sql語句泡垃。其屬性為:
//節(jié)點中的id屬性加要命名空間
private String id;
//直接從節(jié)點屬性中取
private Integer fetchSize;
//直接從節(jié)點屬性中取
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
//對應一條SQL語句
private SqlSource sqlSource;
//每條語句都對就一個緩存,如果有的話忠寻。
private Cache cache;
//這個已經(jīng)過時了
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
//SQL的類型,select/update/insert/detete
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
//是否有內(nèi)映射
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
文章參考
https://blog.csdn.net/Liu_York/article/details/88053053