给自己的每日一句
不从恶人的计谋,不站罪人的道路,不坐亵慢人的座位,惟喜爱耶和华的律法,昼夜思想,这人便为有福!他要像一棵树栽在溪水旁,按时候结果子,叶子也不枯干。凡他所做的尽都顺利
本文内容整理自《孙哥说Mybatis系列视频课程》,老师实力十分雄厚,B站搜孙帅可以找到本人
前言
Mybatis拦截器的开发基本上包含两个步骤:编码和配置。
拦截器编码当中需要实现拦截器的接口,在这个类上边基于注解标注我们需要拦截的目标。这就是自定义拦截器了。
一:拦截器接口说明
public interface Interceptor {
//拦截前需要实现的功能+放行执行具体的Dao中的方法。
Object intercept(Invocation invocation) throws Throwable;
//这个方法的作用就是把这个拦截器的目标,传递给下一个拦截器。这种情况下适用于多个拦截器的存在
//当第一个拦截器处理完毕之后,把处理完毕的目标,传递给下一个拦截器。
//这个方法涉及的是目标传递的过程。
Object plugin(Object target);
//获取拦截器相关参数的
void setProperties(Properties properties);
}
这里边真正起拦截作用的是intercept方法。
二:拦截器实现示意
1:拦截器编码
@Intercepts({
@Signature(type= Executor.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type= Executor.class,method="update",args={MappedStatement.class,Object.class})
})
public class MyMybatisInterceptor implements Interceptor {
private String test;
private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor.class);
@Override
/**
* 作用:执行的拦截功能 书写在这个方法中.
* 放行
*/
public Object intercept(Invocation invocation) throws Throwable {
if (log.isDebugEnabled())
log.debug("----拦截器中的 intercept 方法执行------ "+test);
return invocation.proceed();
}
/*
* 把这个拦截器目标 传递给 下一个拦截器
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
/*
* 获取拦截器相关参数的
*/
@Override
public void setProperties(Properties properties) {
this.test = properties.getProperty("test");
}
}
@Intercepts({
@Signature(type= Executor.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type= Executor.class,method="update",args={MappedStatement.class,Object.class})
})
这样一看就明白咋回事了,我们要拦截是的是Executor当中的方法,方法名字是query,args是方法中的参数,这个方法要严格和这个参数对应上。这样Mybatis就能唯一的确认要拦截哪个方法了
2:拦截器配置
后续我们要在Mybatis当中配置添加拦截器配置,通知Mybatis启动的时候加载当前开发的拦截器。
<plugins>
<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor"/>
</plugins>
这里边老铁们可能会有一个问题,咱们这个不是拦截的是Executor的query方法么,然后这里边真正走数据库查询的时候不是走的StatementHandler当中的方法么?这种理解是没有问题的,咱们拦截的是Executor当中的方法,但是Executor当中执行query的时候,底层走的也是StatementHandler当中的方法。拦住了Executor就相当于拦截住了StatementHandler当中的query。那同样拦截住了
三:拦截器作用示范
/**
* 用于测试:Plugins的基本使用
*/
@Test
public void test1() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
UserDAO userDAO = session.getMapper(UserDAO.class);
User user = userDAO.queryUserById(4);
System.out.println("user = " + user);
User newUser = new User(4, "xiaohuahua");
userDAO.update(newUser);
session.commit();
}
执行结果如下:
2023-06-15 20:13:02 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------ 111111
2023-06-15 20:13:02 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 20:13:02 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 20:13:02 DEBUG PooledDataSource:406 - Created connection 1561408618.
2023-06-15 20:13:02 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5d11346a]
2023-06-15 20:13:02 DEBUG queryUserById:159 - ==> Preparing: select id,name from t_user where id = ?
2023-06-15 20:13:02 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 20:13:02 DEBUG queryUserById:159 - <== Total: 1
user = User{id=4, name='二姐'}
2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------ 111111
2023-06-15 20:13:02 DEBUG update:159 - ==> Preparing: update t_user set name=? where id=?
2023-06-15 20:13:02 DEBUG update:159 - ==> Parameters: xiaohuahua(String), 4(Integer)
2023-06-15 20:13:02 DEBUG update:159 - <== Updates: 1
2023-06-15 20:13:02 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5d11346a]
Process finished with exit code 0
我们开发完毕拦截器之后,Mybatis启动的时候自动为我们进行加载,运行的时候自动走拦截器。
四:拦截器作用解析
1:如何给拦截器注入参数?
首先需要在Mybatis-config.xml当中通过property标签配置拦截器属性
<plugins>
<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor">
<property name="test" value="111111"/>
</plugin>
</plugins>
然后在MyMybatisInterceptor拦截器初始化的时候,基于其中setProperties方法进行拦截器属性赋值,赋值之后在跑动的时候就可以在使用这些属性了。
@Override
public void setProperties(Properties properties) {
this.test = properties.getProperty("test");
}
上边日志中的:
2023-06-15 20:13:02 DEBUG MyMybatisInterceptor:28 - ----拦截器中的 intercept 方法执行------ 111111
不就是最好的说明么?
2:如何配置拦截多个方法?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
@Intercepts({
@Signature(type= Executor.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type= Executor.class,method="update",args={MappedStatement.class,Object.class})
})
@Intercepts({})这里边的{}就代表了数组,如果只有一个数据的话,{}是可以去掉的。
五:拦截器细节分析
1:拦截器想要拦截SQL,如何拦截最合适?
Executor的功能是比较繁杂的,有增删改查包括事务的一些操作,而他真正增删改查的操作是交给StatementHandler来做,StatementHandler当中的操作就比较单一了,所以我们把拦截放到StatementHandler上是比较合理的。statementHandler当中只有两个query和一个update,然后里边还有一个prepare方法(BaseStatementHandler当中写的,完成Mybatis当中所有的Statement对象的创建),这个方法的作用是准备Statement给StatementHandler中的query和update使用。
Ps:StatementHandler使用的是装饰器设计模式,然后也有适配器设计模式。
所以,如果拦截器的目的是获取SQL的话,最合适的方法就是拦截BaseStatementHandler当中的prepare这个唯一生产Statement对象的方法。
而且,我们观察下prepare这个方法:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
这里边我们看到这个方法里边有Connection,有了连接对象之后,我们就可以拿到所有的JDBC中的对象。
2:拦截器想要拦截SQL,为什么这么合适?
1:BaseStatementHandler中的prepare方法生产所有的Statement对象。
2:prepare方法里边有Connection,有了连接对象之后,我们就可以拿到所有的JDBC中的对象
3:拦截器拦截prepare方法测试
public abstract class MyMybatisInterceptorAdapter implements Interceptor {
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
}
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyMybatisInterceptor2 extends MyMybatisInterceptorAdapter {
private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (log.isDebugEnabled())
log.debug("----拦截器中的 MyMybatisInterceptor2 intercept方法执行------ "+test);
return invocation.proceed();
}
@Override
public void setProperties(Properties properties) {
}
}
<plugins>
<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor2"/>
</plugins>
执行结果如下:
2023-06-15 21:16:03 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:03 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:16:04 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 21:16:04 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 21:16:04 DEBUG PooledDataSource:406 - Created connection 929776179.
2023-06-15 21:16:04 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@376b4233]
2023-06-15 21:16:04 DEBUG MyMybatisInterceptor2:25 - ----拦截器中的 MyMybatisInterceptor2 intercept方法执行------
2023-06-15 21:16:04 DEBUG queryUserById:159 - ==> Preparing: select id,name from t_user where id = ?
2023-06-15 21:16:04 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 21:16:04 DEBUG queryUserById:159 - <== Total: 1
Process finished with exit code 0
4:如何拦截器中获取SQL
我们先找到boundSql对象
具体的代码如下,有两种实现方式:
@Override
public Object intercept(Invocation invocation) throws Throwable {
//RoutingStatementHandler---delegate---
//RoutingStatementHandler satementHandler = (RoutingStatementHandler) invocation.getTarget();
//BoundSql boundSql = satementHandler.getBoundSql();
//String sql = boundSql.getSql();
//log.info("sql:",sql);
基于Mybatis提供的反射工厂来干。直接打破封装用反射即可,以下是Mybatis低等用于反射的对象。
MetaObject metaObject = SystemMetaObject.forObject(invocation);
String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
if (log.isDebugEnabled()) {
log.debug("sql : " + sql);
}
return invocation.proceed();
}
具体的实现结果如下:
Connected to the target VM, address: '127.0.0.1:34056', transport: 'socket'
2023-06-15 21:26:00 DEBUG LogFactory:135 - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:26:00 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
2023-06-15 21:38:58 DEBUG UserDAO:62 - Cache Hit Ratio [com.baizhiedu.dao.UserDAO]: 0.0
2023-06-15 21:38:58 DEBUG JdbcTransaction:137 - Opening JDBC Connection
2023-06-15 21:38:58 DEBUG PooledDataSource:406 - Created connection 1906879951.
2023-06-15 21:38:58 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@71a8adcf]
2023-06-15 21:38:58 DEBUG MyMybatisInterceptor2:25 - ----拦截器中的 MyMybatisInterceptor2 intercept方法执行------
2023-06-15 21:38:58 DEBUG queryUserById:159 - ==> Preparing: select id,name from t_user where id = ?
2023-06-15 21:38:58 DEBUG queryUserById:159 - ==> Parameters: 4(Integer)
2023-06-15 21:38:58 DEBUG queryUserById:159 - <== Total: 1
Disconnected from the target VM, address: '127.0.0.1:34056', transport: 'socket'
Process finished with exit code 0
5:细节说明
MetaObject metaObject = SystemMetaObject.forObject(invocation);
String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
if (log.isDebugEnabled()) {
log.debug("sql : " + sql);
}
Mybatis当中很多反射操作都是这么干的,这样写更加Mybatis一点,这样操作是基于对象从属的层级一层一层点进去的,当然如果我们想要去给他这样赋值也是可以的。
String sql = (String) metaObject.set("target.delegate.boundSql.sql","select * from user where id = ?");