Java?mybatis?開發(fā)自定義插件
介紹
MyBatis 允許你在映射語句執(zhí)行過程中的某一點進行攔截調用。比如執(zhí)行前、執(zhí)行后或者對SQL結果集處理、sql入?yún)⑻幚淼龋@樣就可以在不修改mybatis源碼的情況下對sql執(zhí)行的過程或結果進行修改,實現(xiàn)了解耦。mybatis 是在動態(tài)代理的基礎上實現(xiàn)的。
使用場景
如果業(yè)務中需要設置一些通用數(shù)據(jù)庫操作,比如創(chuàng)建時間、創(chuàng)建人等通用字段又或者是分頁操作等,這類都可以使用插件開發(fā)方式,PageHelper就是基于Interceptor的一個mybatis插件。
Interceptor攔截器
public interface Interceptor {
/**
* 子類攔截器必須要實現(xiàn)的方法,
* 在該方法對內自定義攔截邏輯
* @param invocation
* @return
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
生成目標類的代理對象
* 也可以根據(jù)需求不返回代理對象,這種情況下這個攔截器將不起作用
* 無特殊情況使用默認的即可
* @param target
* @return
*/
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 設置變量
* 在注冊攔截器的時候設置變量,在這里可以獲取到
* @param properties
*/
default void setProperties(Properties properties) {
// NOP
}
}InterceptorChain攔截器鏈
在org.apache.ibatis.plugin包下有個InterceptorChain類,該類有個interceptors屬性,所有實現(xiàn)了Interceptor接口的攔截器都會被存儲到interceptors中。
源碼如下:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
/**
* 讓目標類在所有的攔截器中生成代理對象,并返回代理對象
* @param target
* @return
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* 添加過濾器
* @param interceptor
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}攔截方法
默認情況下,MyBatis 允許使用插件來攔截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 接口下面的方法。如果系統(tǒng)中有配置自定義插件,默認情況下,系統(tǒng)會把上面四個類的默認子類都作為目標類來讓所有的攔截器進行攔截, 以保證所有的攔截器都能對Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler子類進行攔截。
源碼如下: 在org.apache.ibatis.session.Configuration類中
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 使用攔截器進行攔截
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 使用攔截器進行攔截
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 使用攔截器進行攔截
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 使用攔截器進行攔截
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}注解
Intercepts
Intercepts的作用是攔截Signature注解數(shù)組中指定的類的方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* Returns method signatures to intercept.
* Signature注解列表
* @return method signatures
*/
Signature[] value();
}Signature
Signature注解作用是攔截指定類的方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
/**
* Returns the java type.
* 要攔截的類
* @return the java type
*/
Class<?> type();
/**
* Returns the method name.
* 要攔截的類的方法
* @return the method name
*/
String method();
/**
* Returns java types for method argument.
* 要攔截的類的方法的參數(shù)列表
* @return java types for method argument
*/
Class<?>[] args();
}示例
步驟
- 1、實現(xiàn)org.apache.ibatis.plugin.Interceptor接口
- 2、添加Intercepts和Signature注解
- 3、根據(jù)需求實現(xiàn)Interceptor方法邏輯
入門使用
這里會寫兩個使用示例,一個是動態(tài)給屬性賦值,一個是打印SQL。
表結構:
CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `gender` varchar(20) DEFAULT NULL, `userName` text NOT NULL, `create_date` datetime DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
實體類:
public class UserInfo {
private Long id;
private String gender;
private String userName;
private Date createDate;
// 省略get、set方法
}動態(tài)給屬性賦值
在創(chuàng)建表時,有些是每個表都有的參數(shù),比如創(chuàng)建時間、修改時間等,這類參數(shù)如果在每個類進行保存或修改的時候都進行設值的話就有點重復操作了,所以可以通過mybatis插件進行處理。
1、Interceptor 實現(xiàn)類InsertInterceptor:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class InsertInterceptor implements Interceptor {
private Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
MappedStatement mappedStatement= (MappedStatement) args[0];
Object parameter = args[1];
Executor executor = (Executor) invocation.getTarget();
final Class<?> parameterClass = parameter.getClass();
final String createDate = properties.getProperty("createDate");
//獲取createDate 屬性描述器
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(createDate , parameterClass);
//獲取createDate 寫方法
final Method writeMethod = propertyDescriptor.getWriteMethod();
//調用createDate 寫方法
writeMethod.invoke(parameter , new Date());
return executor.update(mappedStatement, parameter);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target , this);
}
/**
* 設置變量
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}2、mybatis配置文件中注冊InsertInterceptor
<plugins>
<plugin interceptor="plugin.PrintSqlPlugin"/>
<plugin interceptor="plugin.InsertInterceptor">
<property name="createDate" value="createDate"/>
</plugin>
</plugins>3、測試
public class UserTest {
private final static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
} catch (IOException e) {
System.out.println(e.getMessage());
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
@Test
public void insert(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfo userInfo = new UserInfo();
userInfo.setUserName("test1");
userInfo.setGender("male");
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
mapper.insertUser(userInfo);
sqlSession.commit();
sqlSession.close();
}
}查看數(shù)據(jù)庫,可以看到在沒有給createDate屬性收到賦值的情況下,通過攔截器進行賦值,最后是保存到數(shù)據(jù)庫中了。

打印SQL
Interceptor 實現(xiàn)類PrintSqlPlugin:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class PrintSqlPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//被代理對象
Object target = invocation.getTarget();
//代理方法
Method method = invocation.getMethod();
//方法參數(shù)
Object[] args = invocation.getArgs();
MappedStatement mappedStatement= (MappedStatement) args[0];
Object parameter = args[1];
final BoundSql mappedStatementBoundSql = mappedStatement.getBoundSql(parameter);
System.err.println("BoundSql="+mappedStatementBoundSql.getSql());
final Configuration configuration = mappedStatement.getConfiguration();
final String showSql = showSql(configuration, mappedStatementBoundSql);
System.err.println("sql="+showSql);
//方法執(zhí)行
final Object returnValue = invocation.proceed();
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 獲取參數(shù)
* @param obj
* @return
*/
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;
}
/**
* 打印SQL
* @param configuration
* @param boundSql
* @return
*/
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;
}
}使用同樣的方法進行測試,查看控制臺打印結果:
BoundSql=insert into users (gender, userName ,create_date) values(? , ?, ?)
sql=insert into users (gender, userName ,create_date) values('male' , 'test2', '2022-1-14 18:40:08')
mybatis自定義插就到這里了,其實操作也簡單,用好了也很強大。
到此這篇關于Java mybatis 開發(fā)自定義插件的文章就介紹到這了,更多相關Java mybatis 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解mall整合SpringBoot+MyBatis搭建基本骨架
這篇文章主要介紹了詳解mall整合SpringBoot+MyBatis搭建基本骨架,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08
Java畢業(yè)設計實戰(zhàn)之共享租車信息管理系統(tǒng)的實現(xiàn)
這是一個使用了java+Jsp+Servlet+Jdbc+Mysql開發(fā)的共享租車信息管理系統(tǒng),是一個畢業(yè)設計的實戰(zhàn)練習,具有租車管理該有的所有功能,感興趣的朋友快來看看吧2022-02-02
Spring?MVC啟動之HandlerMapping作用及實現(xiàn)詳解
這篇文章主要為大家介紹了Spring?MVC啟動之HandlerMapping作用及實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
使用Eclipse開發(fā)工具如何解決Java Compiler中Annotation Processin不出現(xiàn)的問題
這篇文章主要介紹了使用Eclipse開發(fā)工具如何解決Java Compiler中Annotation Processin不出現(xiàn)的相關資料,需要的朋友可以參考下2015-11-11
Springboot yml如何獲取系統(tǒng)環(huán)境變量的值
這篇文章主要介紹了Springboot yml如何獲取系統(tǒng)環(huán)境變量的值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02

