Z次元
文章 笔记 日志
专题
专栏分类 文章归档
友链
友情链接 朋友圈
交流
留言 关于我
头像
系列文章
-
-
-
-
MyBatis拦截器
系列文章
知识积累
最后更新:2024/12/15|创建时间:2022/4/23

这篇文章主要介绍了MyBatis拦截器的基本使用。文章还展示了拦截器的注册方法,包括通过XML配置和使用配置类。示例代码演示了如何创建拦截器类、拦截特定方法、修改SQL语句等高级用法。整体内容涵盖了MyBatis拦截器从基础到进阶的应用。

前言

Mybatis拦截器注解的基本写法格式为:

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})

说明:
@Intercepts:标识该类是一个拦截器
@Signature:拦截器相关属性设置

type

拦截器的类型,总共有4种,按照执行的先后顺序为:

  1. Executor:拦截执行器的方法。
  2. ParameterHandler:拦截参数的处理。
  3. ResultHandler:拦截结果集的处理。
  4. StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器。

图片

method

可被拦截的方法,按照拦截器的不同类型,总共有下面这些方法:

拦截的类型 拦截的方法
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

args

设置对应接口中的哪一个方法,比如 Executor 中 query方法因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法。

这里的方法和上面的方法的要怎么理解呢?可以这样理解,上面的那个方法(method)是大类的方法,比如update方法,下面的这个方法(args)呢,是具体的方法,用来指明你到底的哪里的update方法。

注册拦截器

使用配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--其它配置项都在著配置文件配置了,所以这里只需要配置configuration项即可-->
<configuration>
    <settings>
        <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <plugins>
        <plugin interceptor="com.example.IbatisInterceptor"/>
    </plugins>
</configuration>

然后在主配置文件中,将mybatis配置文件引入:

# Mybatis配置
mybatis:
  config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了

todo:
mybatis的配置项中,有一个interceptors配置,其参数是一个列表(java.util.List)。通过查阅相关信息发现,不能在配置文件中引用自定义的拦截器。总之是暂时没找到 具体使用方法,先放着吧。

# Mybatis配置
mybatis:
#  config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了
  mapper-locations: classpath:mybatis/*.xml # mybatis映射文件
  configuration: # mybatis全局配置
    map-underscore-to-camel-case: true #开启驼峰命名规则
    interceptors:
      - xxx
      - xxxxx

使用配置类

import com.example.IbatisInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;


@Configuration
public class IbatisInterceptorConfig {

    @Bean
    public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
        IbatisInterceptor ibatisInterceptor = new IbatisInterceptor();
        Properties properties = new Properties();
        properties.setProperty("driver", "mysql");
        properties.setProperty("username", "root");
        properties.setProperty("password", "123456");

        // 给拦截器添加自定义参数
        ibatisInterceptor.setProperties(properties);
        sqlSessionFactory.getConfiguration().addInterceptor(ibatisInterceptor);
        return "interceptor";
    }

}

使用注解

直接在拦截器类上面使用@Component注解即可(本文中就是直接使用的注解)

使用拦截器

使用mybatis拦截器,需要实现Interceptor接口的三个方法:

  1. intercept():当方法被拦截时调用,用于设置拦截后需要执行的业务逻辑,自定义拦截器时,此方法是必须实现的。
  2. plugin():在mybatis加载配置时被调用。设置是否需要对对象进行拦截
  • 拦截对象:调用wrap()方法,返回一个代理对象,并执行intercept()方法
  • 不拦截对象:直接返回目标原本对象,不会执行intercept()方法
  1. setProperties():在mybatis加载配置时被调用。获取并设置mybatis配置文件或配置类中的property属性的值,配置类见上面注册拦截器时的演示

mybatis配置文件演示:

   <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--mybatis配置项,上面的setProperties()方法中获取的propertys就是这里的-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

原理图:
图片

简单使用

以拦截执行器方法为例。
示例:拦截更新操作,在更新操作时统一添加 “更新时间 ”字段,同时打印当前sql语句。

从语义角度分析:
insert、delete、update都是属于更新(update)操作;
而select属于查询(query)操作。


import com.example.bean.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Date;
import java.time.LocalDateTime;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;

        for (Object object : args) {

            // 从MappedStatement中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement mappedStatement = (MappedStatement) object;
                sqlCommandType = mappedStatement.getSqlCommandType();

                // 获取当前擦操作的sql语句
                BoundSql boundSql = mappedStatement.getBoundSql(object);
                String sql = boundSql.getSql();

                // 打印当前操作类型及当前操作的sql语句
                logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);
            }
            // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
            if (object instanceof User) {
                if (SqlCommandType.UPDATE == sqlCommandType) {
                    // 由于没有写额外的获取实体类属性的方法,所以这里只能利用反射机制,获取需要修改的字段对应的实体类属性
                    Field updateTime = object.getClass().getDeclaredField("updateTime");
                    updateTime.setAccessible(true);
                    // 对数据进行修改
                    updateTime.set(object, Date.valueOf(LocalDateTime.now().toLocalDate()));
                }

            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //  拦截属于Executor的对象
        if (target instanceof Executor) {
            // 返回代理
            return Plugin.wrap(target, this);
        }
        //  否则返回对象本身
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

拦截多个请求

示例:只做简单示例,不涉及业务逻辑


import com.example.bean.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.sql.PreparedStatement;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class,method = "query", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class}),
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),

})
public class IbatisInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;

        for (Object object : args) {

            // 从MappedStatement中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement mappedStatement = (MappedStatement) object;
                sqlCommandType = mappedStatement.getSqlCommandType();

                // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
                if (object instanceof User) {
                    if (SqlCommandType.UPDATE == sqlCommandType) {
//                   执行更新操作修改
                    } else if (SqlCommandType.SELECT == sqlCommandType) {
//                    执行查询操作修改
                    } else if (SqlCommandType.DELETE == sqlCommandType) {
//                    执行删除操作修改
                    } else if (SqlCommandType.INSERT == sqlCommandType) {
//                    执行插入操作修改
                    }
                }
            }else if(object instanceof PreparedStatement){
                // 从PreparedStatement中获取到操作类型
                PreparedStatement preparedStatement =  (PreparedStatement) object;
//                doSomething
            }
            // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
                if (object instanceof User) {
                    if (SqlCommandType.UPDATE == sqlCommandType) {
//                   执行更新操作修改
                    } else if (SqlCommandType.SELECT == sqlCommandType) {
//                    执行查询操作修改
                    } else if (SqlCommandType.DELETE == sqlCommandType) {
//                    执行删除操作修改
                    } else if (SqlCommandType.INSERT == sqlCommandType) {
//                    执行插入操作修改
                    }
                }
        }
        return invocation.proceed();
    }
  
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }
  
    @Override
    public void setProperties(Properties properties) {
//        String username = properties.getProperty("username");
//        String password = properties.getProperty("password");
    }
}

拦截并修改sql语句

从上面的示例中我们能够发现,是可以在拦截器中获取到sql语句的,那么我们就可以通过反射修改sql语句。
将通过id删除数据的sql语句改为通过name删除的sql语句:
有两种方法:
第一种是通过Statement修改

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;

        for (Object object : args) {

            // 从MappedStatement中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement mappedStatement = (MappedStatement) object;
                sqlCommandType = mappedStatement.getSqlCommandType();

                // 获取当前擦操作的sql语句
                BoundSql boundSql = mappedStatement.getBoundSql(object);
                String sql = boundSql.getSql();

                // 打印当前操作类型及当前操作的sql语句
                logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);

                // 设置新的sql语句
                String newSql = "delete from users where u_name=?";
            
                /*
                * 通过statement修改sql语句
                */
    
                // 重新new一个查询语句对像
                BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

                // 把新的查询放到statement里
                MappedStatement neMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
                for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                    String prop = mapping.getProperty();
                    if (boundSql.hasAdditionalParameter(prop)) {
                        newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                    }
                }

                final Object[] queryArgs = invocation.getArgs();
                // 这个i就是设置你需要修改哪个拦截的请求,对应上面的@Intercepts注解中的数组中的元素(@Signature注解)对应的索引
                // 因为我上面只有一个(@Signature注解的)元素,对应的索引自然也就是0
                int i = 0;
                queryArgs[i] = neMappedStatement;
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //  拦截属于Executor的对象
        if (target instanceof Executor) {
            // 返回代理
            return Plugin.wrap(target, this);
        }
        //  否则返回对象本身
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}


