美文网首页
Mybatis自定义拦截器,实现拼接sql和修改1

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

作者: 洛神灬殇 | 来源:发表于2020-06-17 13:31 被阅读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})})

    相关文章

      网友评论

          本文标题:Mybatis自定义拦截器,实现拼接sql和修改1

          本文链接:https://www.haomeiwen.com/subject/rkxsxktx.html