当前位置: 移动技术网 > IT编程>开发语言>Java > MyBatis 拦截器 - 项目中使用

MyBatis 拦截器 - 项目中使用

2020年07月22日  | 移动技术网IT编程  | 我要评论

一、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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网