zoukankan      html  css  js  c++  java
  • 通过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同时存在的情况

  • 相关阅读:
    THUPC2018 好图计数
    CF1349F1
    CF1098E
    [HNOI2011]卡农
    CF736D Permutations(伴随矩阵)
    uoj310【UNR #2】黎明前的巧克力(FWT)
    知识点简单总结——常系数齐次线性递推
    知识点简单总结——带花树(一般图最大匹配)
    知识点简单总结——Pollard-Rho算法
    知识点简单总结——二次剩余
  • 原文地址:https://www.cnblogs.com/lixin-link/p/14589413.html
Copyright © 2011-2022 走看看