通過Mybatis插件修改SQL


通過Mybatis插件修改SQL

前言:在PostgresSQL數據庫中,比MySQL多一個Schema的功能,相當於是數據庫下面又分了一層,一個庫里面可以有多個schema,不同schema下面可以由名字相同的表。如果需要全局修改schema就可以使用Mybatis插件的形式來實現同一套SQL去查詢不同的表

由於沒有安裝PostgresSQL,本文僅展示使用插件修改SQL

前置:

User表、DTO、mapper對應的xml

一張測試表:

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(20) NOT NULL COMMENT '名字',
  PRIMARY KEY (`id`)
)

對應DTO:

@Data
public class User {
    private Integer id;
    private String name;
}

UserMapper:

@Mapper
public interface UserMapper {
    public List<User> selectAll();

    public User selectById(@Param("id") Integer id);
}

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="fun.psgame.springbootlearn.mapper.UserMapper2">
    <select id="selectAll" resultType="User">
        <!-- 這里本來應該使用user,但是作為演示使用${table},類似${schema}.user的用法 -->
        select id, name from ${table}
    </select>

    <select id="selectById" resultType="User">
        select id, name from ${table} where id = #{id}
    </select>
</mapper>

可以看到mapper.xml的sql里並沒有使用表名,如果是在postgresSQL里可以不寫死schema的名字,都在mybatis的插件中進行修改

首先在application.yml中定義一下數據庫連接和mybatis的配置文件:

spring配置文件
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/springboot_learn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: sql/*.xml
  config-location: classpath:config/mybatis-config.xml

logging:
  level:
    fun.psgame.springbootlearn: DEBUG

然后是Mybatis配置文件:

mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 插件會導致主鍵回寫失效,暫時不清楚原因 -->
        <setting name="useGeneratedKeys" value="true"/>
    </settings>
    <typeAliases>
        <!-- 配置別名 -->
        <package name="fun.psgame.springbootlearn.dto"/>
    </typeAliases>

    <plugins>
        <!-- 查詢插件1 -->
        <plugin interceptor="fun.psgame.springbootlearn.common.mybatis.QueryParamPlugin">
            <!-- 測試用屬性 -->
            <property name="someProperty" value="100"/>
        </plugin>
        <!-- 查詢插件2 -->
        <plugin interceptor="fun.psgame.springbootlearn.common.mybatis.Query2ParamPlugin">
        </plugin>
        <!-- 插入、更新插件 -->
        <plugin interceptor="fun.psgame.springbootlearn.common.mybatis.UpdateParamPlugin"/>
    </plugins>
</configuration>

創建一個插件基類:


public class MybatisPluginBase {
    // 這是指定攔截位置的注解@Signature中的args的序號
    public static final int PARAMETER_INDEX_MAPPED_STATEMENT = 0;
    public static final int PARAMETER_INDEX_PARAMETER = 1;
    public static final int PARAMETER_INDEX_ROW_BOUNDS = 2;
    public static final int PARAMETER_INDEX_RESULT_HANDLER = 3;
    public static final int PARAMETER_INDEX_CACHE_KEY = 4;
    public static final int PARAMETER_INDEX_BOUND_SQL = 5;

    public Map<String, Object> createSessionParameterMap() {
        Map<String, Object> parameterMap = new HashMap<>();
        // 將table替換為user,如果是postgreSQL則將命名空間替換為session中的schema
        parameterMap.putIfAbsent("table", "user");

        return parameterMap;
    }

    /**
     * 創建參數替換的結果
     *
     * @param args 攔截方法的參數屬性
     * @return 參數替換后的數據,主要是輕重的sqlMap屬性
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public SqlParameterResult createSqlParameterResult(Object[] args) {
        SqlParameterResult result = new SqlParameterResult();

        Map parameterMap = new HashMap<String, Object>();
        // 當執行的mapper接口方法有參數時
        if (args[PARAMETER_INDEX_PARAMETER] != null) {
            // 如果是多個值會存儲在map中
            if (args[PARAMETER_INDEX_PARAMETER] instanceof Map) {
                parameterMap = (Map) args[PARAMETER_INDEX_PARAMETER];
            } else if (BeanUtils.isSimpleValueType(args[PARAMETER_INDEX_PARAMETER].getClass())) {
                //如果是簡單類型就直接設置值
                parameterMap.putIfAbsent("arg0", args[PARAMETER_INDEX_PARAMETER]);
            } else {
                // 如果是DTO類型,反射獲取字段和值的map
                Object dto = args[PARAMETER_INDEX_PARAMETER];
                // 獲取屬性
                PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(dto.getClass());
                for (int i = 0; i < propertyDescriptors.length; i++) {
                    PropertyDescriptor p = propertyDescriptors[i];
                    // spring的BeanUtils.getPropertyDescriptors會把class也獲取到
                    if (p.getPropertyType().equals(Class.class)) continue;

                    Object value = null;
                    try {
                        // 讀取實際值
                        value = p.getReadMethod().invoke(dto);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    parameterMap.putIfAbsent(p.getName(), value);
                }

                result.setHasDto(true);
                result.setDto(dto);
            }
        }

        Map sessionParameterMap = createSessionParameterMap();
        parameterMap.putAll(sessionParameterMap);

        result.setSqlMap(parameterMap);


        return result;
    }

    private String addQuotes(String s) {
        return '"' + s + '"';
    }

    @Getter
    @Setter
    protected class SqlParameterResult {
        // 將接口中傳入的參數修改為map
        private Map sqlMap;
        // 如果有dto,把dto傳出去進行主鍵回寫
        private boolean hasDto = false;
        private Object dto;
    }
}

然后是具體的插件類:


// 指定攔截的類及其方法
@Intercepts({
        // 方法簽名
        @Signature(
                // 所在的類
                type = Executor.class,
                // 方法名
                method = "query",
                // 方法的參數
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryParamPlugin extends MybatisPluginBase implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 獲取到攔截方法的參數
        Object[] args = invocation.getArgs();
        // 創建sql參數的map
        args[PARAMETER_INDEX_PARAMETER] = createSqlParameterMap(args);
        // 創建新的調用對象(參數被覆蓋)
        Invocation overrideInvocation = new Invocation(invocation.getTarget(), invocation.getMethod(), args);
        // 運行新的調用對象
        return overrideInvocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 執行過程中會數次經過這里
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以獲取mybatis配置文件中設置的屬性
        System.out.println("properties = " + properties);
    }
}
不同的query方法
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }) })
public class Query2ParamPlugin extends MybatisPluginBase implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        args[PARAMETER_INDEX_PARAMETER] = createSqlParameterMap(args);
        Invocation overrideInvocation = new Invocation(invocation.getTarget(), invocation.getMethod(), args);
        return overrideInvocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}

攔截更新、插入的方法


@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class UpdateParamPlugin extends MybatisPluginBase implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 獲取攔截的方法的參數
        Object[] args = invocation.getArgs();
        // 根據方法的參數修改sql的參數
        SqlParameterResult sqlParameterMap = createSqlParameterResult(args);
        // 將參數替換成map(不特殊處理會造成主鍵回寫失效)
        args[PARAMETER_INDEX_PARAMETER] = sqlParameterMap.getSqlMap();
        // 構建新的執行器
        Invocation overrideInvocation = new Invocation(invocation.getTarget(), invocation.getMethod(), args);
        // 執行(此時傳出的參數中已經有主鍵)
        Object result = overrideInvocation.proceed();

        // 設置主鍵
        MappedStatement mappedStatement = (MappedStatement) overrideInvocation.getArgs()[PARAMETER_INDEX_MAPPED_STATEMENT];
        Map<String, Object> argMap = (Map<String, Object>) overrideInvocation.getArgs()[PARAMETER_INDEX_PARAMETER];
        if (sqlParameterMap.getHasDto() && mappedStatement.getConfiguration().isUseGeneratedKeys()) {
            String[] keyProperties = mappedStatement.getKeyProperties();
            for (String keyProperty : keyProperties) {
                BeanUtils.setProperty(sqlParameterMap.getDto(), keyProperty, argMap.get(keyProperty));
            }
        }

        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}

還沒有實驗普通參數與DTO同時存在的情況


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM