首页 > 数据库 >MyBatis-Plus这样实现动态SQL

MyBatis-Plus这样实现动态SQL

时间:2023-07-26 10:22:07浏览次数:31  
标签:语句 拦截器 对象 sql Plus SQL MyBatis class

拦截器介绍

拦截器是一种基于 AOP(面向切面编程)的技术,它可以在目标对象的方法执行前后插入自定义的逻辑。MyBatis 定义了四种类型的拦截器,分别是:

  • Executor:拦截执行器的方法,例如 update、query、commit、rollback 等。可以用来实现缓存、事务、分页等功能。
  • ParameterHandler:拦截参数处理器的方法,例如 setParameters 等。可以用来转换或加密参数等功能。
  • ResultSetHandler:拦截结果集处理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用来转换或过滤结果集等功能。
  • StatementHandler:拦截语句处理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用来修改 SQL 语句、添加参数、记录日志等功能。

实现拦截器

  1. 定义一个实现 org.apache.ibatis.plugin.Interceptor 接口的拦截器类,并重写其中的 intercept、plugin 和 setProperties 方法。
  2. 添加 @Intercepts 注解,写上需要拦截的对象和方法,以及方法参数,例如 @Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})}),表示在 SQL 执行之前进行拦截处理。

注册拦截器

Spring Boot 项目中集成了 Mybatis Plus 后要让拦截器生效很简单,Mybatis Plus 的自动配置类会读取项目中所有注册到 Spring 容器的拦截器并进行自动注册。如下图,

图片MybatisPlusAutoConfiguration图片注册拦截器

所以我们只需要定义一个 DynamicSqlInterceptor 拦截器并加上 @Component 注解就行,代码如下,

@Component
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {
 ...
}

代码示例

yml 配置

指定 xml 文件中需要替换的占位符标识:@dynamicSql 以及待替换日期条件。

# 动态sql配置
dynamicSql:
  placeholder: "@dynamicSql"
  date: "2023-07-10 20:10:30"

Dao 层代码

在需要进行 SQL 占位符替换的方法上加 @DynamicSql 注解。

public interface DynamicSqlMapper {
    @DynamicSql
    Long count();
}

mapper 文件

将日期条件改成占位符 where create_time > @dynamicSql

<mapper namespace="ltd.newbee.mall.core.dao.DynamicSqlMapper">
    <select id="count" resultType="java.lang.Long">
        select count(1) from member
        where create_time > @dynamicSql
    </select>
</mapper>

拦截器核心代码

@Component
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, 
                method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {

    @Value("${dynamicSql.placeholder}")
    private String placeholder;

    @Value("${dynamicSql.date}")
    private  String dynamicDate;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 获取 StatementHandler 对象也就是执行语句
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 2. MetaObject 是 MyBatis 提供的一个反射帮助类,可以优雅访问对象的属性,这里是对 statementHandler 对象进行反射处理,
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                        new DefaultReflectorFactory());
        // 3. 通过 metaObject 反射获取 statementHandler 对象的成员变量 mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // mappedStatement 对象的 id 方法返回执行的 mapper 方法的全路径名,如ltd.newbee.mall.core.dao.UserMapper.insertUser
        String id = mappedStatement.getId();
        // 4. 通过 id 获取到 Dao 层类的全限定名称,然后反射获取 Class 对象
        Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
        // 5. 获取包含原始 sql 语句的 BoundSql 对象
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        log.info("替换前---sql:{}", sql);
        // 拦截方法
        String mSql = null;
        // 6. 遍历 Dao 层类的方法
        for (Method method : classType.getMethods()) {
            // 7. 判断方法上是否有 DynamicSql 注解,有的话,就认为需要进行 sql 替换
            if (method.isAnnotationPresent(DynamicSql.class)) {
                mSql = sql.replaceAll(placeholder, String.format("'%s'", dynamicDate));
                break;
            }
        }
        if (StringUtils.isNotBlank(mSql)) {
            log.info("替换后---mSql:{}", mSql);
            // 8. 对 BoundSql 对象通过反射修改 SQL 语句。
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, mSql);
        }
        // 9. 执行修改后的 SQL 语句。
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 使用 Plugin.wrap 方法生成代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 获取配置文件中的属性值
    }
}

现在我们对拦截器核心代码逻辑进行讲解:

  1. 通过 invocation 参数获取 statementHandler 对象,也就是包含拼接后 SQL 语句的对象。
  2. 获取 metaObject 对象, MetaObject 是 MyBatis 提供的一个反射帮助类,可以优雅访问对象的属性,这里是访问 statementHandler 对象进行反射处理。
  3. 通过 metaObject 反射获取 statementHandler 对象的成员变量 mappedStatement。
  4. 通过 mappedStatement 对象的 id 方法获取到 Dao 层类的全限定名称,然后反射获取 Dao 层类的 Class 对象。
  5. 获取包含原始 SQL 语句的 BoundSql 对象。
  6. 遍历 Dao 层类的方法。
  7. 判断方法上是否有 DynamicSql 注解,有的话就进行时间条件替换
  8. 对 BoundSql 对象通过反射修改 SQL 语句。
  9. 执行修改后的 SQL 语句。

代码测试

// 测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class DynamicTest {

    @Autowired
    private DynamicSqlMapper dynamicSqlMapper;

    @Test
    public void test() {
        Long count = dynamicSqlMapper.count();
        Assert.notNull(count, "count不能为null");
    }
}

执行结果:

