自定义MyBatis拦截器更改表名

by emanjusaka from ​ https://www.emanjusaka.top/archives/10 彼岸花开可奈何

本文欢迎分享与聚合,全文转载请留下原文地址。

自定义MyBatis拦截器可以在方法执行前后插入自己的逻辑,这非常有利于扩展和定制 MyBatis 的功能。本篇文章实现自定义一个拦截器去改变要插入或者查询的数据源。

@Intercepts

@Intercepts是Mybatis的一个注解,它的主要作用是标识一个类为拦截器。该注解通过一个@Signature注解(即拦截点),来指定拦截那个对象里面的某个方法。

具体来说,@Signature注解的属性type用于指定拦截器类型,可能的值包括:

  • Executor(sql的内部执行器)
  • ParameterHandler(拦截参数的处理)
  • StatementHandler(拦截sql的构建)
  • ResultSetHandler(拦截结果的处理)。

method属性表示在指定的拦截器类型中要拦截的方法

args属性表示拦截的方法对应的参数

实现步骤

  1. 实现org.apache.ibatis.plugin.Interceptor接口,重写一下的方法:

  2. 添加拦截器注解,@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})

  3. 配置文件中添加拦截器

    注意需要在 Spring Boot 的 application.yml 文件中配置 mybatis 配置文件的路径。

    mybatis拦截器目前不支持在application.yml配置文件中通过属性配置,目前只支持通过xml配置或者代码配置。

代码实现

Mybatis拦截器:

java 复制代码
package top.emanjusaka.springboottest.mybatis.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import top.emanjusaka.springboottest.mybatis.annotation.DBTableStrategy;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author emanjusaka
 * @Date 2023/10/18 17:25
 * @Version 1.0
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicMybatisPlugin implements Interceptor {
    private Pattern pattern = Pattern.compile("(from|into|update)[\\s]{1,}(\\w{1,})", Pattern.CASE_INSENSITIVE);

    @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());
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 获取自定义注解判断是否进行分表操作
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        Class<?> clazz = Class.forName(className);
        DBTableStrategy dbTableStrategy = clazz.getAnnotation(DBTableStrategy.class);
        if (null == dbTableStrategy || !dbTableStrategy.changeTable() || null == dbTableStrategy.tbIdx()) {
            return invocation.proceed();
        }
        // 获取SQL
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        // 替换SQL表名
        Matcher matcher = pattern.matcher(sql);
        String tableName = null;
        if (matcher.find()) {
            tableName = matcher.group().trim();
        }
        assert null != tableName;
        String replaceSql = matcher.replaceAll(tableName + "_" + dbTableStrategy.tbIdx());
        // 通过反射修改SQL语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, replaceSql);
        field.setAccessible(false);
        return invocation.proceed();
    }
}

mapper的xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.emanjusaka.springboottest.score.repository.IScoreRepository">
    <select id="selectAll" resultType="top.emanjusaka.springboottest.score.model.vo.ScoreVO">
        select * from score
    </select>
</mapper>

切换表名的注解:

java 复制代码
package top.emanjusaka.springboottest.score.repository;

import org.apache.ibatis.annotations.Mapper;
import top.emanjusaka.springboottest.mybatis.annotation.DBTableStrategy;
import top.emanjusaka.springboottest.score.model.vo.ScoreVO;

import java.util.List;

/**
 * @Author emanjusaka
 * @Date 2023/10/18 17:45
 * @Version 1.0
 */
@Mapper
@DBTableStrategy(changeTable = true,tbIdx = "2")
public interface IScoreRepository {
    List<ScoreVO> selectAll();
}

测试代码:

java 复制代码
package top.emanjusaka.springboottest;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import top.emanjusaka.springboottest.score.model.vo.ScoreVO;
import top.emanjusaka.springboottest.score.service.IScore;

import javax.annotation.Resource;
import java.util.List;

@SpringBootTest
class SpringBootTestApplicationTests {
    @Resource
    private IScore score;
    @Test
    void contextLoads() {
        List<ScoreVO> list = score.selectAll();
        list.forEach(System.out::println);
    }

}

运行结果

通过上图可以看出,现在表名已经修改成了score_2了。通过这种机制,我们可以应用到自动分表中。本文的表名的索引是通过注解参数传递的,实际应用中需要通过哈希散列计算。

本文原创,才疏学浅,如有纰漏,欢迎指正。如果本文对您有所帮助,欢迎点赞,并期待您的反馈交流,共同成长。

原文地址: https://www.emanjusaka.top/archives/10

微信公众号:emanjusaka的编程栈

相关推荐
gechunlian8814 分钟前
Spring Boot中的404错误:原因、影响及处理策略
java·spring boot·后端
岁岁种桃花儿25 分钟前
AI超级智能开发系列从入门到上天第四篇:AI应用方案设计
java·服务器·开发语言
架构师沉默1 小时前
Java 终于有自己的 AI Agent 框架了?
java·后端·架构
程序员爱酸奶1 小时前
ThreadLocal内存泄漏深度解析
java
givemeacar1 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis
czlczl200209251 小时前
JVM创建对象过程
java·开发语言
一直都在5721 小时前
线程间的通信
java·jvm
GIOTTO情2 小时前
Infoseek危机公关全链路技术解析:基于近期热点舆情的落地实践
java
我是人✓2 小时前
从零入门 Servlet:JavaWeb 核心组件的实操与理解
java·servlet
lay_liu2 小时前
Spring Boot 自动配置
java·spring boot·后端