欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

MyBatis实现动态SQL更新的代码示例,

来源: javaer 分享于  点击 21138 次 点评:13

MyBatis实现动态SQL更新的代码示例,


目录
  • 简介
  • 拦截器介绍
  • 实现拦截器
  • 注册拦截器
  • 代码示例
    • yml 配置
    • Dao 层代码
    • mapper 文件
    • 拦截器核心代码
    • 代码测试
  • 拦截器应用场景
    • 总结

      本文示例代码全部在 Spring Boot3.0、Mybatis Plus3.5.3.1 版本下运行。

      简介

      MyBatis 是一个流行的 Java 持久层框架,它提供了灵活的 SQL 映射和执行功能。有时候我们可能需要在运行时动态地修改 SQL 语句,例如添加一些条件、排序、分页等。MyBatis 提供了一个强大的机制来实现这个需求,那就是拦截器(Interceptor)。

      拦截器介绍

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

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

      实现拦截器

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

      注册拦截器

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

      所以我们只需要定义一个 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) {
              // 获取配置文件中的属性值
          }
      }

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

      • 通过 invocation 参数获取 statementHandler 对象,也就是包含拼接后 SQL 语句的对象。
      • 获取 metaObject 对象, MetaObject 是 MyBatis 提供的一个反射帮助类,可以优雅访问对象的属性,这里是访问 statementHandler 对象进行反射处理。
      • 通过 metaObject 反射获取 statementHandler 对象的成员变量 mappedStatement。
      • 通过 mappedStatement 对象的 id 方法获取到 Dao 层类的全限定名称,然后反射获取 Dao 层类的 Class 对象。
      • 获取包含原始 SQL 语句的 BoundSql 对象。
      • 遍历 Dao 层类的方法。
      • 判断方法上是否有 DynamicSql 注解,有的话就进行时间条件替换
      • 对 BoundSql 对象通过反射修改 SQL 语句。
      • 执行修改后的 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 中条件或者特殊字符进行逻辑替换。(也是本文的应用场景)

      总结

      到此本文讲解的 MyBatis 实现动态 SQL 内容就讲解完毕了,希望大家喜欢。

      以上就是MyBatis实现动态SQL更新的代码示例的详细内容,更多关于MyBatis动态SQL更新的资料请关注3672js教程其它相关文章!

      您可能感兴趣的文章:
      • 详解MyBatis的动态SQL实现原理
      • MyBatis注解实现动态SQL问题
      • Mybatis实现动态SQL编写的示例详解
      • MyBatis动态sql查询及多参数查询方式
      • SQL数据分表Mybatis Plus动态表名优方案
      相关栏目:

      用户点评