MyBatisPlus返回Map键值对数据Key值大小写问题,另外一种解决方式

1,969 阅读4分钟

在开发过程中遇见了MyBatisPlus返回Map键值对数据Key值大小写问题,这个问题已经不是第一次处理了,但是在这一次的处理中产生了别的 bug,所以换了一种解决方法(只是解决了,不能确定会不会出现别的问题),记录一下。在处理问题之前参考了这位同学的文章:MyBatisPlus返回Map键值对数据Key值大小写问题

背景

公司要做国产化改造,主要就是将原有的 oracle 数据库替换为人大金仓的数据库

相关技术组件:

  • springboot 2.7.7
  • mybatisplus 3.5.2
  • druid 1.2.5
  • 人大金仓数据库kingbase

改造回顾

在迁移改造过程中发现有一个接口的返回值跟原有的不一样,返回的 json 的 key 为user_id这样的小写,但是前端使用的是大写USER_ID(吐槽balabala)。查看接口后发现使用的查询的 mapper 的返回类型为 Map 类型,在 oracle 环境下Map 的 key 默认为大写USER_ID,在修改数据库类型为 kingbase后返回值的 key 却变为了小写user_id(mysql也会)。

因为不是第一次遇见这种问题,所以这时候我是知道可以通过修改代码配置的方式去全局统一修改这个问题的,百度后然后按照MyBatisPlus返回Map键值对数据Key值大小写问题的方式修改一遍,但这样的改动却产生了新的mybatis-plus相关的bug。

先贴出关键代码

@Bean(name = "sqlSessionFactory")
@ConditionalOnBean(name = "dataSource")
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dataSource") DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setObjectWrapperFactory(new MapWrapperFactory());
        bean.setDataSource(dataSource);
        // 添加XML目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath*:com/ultrapower/ioss/**/mapper/**/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
}

@Bean("sqlSessionTemplate")
@ConditionalOnBean(name = "sqlSessionFactory")
public SqlSessionTemplate sqlSessionTemplate(
            @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
}

@ConditionalOnBean(name = "dataSource")
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        return dataSourceTransactionManager;
}


步骤一

按上述方案修改完配置后未生效,将断点打在第 4 行时,启动服务后发现并未走至此断点,怀疑因为@ConditionalOnBean(name = "dataSource")和 druid的 dataSource 注入的原因造成 bean 名称不匹配导致未走这个 bean 注入,尝试换过druid的 datasource bean名称后仍未成功,然后就没有再继续深究了,就索性将@ConditionalOnBean去掉就可以了。

步骤二

服务启动后能成功注入sqlSessionFactory的 bean。此时接口查询的返回值json 的 key 变成了大写,

但是测试别的接口时发现其他 mybatis plus 相关的封装接口 list、save、getOne 等接口报错invalid bound statement这样的错误,猜测 bean.setMapperLocations(resolver.getResources("classpath*:com/ultrapower/ioss/ ** /mapper/ ** /*.xml"))这段代码引起的 mapper.xml 扫描不对,无法与 mapper.java 绑定。

然后注释掉代码后重启服务,重启后发现仍然不行,此时猜测是因为自己注入的sqlSessionFactory的 bean覆盖了配置文件中mybatis-plus.mapper-locations的配置的 bean,导致原有配置失效而无法正确完成 mapper 的扫描。

步骤三

发现上面的路走不通后,选择通过修改接口的方式,将原有的 Map 的返回类型改为 vo 返回。然后新增了vo类并定义了大写格式的字段USER_ID,并在 mapper 中通过 resultType 的方式接收返回结果。

重启测试,在通过 postman 调用后发现此时返回的字段为 user_ID不是期望的 USER_ID(第一次见这种情况,此时的我应该是黑人问号脸)。通过断点发现,service 层返回的结果确实是 USER_ID 但是在 controller 返回结果后,在前端接受到的返回值的 key 从USER_ID变为了user_ID。

通过百度发现可能是因为springboot的jackson序列化的时候自动转化的(其实我自己也知道变量名全大写不对,但是字段改小写的话还得修改前端,吐槽中...)。这种转化策略可通过修改spring.jackson.property-naming-strategy的方式去改变,但是没有找到我的合适的策略,参考blog.csdn.net/qq_41959009…

步骤四

上面这条路走不通,再拐回去。

既然我不能随意改变sqlSessionFactory的注入,那我能不能在sqlSessionFactory注入之后在执行setObjectWrapperFactory()呢?

spring bean 注入后的操作可以通过实现 BeanPostProcessor 的方式,重写 postProcessAfterInitialization 的方式实现(gpt不推荐在这里改sqlSessionFactory的属性)。

下面贴出关键代码

MapKeyUpperWrapper.java

/**
 * mybatis返回值,key的大小写转换,在oracle下默认为大写,在mysql下默认为小写,使用此转换器在mysql环境下转为大写。
 * 默认值:大写
 */
public class MapKeyUpperWrapper extends MapWrapper {
    public MapKeyUpperWrapper(MetaObject metaObject, Map<String, Object> map) {
        super(metaObject, map);
    }

    @Override
    public String findProperty(String name, boolean useCamelCaseMapping) {
        // 转小写为toUpperCase()
        return name == null ? "" : name.toUpperCase();
    }
}

DefaultObjectWrapperFactory.java

public class DefaultObjectWrapperFactory implements ObjectWrapperFactory {
   @Override
   public boolean hasWrapperFor(Object object) {
       return object != null && object instanceof Map;
   }

   @Override
   public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
       return new MapKeyUpperWrapper(metaObject, (Map) object);
   }
}

SqlSessionFactoryPostProcessor.java

@Component
public class SqlSessionFactoryPostProcessor implements BeanPostProcessor {
   @SneakyThrows
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) {
       if(bean instanceof SqlSessionFactory){
           DefaultSqlSessionFactory defaultSqlSessionFactory = (DefaultSqlSessionFactory)bean;
           defaultSqlSessionFactory.getConfiguration().setObjectWrapperFactory(new DefaultObjectWrapperFactory());
       }
       return bean;
   }
}

重启服务后接口正确返回我需要的大写的 key:USER_ID ,而且 mybatis-plus 的封装接口也不再受影响。

以上就是整个过程的记录,我的这种改法不一定是对的,但是目前确实解决了我的问题,也没有引起别的问题。如果大家有别的方法或者对当前方法有什么指正,请在评论区留言。