关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

使用场景:

使用mybatis的时候,简单的连表查询,用Map接收的时候,都是像DB定义的字段一样,类似以下 student_name,student_id,没有转换为驼峰,但是又不能因为这一个定义一个javabean来映射数据库字段集合,这样,会有无穷无尽的javabean,完全不是办法。然后看了下mybatis-spring-boot的配置文档

http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/, 发现有这么个属性 

mybatis.configuration.map-underscore-to-camel-case=true

看着属性意思,很像是 map 下划线转换为驼峰,然后我天真的以为,加个这个,就会将Map里面的key转换为驼峰的命名方式, 总归是太天真了。没办法,接着找了了一下,发现官方文档http://www.mybatis.org/mybatis-3/configuration.html#properties, 描述过这个的作用, 如下

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

才发现,这个属性的作用,是作用于javabean的field的,并不是map。既然 map-underscore-to-camel-case 不能作用于map,那么只能自己动手了,然后就想到了2个解决方案:

  1. 继承HashMap,重写Put函数,将mybatis返回的Map,写上自定义Map的路径,自定义的Map,将所有的key内部转换为驼峰表达式
  2. 找到mybatis的Handler,通过Handler来找到转换映射关系的接口定义,继承或者实现接口,然后走自定义的转换规则来实现Map映射的驼峰转换

最终决定,采用第二种,方便以后扩展

下面是使用第二种的两种在项目中的具体实现

 

第一种实现方法:

首先,我开始找mybatis的configuration里面的mapUnderscoreToCamelCase属性调用的地方

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

有两个地方调用了,然后我们看第一个调用的地方,发现名称是createAutomaticMappings,应该是map的映射字段创建,如下代码,光标显示的地方,就是调用的地方

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

然后我们在查看属性传递进去后的操作,点进 metaObject.findProperty的实现

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

接着在看,objectWrapper.findProperty,发现是个接口,然后我们需要查看,是对应的哪个实现,然后我debug发现是 MapWrapper的实现,并没有做任何操作,直接返回了name。

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

到这里,我们可以看下源码的目录结构,发现了以下

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

心里大概明白怎么回事了,具体过程应该如下

通过接口ObjectWrapper来定义行为,有默认的一些实现,然后通过工厂ObjectWrapperFactory接口,来创建ObjectWrapper接口,最后来进行对方的自动映射跟包装,至此,心里已经想到怎么改动了

1.首先,我们先继承类 MapWrapper,重写findProperty,通过useCamelCaseMapping来判断是否开启使用驼峰。

public class CustomWrapper extends MapWrapper{
	
	public CustomWrapper(MetaObject metaObject, Map<String, Object> map) {
		super(metaObject, map);
	}
	
	@Override
	public String findProperty(String name, boolean useCamelCaseMapping) {
		if(useCamelCaseMapping){
            //CaseFormat是引用的 guava库,里面有转换驼峰的,免得自己重复造*,pom添加
            /**
             **         <dependency>
                           <groupId>com.google.guava</groupId>
                           <artifactId>guava</artifactId>
                           <version>24.1-jre</version>
                         </dependency>
             **/
			return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,name);
		}
		return name;
	}
}

2. 然后,我们在实现接口 ObjectWrapperFactory,通过包装工厂来创建自定义的包装类,通过hasWrapperFor判断参数不为空,并且类型是Map的时候才使用自己扩展的ObjectWrapper。

public class MapWrapperFactory implements ObjectWrapperFactory {
	@Override
	public boolean hasWrapperFor(Object object) {
		return object != null && object instanceof Map;
	}
	@Override
	public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
		return new CustomWrapper(metaObject,(Map)object);
	}
}

3. 做完以上操作后,我们需要替换下,以前默认的实现,刚好,mybatis-spring-boot上面有告诉我们怎么做,返回一个 ConfigurationCustomizer 的bean,通过匿名内部类实现覆盖默认的MapWrapper的findProperty函数。

@Configuration
public class MybatisConfig {
	
	@Bean
	public ConfigurationCustomizer mybatisConfigurationCustomizer(){
		return new ConfigurationCustomizer() {
			@Override
			public void customize(org.apache.ibatis.session.Configuration configuration) {
				configuration.setObjectWrapperFactory(new MapWrapperFactory());
			}
		};
	}
}

做了以上操作后,我们就将默认的Map映射的包装类查找property的部分,变成了自己想要的样子;最后,我们在 properties或者yml里面加上
mybatis.configuration.map-underscore-to-camel-case=true ,
毕竟在CustomerWrapper里面,我们是通过使用这个来控制是否转换驼峰的,最后写个测试用例跑一下,然后会发现,以前db的字段,下划线已经转换成驼峰命名了。

 

第二种实现方法:

这种方法就比较投机取巧了, 但是可以很简单的实现Map集合的映射,下面我们来具体看看实现过程。(PS: 推荐使用第一种方法吧, 比较官方!)

1.首先mybatis-3.4.5.jar中一个MapWrapper类, 现在我把这个MapWrapper类直接复制一份,然后创建相应的包, 这个包路径跟在jar包中的包路径完全一样的(位于根路径下的: 关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)), 然后将复制的该MapWrapper文件直接粘贴至该包下, 那么这个就是我们自己的MapWrapper类。

2.对默认的Map映射的包装类查找property的findProperty方法进行改造,加入自己的映射转换规则, 当然也可以像第一种方法中一样: “引用的 guava,里面有转换驼峰的,免得自己重复造*,pom添加依赖即可。这里我们自己尝试的实现, 代码如下:

@Override
  public String findProperty(String name, boolean useCamelCaseMapping) {
	  if (useCamelCaseMapping
              && ((name.charAt(0) >= 'A' && name.charAt(0) <= 'Z')
                   || name.indexOf("_") >= 0)) {
          return underlineToCamelhump(name);
      }
      return name;
  }
  private String underlineToCamelhump(String inputString) {
      StringBuilder sb = new StringBuilder();
      boolean nextUpperCase = false;
      for (int i = 0; i < inputString.length(); i++) {
          char c = inputString.charAt(i);
          if (c == '_') {
              if (sb.length() > 0) {
                  nextUpperCase = true;
              }
          } else {
              if (nextUpperCase) {
                  sb.append(Character.toUpperCase(c));
                  nextUpperCase = false;
              } else {
                  sb.append(Character.toLowerCase(c));
              }
          }
      }
      return sb.toString();
  }

做了以上操作后,我们就将默认的Map映射的包装类查找property的部分,变成了自己想要的样子;

最后, 我们同样在Mybatis配置文件中添加了如下配置:

关于Springboot+Mybatis中返回结果集为Map时其内的Key转换为驼峰的命名(2种方法)

毕竟在CustomerWrapper里面,我们是通过使用这个来控制是否转换驼峰的。

当然最主要可以这样实现的原因还是基于类加载机制允许的基础上的,了解过相应的类加载原理及加载顺序后我们可以知道:“位于WEB-INF目录下自定义的class类优先与该目录下的lib下jar包中的类加载; 同一个JVM里面,判断两个类是否相同的依据是类的全路径名称和类加载器名称是否完全相同。但由于委派机制的存在,每个类只要在父类加载器哪里被找到,子类加载器就不会再尝试加载了,因此,同名的类只能被加载一次,这被称为单一原则。”

 

到此文章结束了

以上,如有不正之处,欢迎指正

 

参考链接:https://my.oschina.net/u/2278977/blog/1795969