2023-07-11 22:13:33.375 [main] INFO  l.n.m.config.DynamicSqlInterceptor - [intercept,52] - 替换前---sql:select count(1) from member
        where create_time > @dynamicSql
2023-07-11 22:13:33.376 [main] INFO l.n.m.config.DynamicSqlInterceptor - [intercept,62] - 替换后---mSql:select count(1) from member
        where create_time > '2023-07-10 20:10:30'

拦截器应用场景

  • SQL 语句执行监控:可以拦截执行的 SQL 方法,打印执行的 SQL 语句、参数等信息,并且还能够记录执行的总耗时,可供后期的 SQL 分析时使用。
  • SQL 分页查询:MyBatis 中使用的 RowBounds 使用的内存分页,在分页前会查询所有符合条件的数据,在数据量大的情况下性能较差。通过拦截器,可以在查询前修改 SQL 语句,提前加上需要的分页参数。
  • 公共字段的赋值:在数据库中通常会有 createTime , updateTime 等公共字段,这类字段可以通过拦截统一对参数进行的赋值,从而省去手工通过 set 方法赋值的繁琐过程。
  • 数据权限过滤:在很多系统中,不同的用户可能拥有不同的数据访问权限,例如在多租户的系统中,要做到租户间的数据隔离,每个租户只能访问到自己的数据,通过拦截器改写 SQL 语句及参数,能够实现对数据的自动过滤。
  • SQL 语句替换:对 SQL 中条件或者特殊字符进行逻辑替换。(也是本文的应用场景)

总结

标签:语句,拦截器,对象,sql,Plus,SQL,MyBatis,class
From: https://www.cnblogs.com/privateLogs/p/17581749.html

相关文章

  • How to make sqlplus output appear in one line
    Howtomakesqlplusoutputappearinonelinehttps://dba.stackexchange.com/questions/54149/how-to-make-sqlplus-output-appear-in-one-line#SQL*PlusUser'sGuideandReferencehttp://docs.oracle.com/cd/E16655_01/server.121/e18404/ch_twelve040.htm#BAC......
  • Using PL/SQL Object Types for JSON
    #https://docs.oracle.com/en/database/oracle/oracle-database/12.2/adjsn/using-PLSQL-object-types-for-JSON.html#GUID-F0561593-D0B9-44EA-9C8C-ACB6AA9474EEDECLAREjeJSON_ELEMENT_T;joJSON_OBJECT_T;BEGINje:=JSON_ELEMENT_T.parse('{"name......
  • 万字长文浅析配置对MySQL服务器的影响
    有很多的服务器选项会影响这MySQL服务器的性能,比如内存中临时表的大小、排序缓冲区等。有些针对特定存储引擎(如InnoDB)的选项,也会对查询优化很有用。调整服务器的配置从某种程度来说是一个影响全局的行为,因为每个修改都可能对该服务器上的每个查询造成影响。不过有些选项是针对特......
  • 从另一电脑复制下来的MYSQL的数据文件(包括FRM IBD)快速恢复到另一MYSQL服务器过程
    从另一电脑复制下来的MYSQL的数据文件(包括FRMIBD)快速恢复到另一MYSQL服务器过程:1.安装mysql最好相同的版本,安装Navicateformysql,连接相应的服务器2.安装mysql-utilities,地址:https://downloads.mysql.com/archives/utilities/以恢复td_gov_company_abnormal.frm为例:3.C......
  • MyBatisPlus
    【狂神说Java】MyBatisPlus最新完整教程通俗易懂:https://www.bilibili.com/video/BV17E411N7KN/快速入门使用第三方组件:1、导入对应的依赖2、研究依赖如何配置3、代码如何编写4、提高扩展技术能力【代码演示】pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxm......
  • MySQL group by分组后,将每组所得到的id拼接起来
    背景需要将商品表中的sku按照spu_id分组后,并且得到每个spu下的sku_id,需要使用到group_concat函数selectspu_id,count(*),group_concat(idSEPARATOR',')asidsfromproduct_skuwherecategory='tv'groupbyspu_id;group_concat函数group_concat函数,实现分组查......
  • MySQL查询阻塞该如何解决
    MySQL是广泛使用的开源数据库管理系统,它提供了方便的查询功能。然而,在高并发访问的情况下,可能出现查询阻塞的情况。下面是一些解决此问题的方法。SHOWFULLPROCESSLIST;可以使用上述命令查看所有正在执行的SQL查询,并查看它们是否阻塞其他查询。如果有查询阻塞了其他查询,可以使......
  • Mysql主从复制
    介绍MySQL主从复制时一个异步的复制过程,底层时基于MySQL数据库自带的二进制日志功能。就是一台或者多台MySQL数据库(slave从库)从另一台MySQL(master主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制时MySQL数据库自带功能,无......
  • JDBC preparedStatement.executeQuery() 与 preparedStatement.executeQuery(sql)
    preparedStatement.executeQuery()这个方法是执行带占位符、已经预编译的sql命令而它--->preparedStatement.executeQuery(sql)这个方法是执行未预编译、完整的sql命令,而不是预编译的sql命令preparedStatement不是应该执行预编译的sql吗?是这样的,但是preparedStatement还兼......
  • Asp.Net 使用Log4Net (SQL Server)
    Asp.Net使用Log4Net(SQLServer)1.创建数据库表首先,在你的SQLServer数据库中创建一个用于存储日志的表。以下是一个简单的表结构示例:CREATETABLE[dbo].[Logs]([Id][INT]IDENTITY(1,1)PRIMARYKEY,[Date][DATETIME]NOTNULL,[Thread][VARCHAR](255)......