Mybatis自定义拦截器,实现拼接sql和修改1

一、应用场景

1.分页,如com.github.pagehelper的分页插件实现;

2.拦截sql做日志监控;

3.统一对某些sql进行统一条件拼接,类似于分页。

二、MyBatis的拦截器简介

       然后我们要知道拦截器拦截什么样的对象,拦截对象的什么行为,什么时候拦截?

       在Mybatis框架中,已经给我们提供了拦截器接口,防止我们改动源码来添加行为实现拦截。说到拦截器,不得不

提一下,拦截器是通过动态代理对Mybatis加入一些自己的行为。

拦截对象

确立拦截对象范围:要拦对人,既要保证拦对人,又要保证对正确的人执行正确的拦截动作

拦截地点

       在Mybatis的源码中:

Executor、StatementHandler

拦截时机

        如果mybatis为我们提供了拦截的功能,我们应该在Mybatis执行过程的哪一步拦截更加合适呢?

        拦截某个对象干某件事的时候,拦截的时机要对,过早的拦截会耽误别人做自己的工作,拦截太晚达不到目的。

        Mybatis实际也是一个JDBC执行的过程,只不过被包装起来了而已,我们要拦截Mybatis的执行,最迟也要在获取

PreparedStatement时拦截:

PreparedStatement statement = conn.prepareStatement(sql.toString());

      在此处偷偷的将sql语句换掉,就可以改变mybatis的执行,加入自己想要的执行行为。

      而获取Mybatis的Statement是在StatementHandler中进行的。

三、代码示例

第一步、引入依赖

<dependency>

    <groupId>org.mybatis.spring.boot</groupId>

    <artifactId>mybatis-spring-boot-starter</artifactId>

    <version>1.3.2</version>

</dependency>

 第二步、配置application.propertities,指定好好映射文件和实体类的目录

### mybatis

mybatis.mapperLocations: classpath:mapping/*.xml 

###classpath就是应用程序resources的路径

mybatis.type-aliases-package: com.pingan.yc.demo.model

第三步、配置代码生成器,引入配置在pom中添加一下代码

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.8</source>

<target>1.8</target>

</configuration>

</plugin>

<!--<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-resources-plugin</artifactId>

<configuration>

<encoding>${encoding}</encoding>

</configuration>

</plugin>-->

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

<!-- mybatis generator 自动生成代码插件 -->

<plugin>

<groupId>org.mybatis.generator</groupId>

<artifactId>mybatis-generator-maven-plugin</artifactId>

<version>1.3.2</version>

<configuration>

<!-- 自动生成代码的配置文件地址 -->

<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>

<verbose>true</verbose>

<overwrite>true</overwrite>

</configuration>

</plugin>

</plugins>

</build>

在resource文件夹下新建generator.xml文件

        内容如下:需要注意配置数据库连接驱动和数据库连接,指定生成实体类和映射文件的路径,最后按格式配置要生成的数据表

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE generatorConfiguration

        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包-->

    <classPathEntry  location="D:\Users\admin\.m2\repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>

    <context id="DB2Tables"  targetRuntime="MyBatis3">

        <commentGenerator>

            <property name="suppressDate" value="true"/>

            <!-- 是否去除自动生成的注释 true:是 : false:否 -->

            <property name="suppressAllComments" value="true"/>

        </commentGenerator>

        <!--数据库链接URL,用户名、密码 -->

        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:4433/standard_policy_db" userId="dev" password="dev">

        </jdbcConnection>

        <javaTypeResolver>

            <property name="forceBigDecimals" value="false"/>

        </javaTypeResolver>

        <!-- 生成模型的包名和位置-->

        <javaModelGenerator targetPackage="com.pingan.yc.policy.model" targetProject="src/main/java">

            <property name="enableSubPackages" value="true"/>

            <property name="trimStrings" value="true"/>

        </javaModelGenerator>

        <!-- 生成映射文件的包名和位置-->

        <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">

            <property name="enableSubPackages" value="true"/>

        </sqlMapGenerator>

        <!-- 生成DAO的包名和位置-->

        <javaClientGenerator type="XMLMAPPER" targetPackage="com.pingan.yc.policy.dao" targetProject="src/main/java">

            <property name="enableSubPackages" value="true"/>

        </javaClientGenerator>

        <!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名-->

        <table tableName="t_user_yc" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"  />

        <table tableName="t_userinfo_yc" domainObjectName="UserInfo" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"  selectByExampleQueryId="true"/>

    </context>

</generatorConfiguration>

最后:在maven命令窗口或如下界面中执行mybatis-generator:generate命令

最后生成如下文件:

第四步、拦截器 

     (生成对应文件后,mybatis环境算是集成好了,可以运行一个测试类试试能否从数据库读取数据。)定义一个类实现Mybatis的Interceptor接口,@Component注解必须要添加,不然可能出现拦截器无效的情况!!!

import org.apache.ibatis.executor.statement.StatementHandler;   

import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.plugin.*;

import org.apache.ibatis.reflection.DefaultReflectorFactory;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.reflection.SystemMetaObject;

import org.springframework.stereotype.Component;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.util.Properties;

@Component

@Intercepts({

        @Signature(

                type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class

        })

})

public class MySqlInterceptor implements Interceptor {

    @Override

    public Object intercept(Invocation invocation) throws Throwable {

        // 方法一

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

        //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        //id为执行的mapper方法的全路径名,如com.uv.dao.UserMapper.insertUser

        String id = mappedStatement.getId();

        //sql语句类型 select、delete、insert、update

        String sqlCommandType = mappedStatement.getSqlCommandType().toString();

        BoundSql boundSql = statementHandler.getBoundSql();

        //获取到原始sql语句

        String sql = boundSql.getSql();

        String mSql = sql;

        //TODO 修改位置

        //注解逻辑判断  添加注解了才拦截

        Class<?> classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf(".")));

        String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());

        for (Method method : classType.getDeclaredMethods()) {

            if (method.isAnnotationPresent(InterceptAnnotation.class) && mName.equals(method.getName())) {

                InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);

                if (interceptorAnnotation.flag()) {

                    mSql = sql + " limit 2";

                }

            }

        }

        //通过反射修改sql语句

        Field field = boundSql.getClass().getDeclaredField("sql");

        field.setAccessible(true);

        field.set(boundSql, mSql);

        return invocation.proceed();

    }

    @Override

    public Object plugin(Object target) {

        if (target instanceof StatementHandler) {

            return Plugin.wrap(target, this);

        } else {

            return target;

        }

    }

        @Override

    public void setProperties(Properties properties) {

    }

}

此外,定义了一个方法层面的注解,实现局部指定拦截

@Target({ElementType.METHOD,ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

public @interface InterceptAnnotation {

    boolean flag() default  true;

}

最后只需要在指定的方法出添加注解就可以实现局部拦截

最后运行看结果就好了。(按我的逻辑下来会只查到2条数据)

在@Interceptor的注解中也有以下的注解方式,究竟有什么不同和差异,请大家自己研究咯,我就在此抛砖引玉了,请各位大牛指导了。

@Intercepts(value = {

        @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 = Executor.class,

                method = "query",

                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266