第二种是直接通过反射修改:


import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Properties;


@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class IbatisInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;

        for (Object object : args) {

            // 从MappedStatement中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement mappedStatement = (MappedStatement) object;
                sqlCommandType = mappedStatement.getSqlCommandType();

                // 获取当前擦操作的sql语句
                BoundSql boundSql = mappedStatement.getBoundSql(object);
                String sql = boundSql.getSql();

                // 打印当前操作类型及当前操作的sql语句
                logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);

                // 设置新的sql语句
                String newSql = "delete from users where u_name=?";

                // 通过反射修改sql语句
                Field field = boundSql.getClass().getDeclaredField("sql");
                field.setAccessible(true);
                field.set(boundSql, newSql);
            
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //  拦截属于Executor的对象
        if (target instanceof Executor) {
            // 返回代理
            return Plugin.wrap(target, this);
        }
        //  否则返回对象本身
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

最后来个图吧:
图片

版权声明
本文依据 CC-BY-NC-SA 4.0 许可协议授权,请您在转载时注明文章来源为 Z次元 ,若本文涉及转载第三方内容,请您一同注明。
更多专栏文章推荐
知识积累
人非生而知之者,孰能无惑?惑而不从师,其为惑也,终不解矣。
SpringBoot项目统一异常处理
2022/4/20
Java Stream流
2022/4/15
JDK新版特性(18-21)
2025/2/7
JDK新版特性(11-17)
2025/1/30
评论区
发表评论

这里还没有评论哦

快来发一条评论抢占前排吧

前言
type
method
args
注册拦截器
使用配置文件
使用配置类
使用注解
使用拦截器
简单使用
拦截多个请求
拦截并修改sql语句
目录
前言
type
method
args
注册拦截器
使用配置文件
使用配置类
使用注解
使用拦截器
简单使用
拦截多个请求
拦截并修改sql语句
十玖八柒
每天进步多一点
欢迎到访φ(゜▽゜*)♪
最新评论
永恒末匕:

好哇塞,这个厉害

十玖八柒:

测试图片发送

Corwin: @十玖八柒

哎 主要是我的个人网站用的是静态的cos 实现评论框还是有点困难

十玖八柒: @Corwin

评论模块是自己写的,富文本编辑器是封装的Tiptap(这个编辑器非常强大,你可以去看下)

我的
个人主页
站点地图
RSS订阅
导航
十年之约
虫洞穿梭
全站友链
虚位以待
©2020 - 2025 By 十玖八柒 版权所有
豫ICP备20021466号