自定義MyBatis攔截器更改表名

2023-10-23 15:02:43

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攔截器:

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

切換表名的註解:

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

測試程式碼:

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的程式設計棧