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

2020-06-17  本文已影响0人  洛神灬殇

一、应用场景

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})})

上一篇 下一篇

猜你喜欢

热点阅读