mybatiesplus 用自定义注解实现取消指定方法结果集key驼峰化处理

问题

  • 项目中出现部分持久层方法返回字段是动态的方法,返回类型为list,并且不想在控制层再循环结果进行逆驼峰化处理
  • 后端mybatiesplus开启了驼峰化处理,配置方式为map-underscore-to-camel-case: true,
    在config中配置了
    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer(){
    
          return new ConfigurationCustomizer(){
              @Override
              public void customize(MybatisConfiguration configuration){
                  configuration.setObjectWrapperFactory(new MybatisMapWrapperFactory());
              }
          };
    }
    
    来开启全局驼峰化处理

版本

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.5</version>
    </dependency>

解决

  • 在执行查询前会调用org.apache.ibatis.mapping.MappedStatement.getBoundSql(Object) 方法进行参数绑定等操作并返回 org.apache.ibatis.mapping.BoundSql的实例。
    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap();
        this.metaParameters = configuration.newMetaObject(this.additionalParameters);
    }

BoundSql中的成员变量 MetaObject metaParameters 为在构造方法中调用Configuration.newMetaObject 创建,

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  this.originalObject = object;
  this.objectFactory = objectFactory;
  this.objectWrapperFactory = objectWrapperFactory;
  this.reflectorFactory = reflectorFactory;
  if (object instanceof ObjectWrapper) {
  this.objectWrapper = (ObjectWrapper)object;
  
  } else if (objectWrapperFactory.hasWrapperFor(object)) {
  this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  
  } else if (object instanceof Map) {
  this.objectWrapper = new MapWrapper(this, (Map)object);
  } else if (object instanceof Collection) {
  this.objectWrapper = new CollectionWrapper(this, (Collection)object);
  } else {
  this.objectWrapper = new BeanWrapper(this, object);
  }

    }  

MetaObject 实例中的ObjectWrapper objectWrapper 成员变量为在config中配置的com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory
MybatisMapWrapperFactory的getWrapperFor方法会返回MybatisMapWrapper的实例,

public String findProperty(String name, boolean useCamelCaseMapping) {
    return useCamelCaseMapping && !StringUtils.isCamel(name) ? StringUtils.underlineToCamel(name) : name;
}

MybatisMapWrapper 中的findProperty 方法进行驼峰化处理的方法。
由上可知,在前query前添加拦截器,调整configuration中的objectWrapperFactory 为默认的DefaultObjectWrapperFactory,或者自定义一个ObjectWrapperFactory和MapWrapper的实现类,实现不进行驼峰化处理。

  • 通过自定义的注解来标注方法进行识别,在拦截器中进行调整

UseCamelCaseMapping

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface UseCamelCaseMapping {
boolean value() default true;
}

UseCamelCaseMappingInterceptor


import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
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.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
        )
})
public class UseCamelCaseMappingInterceptor implements Interceptor {
  
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        final Object target = invocation.getTarget();
        final Object[] args = invocation.getArgs();
        BoundSql boundSql = (BoundSql) args[5];
        final Object parameter = args[1];
        final MappedStatement mappedStatement = (MappedStatement) args[0];

        final String namespace = mappedStatement.getId();
        final String className = StringUtils.substringBeforeLast(namespace, ".");
        final Class<?> clazz = Class.forName(className);

        final String methodName = StringUtils.substringAfterLast(namespace, ".");
        // 获得方法注解
        final Method method = this.findMethod(clazz, methodName, parameter);
        if (!method.isAnnotationPresent(UseCamelCaseMapping.class)) {
            return invocation.proceed();
        }
        final UseCamelCaseMapping useCamelCaseMapping = method.getAnnotation(UseCamelCaseMapping.class);

        final boolean value = useCamelCaseMapping.value();
        //useCamelCaseMapping  默认为true与mybatiesplus默认保持一致,配置成false 时会取消驼峰化处理
        if (!value) {
            final Configuration configuration = mappedStatement.getConfiguration();
            //这里将转换工厂设置为默认的,这样可以达到取消的目的
            configuration.setObjectWrapperFactory(new DefaultObjectWrapperFactory());
            //这里重新进行sql参数绑定会,结果处理会使用DefaultObjectWrapperFactory
            boundSql = mappedStatement.getBoundSql(parameter);
            args[5] = boundSql;
        }
        return invocation.proceed();
    }

    private Method findMethod(Class<?> clazz, String methodName, Object parameter) {
        final Method[] methods = clazz.getMethods();
        final List<Method> methodsList = new ArrayList<>();
        for (int m = 0; m < methods.length; m++) {
            final String menthod = methods[m].getName();
            if (menthod.equals(methodName)) {
                methodsList.add(methods[m]);
            }
        }
        if (methodsList.size() == 1) {
            return methodsList.get(0);
        } else if (methodsList.size() > 1) {
            return findMethodByParamater(methodsList, parameter);
        } else {
            return null;
        }
    }

    private Method findMethodByParamater(List<Method> methodsList, Object parameter) {
        Method result = null;
        if (parameter instanceof Map) {
            final Map parameterMap = (Map) parameter;
            result = findMethodByParamaters(methodsList, parameterMap);
        } else {
            //这里就是
            for (Method method : methodsList) {
                final Parameter[] parameters = method.getParameters();
                if (parameters.length == 1) {
                    final Class<?> type = parameters[0].getType();
                    if (type.equals(parameter.getClass()) || parameter.getClass().isAssignableFrom(type)) {
                        result = method;
                        break;
                    }
                }
            }
        }
        return result;
    }

    private Method findMethodByParamaters(List<Method> methodsList, Map parameterMap) {
        Method result = null;
        for (Method method : methodsList) {
            final Parameter[] parameters = method.getParameters();
            if (parameters.length == (parameterMap.size() / 2)) {
                boolean flag = true;//
                for (int i = 0; i < parameters.length; i++) {
                    final Class<?> type = parameters[i].getType();
                    final String name = parameters[i].getName();
                    final Class<?> valueClass = parameterMap.get("param" + (i + 1)).getClass();
                    //这里简单判断,不考虑高级根部类或接口做参数,判断逻辑太复杂,暂时没想好先搁置
                    if (!(parameterMap.containsKey(name) &&
                            (valueClass.equals(type) || valueClass.isAssignableFrom(type)))) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    result = method;
                    break;
                }
            }
        }
        return result;
    }


    @Override
    public final Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }


    @Override
    public final void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }

这里对于获取当前method的方法比较简单,对与重载方法只比对了参数和类型,对一些复杂的情况没有考虑,但在mapper中应该很少会重载方法。
参考:https://www.cnblogs.com/wftop1/p/17145655.html

posted @ 2024-12-19 15:01  alwaysSilent  阅读(171)  评论(0)    收藏  举报