MyBatis 拦截器 - 项目中使用

MyBatis 拦截器 - 项目中使用

      • 一、MyBatis 拦截器介绍
      • 二、自定义拦截器类
          • 2.1、Interceptor接口
          • 2.2、@Intercepts注解
      • 三、项目使用
          • 3.1、jar
          • 3.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 {

    /**
     * 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理;
     * 
     * 执行拦截内容的地方,比如想收点保护费。由plugin()⽅法触发,interceptor.plugin(target) 以证明。
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 决定是否触发intercept()⽅法。
     * 
     * plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
     *
     * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
     * 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
     * 
     * Plugin类是插件的核心类,给target创建一个JDK的动态代理对象,触发intercept()⽅法
     */
    Object plugin(Object target);

    /**
     * 给自定义的拦截器传递xml配置的属性参数。
     * 
     * 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
     */
    void setProperties(Properties properties);

}
2.2、@Intercepts注解

Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。

@Intercepts注解:装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截
多个方法,自然就是多个@Signature列表。

解释:要拦截Executor接⼝内的query()⽅法,参数类型为args列表。MyBatis 拦截器 - 项目中使用_第1张图片
Signature来指定咱们需要拦截那个类对象的哪个方法。
MyBatis 拦截器 - 项目中使用_第2张图片
自定义一个MybatisInterceptor类,来拦截Executor类里面的update。
在这里插入图片描述

三、项目使用

3.1、jar
		
        
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plusartifactId>
            <version>3.1.2version>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-extensionartifactId>
            <version>3.1.2version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>  
            <artifactId>lombokartifactId>   
            <version>1.16.18version>     
            <scope>providedscope>
        dependency>
3.2、xml配置
	
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        
		<property name="mapperLocations" value="classpath:mapper/*.xml" />
        <property name="plugins">
        	<array>
        		
				<bean class="com.github.pagehelper.PageHelper">
					<property name="properties">
						<value>dialect=mysqlvalue>
					property>
				bean>
        	 	
				
                
                <bean id="mybatisInterceptor"
                       class="com.common.MybatisInterceptor"/>
            array>
        property>
    bean>
代码
/**
 * 参考文献:http://www.yangxuwang.com/jingyan/1533818219451005
 * 

* 定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回 * 一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。 *

* 对于实现自己的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(注释的方法)
MyBatis 拦截器 - 项目中使用_第3张图片

你可能感兴趣的:(Mybatis,持久层框架)