MyBatis 拦截器 - 项目中使用
程序员文章站
2022-06-28 18:30:04
一、MyBatis 拦截器介绍二、自定义拦截器类2.1、Interceptor接口2.2、@Intercepts注解三、项目使用3.1、jar3.2、xml配置代码涉及知识点:1、辅助类2、@PostConstruct 注解...
一、MyBatis 拦截器介绍
MyBatis拦截器设计初衷为了供用户在某些时候不动原有逻辑,通过拦截某些方法的调用,拦截的方法执行前后进添加逻辑。当然,也可以执行自己的逻辑,不执行被拦截的方法。
Mybatis核心对象 | 解释 |
---|---|
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement 所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装 |
SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
Configuration | MyBatis所有的配置信息都维持在Configuration对象之中 |
二、自定义拦截器类
实现Interceptor接口,自定义拦截器类上添加@Intercepts注解。
2.1、Interceptor接口
public interface Interceptor {
/**
* 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
*
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
* 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
*/
Object plugin(Object target);
/**
* 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
*/
void setProperties(Properties properties);
}
2.2、@Intercepts注解
Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。
Signature来指定咱们需要拦截那个类对象的哪个方法。
自定义一个MybatisInterceptor类,来拦截Executor类里面的update。
三、项目使用
3.1、jar
<!-- DAO: MyBatis -->
<!-- <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId>
<version>3.3.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> -->
<!-- mybatis-plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.1.2</version>
</dependency>
<!--扩展包 - 解决 NoClassDefFoundError: org/mybatis/logging/LoggerFactory-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
3.2、xml配置
<!-- 3、配置mybatis-plus的sqlSessionFactory -->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<property name="plugins">
<array>
<!-- 集成PageHelper分页插件 -->
<bean class="com.github.pagehelper.PageHelper">
<property name="properties">
<value>dialect=mysql</value>
</property>
</bean>
<!-- 自带的 mybatis-plus性能拦截器 -->
<!-- <bean id="performanceInterceptor"
class="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor"/>-->
<!-- 自定义 mybatis-plus性能拦截器,兼打印sql,不建议生产环境配置 -->
<bean id="mybatisInterceptor"
class="com.common.MybatisInterceptor"/>
</array>
</property>
</bean>
代码
/**
* 参考文献:http://www.yangxuwang.com/jingyan/1533818219451005
* <p>
* 定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回
* 一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。
* <p>
* 对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,
* 而@Signature则表明要拦截的接口、方法以及对应的参数类型
* Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理
*/
/**
* method:表示拦截的方法,mybatis支持的方法有 update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
* 方法,其中,update包括新增、修改、删除等方法,query用于查询,其它的基本用不到。
* args:表示拦截的参数类型,有MappedStatement、Object、RowBounds和ResultHandler等等.
* type:表示拦截的类,有Executor、StatementHandler、ParameterHandler和ResultSetHandler。
*
* @author Xin
*/
@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, CacheKey.class, BoundSql.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class MybatisInterceptor implements Interceptor {
/**
* intercept方法就是要进行拦截的时候要执行的方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
ms.getStatementType();
// 当前SQL使用的是哪个Mapper,即哪个Mapper类
String mapper = ms.getResource();
System.out.println("mapper: " + mapper); // file [D:\svn\bidding\target\classes\mapper\AdminMapper.xml]
Configuration configuration = ms.getConfiguration();
// 执行当前SQL的Mapper id,其组成 [ 类型.方法 ]
String mapperID = ms.getId();
System.out.println("SQL的Mapper id: " + mapperID); // com.jiuan.bidding.dao.AdminMapper.changeSalt
// 获取当前执行的SQL使用哪个数据源,我这里的数据源组件使用的是Druid,如果使用c3p0或者其他,则需要查看相关API,一般来降一个项目可能会配多个数据源,但是数据源组件都会使用一个
DruidDataSource dataSource = (DruidDataSource) configuration.getEnvironment().getDataSource();
// 获取数据库的类型[即mysql,或者oracle等等]
String dbType = dataSource.getDataSourceStat().getDbType();
System.out.println("数据库的类型: " + dbType); // mysql
// 存放的是SQL的参数[它是一个实例对象]
Object parameterObject = args[1];
Object target = invocation.getTarget();
StatementHandler handler = configuration.newStatementHandler((Executor) target, ms, parameterObject, RowBounds.DEFAULT, null, null);
System.out.println("SQL的参数: " + handler); // org.apache.ibatis.executor.statement.RoutingStatementHandler@11ac67cc
/**
* commandName.startsWith(增/删/改/查),可以得到crud的具体类型[得到的是大写的INSERT UPDATE]
* method.getName()得到的name可能为update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
*/
String commandName = ms.getSqlCommandType().name();
Method method = invocation.getMethod();
String methodName = method.getName();
System.out.println("增/删/改/查: " + commandName); // UPDATE DELETE
System.out.println("增/删/改/查: " + methodName); // update
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 这个ParameterMapping表示当前SQL绑定的是哪些参数,及参数类型,但并不是参数本身
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 将参数值转成json字符串
String parameterObjects = JSON.toJSONString(boundSql.getParameterObject());
System.out.println("SQL绑定的是哪些参数,及参数类型:" + parameterMappings); //[ParameterMapping{property='salt', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}, ParameterMapping{property='mm', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}, ParameterMapping{property='oldsalt', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}]
System.out.println("参数值json字符串: " + parameterObjects); // {"mm":"f9e2645255983eb44ce90ef10f2dbf51","salt":"2a8b30b479b549ce888569184ce1e553","oldsalt":"21903a20bb66484f9c3cb87bf7f1acdb","param3":"f9e2645255983eb44ce90ef10f2dbf51","param1":"2a8b30b479b549ce888569184ce1e553","param2":"21903a20bb66484f9c3cb87bf7f1acdb"}
// "a3a36e56-4da0-444e-a37c-fc2bd85916e1"
// 要拦截的SQL,通过拦截器的SQL 其不带参数
String srcSQL = boundSql.getSql();
// 返回拼装好参数的SQL
String retSQL = formatSQL(srcSQL, dbType, parameterObjects);
// 先执行当前的SQL方法,即通过当前拦截器的CRUD操作,因为我们要返回这个结果
Object result = invocation.proceed();
System.out.println("sql语句1: " + srcSQL); // UPDATE tb_bid_zyryxx SET SALT = ?, MM = ? WHERE SALT = ?
System.out.println("sql语句2: " + retSQL); // UPDATE tb_bid_zyryxx SET SALT = ?, MM = ? WHERE SALT = ?
System.out.println("result: " + result); // 1
// 组装自己的SQL记录类
BidderLog log = new BidderLog();
// 记录SQL
log.setId(IdUtil.getUUID());
log.setUserid((String) session.getAttribute(Constants.SESSION_ORGAN_ID));
log.setUsername((String) session.getAttribute(Constants.SESSION_ORGAN_NAME));
log.setIp(InetAddress.getLocalHost().getHostAddress());
log.setTime(new Date());
// log.setModel(); 模块
log.setContent(srcSQL + parameterObjects);
bidderLogDao.insert(log);
//记录影响行数
// log.setResult(Integer.valueOf(Integer.parseInt(result.toString())));
// 记录时间
// log.setOperateDate(new Date());
//TODO 还可以记录参数,或者单表id操作时,记录数据操作前的状态
//获取insertSqlLog方法
// ms = ms.getConfiguration().getMappedStatement("insertSqlLog");
//替换当前的参数为新的ms
// args[0] = ms;
//insertSqlLog 方法的参数为 log
args[1] = log;
//执行insertSqlLog方法
// invocation.proceed();
// 返回拦截器拦截的执行结果
return result;
}
/**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法
* 对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,
* 里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。
*/
@Override
public Object plugin(Object o) {
// 只拦截Executor对象,减少目标被代理的次数
if (o instanceof Executor) {
return Plugin.wrap(o, this);
}
return o;
}
/**
* setProperties方法是用于在Mybatis配置文件中指定一些属性的
* 这个方法在Configuration初始化当前的Interceptor时就会执行
*/
@Override
public void setProperties(Properties prop) {
String maxTime = prop.getProperty("maxTime");
String format = prop.getProperty("format");
if (StringUtils.isNotEmpty(maxTime)) {
this.maxTime = Long.parseLong(maxTime);
}
if (StringUtils.isNotEmpty(format)) {
this.format = Boolean.valueOf(format);
}
}
/**
* @describe: 组装SQL
* @params:
* @Author: Kanyun
* @Date: 2018/8/22 10:53
*/
public String formatSQL(String src, String dbType, String params) {
// 要传入的SQLUtils的参数集合,实际上虽然泛型是Object,但其实都是基本数据类型
List<Object> paramList = new ArrayList();
// 有了JSON字符串我们就可以通过正则表达式得到参数了
System.out.println(params);
// 需要注意的是这个SQLUtils是Druid数据源中的一个工具类,因为有现成的拼sql的工具,所以我就不再重复造*了,如果你的项目并没有使用Druid,
// 则需要将这个工具类加入到你的项目中
String retSQL = SQLUtils.format(src, dbType, paramList);
return retSQL;
}
/**
* 获取此方法名的具体 Method
*
* @param clazz class 对象
* @param methodName 方法名
* @return 方法
*/
public Method getMethodRegular(Class<?> clazz, String methodName) {
if (Object.class.equals(clazz)) {
return null;
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method;
}
}
return getMethodRegular(clazz.getSuperclass(), methodName);
}
/**
* 获取sql语句开头部分
*
* @param sql ignore
* @return ignore
*/
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet<>();
set.add(upperCaseSql.indexOf("SELECT "));
set.add(upperCaseSql.indexOf("UPDATE "));
set.add(upperCaseSql.indexOf("INSERT "));
set.add(upperCaseSql.indexOf("DELETE "));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
}
List<Integer> list = new ArrayList<>(set);
list.sort(Comparator.naturalOrder());
return list.get(0);
}
}
涉及知识点:
1、辅助类
Mybatis拦截器实现通用Mapper,注入Mybatis会报错,nullpointException(空指针)
大佬提出,辅助类:
一开始:BidderHelp.getBidderLog() 执行一次调用一次。
/**
* 拦截器 - 辅助类
*
* @author Xin
*/
public class BidderHelp implements InitializingBean {
private static BidderHelp instance = null;
@Autowired
private BidderLogMapper bidderLogDao;
@Override
public void afterPropertiesSet() throws Exception {
BidderHelp.instance = this;
}
public static BidderLogMapper getBidderLog(){
return instance.bidderLogDao;
}
}
优化:服务器启动加载一次,然后不在调用。
/**
* 拦截器 - 辅助类
*/
@Component
public class BidderHelp implements InitializingBean {
public static BidderHelp instance;
@Autowired
public BidderLogMapper bidderLogDao;
@Override
public void afterPropertiesSet() throws Exception {
BidderHelp.instance = this;
}
/**
* @PostConstruct 用于在完成依赖项注入以执行任何初始化之后需要执行的方法。必须在类投入使用 之前调用此方法。
* 初始化bidderLogDao
*/
@PostConstruct
public void initialize() {
instance = this;
instance.bidderLogDao = this.bidderLogDao;
}
}
2、@PostConstruct 注解
java 注解,不是Spring滴!!
Java中该注解的说明:@PostConstruct该注解被用来修饰一个 非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
本文地址:https://blog.csdn.net/weixin_45395031/article/details/107464469
推荐阅读
-
java使用插件pagehelper在mybatis中实现分页查询
-
MyBatis中XML和注解的对比及使用
-
Mysql数据库中的 Order by 语句的特殊之处(select 中的项目不必出现在order by中)---不建议使用!
-
详解Angular.js中$http拦截器的介绍及使用
-
在eclipse中使用spring,springmvc,mybatis搭建web项目
-
详解Spring Boot中MyBatis的使用方法
-
Eclipse中怎么使用Working set管理项目?
-
Maven项目开发中使用Mybatis注解开发
-
使用HTTP-REPL工具测试ASP.NET Core 2.2中的WEB API项目
-
vue中axios解决跨域问题和拦截器的使用方法