Java获取Mybatis中的映射字段名,根据实体类属性获取实际字段列名
说明
项目如果需要动态的生成SQL语句,那么语句中的字段名是一个比较麻烦的事情,虽然Entity
对象和数据表一般是一比一按照驼峰命名法和下划线命名法标准转换来映射的,但是简单的将Entity
对象中的属性转为字段名是一个有风险的操作
有没有哪里记录了实体类属性和数据表字段的映射关系呢?那么你应该立即想到了mybatis mapper xml文件中的ResultMap
了
<mapper namespace="xx.xx.dao.StudentMapper">
<resultMap id="BaseResultMap" type="xx.xx.model.entity.StudentEntity">
<id property="studentId" column="student_id"/>
<result property="studentName" column="student_name"/>
<result column="student_number" property="studentNumber"/>
<result column="identity" property="identity"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
</resultMap>
<resultMap id="main" type="xx.xx.model.entity.StudentEntity">
<id property="studentId" column="student_id"/>
<result property="studentName" column="student_name"/>
<result column="student_number" property="studentNumber"/>
<result column="identity" property="identity"/>
</resultMap>
</mapper>
如何获取mybatis的ResultMap?
原理过程
- 获取mybatis SqlSessionTemplate
- 获取回话配置
- 获取所有的ResultMap
- 根据会话配置获取指定id的ResultMap
- 读取ResultMap匹配到property属性和实体类属性名一致则返回
/**
* 获取实体类对应的mybatis mapper的resultMap映射对象
* 必须保证jdk1.8及以上
*
* @param clazz 实体类
* @return ResultMap
*/
private static ResultMap getBaseResultMap(Class<?> clazz) {
//获取SqlSessionTemplate
SqlSessionTemplate sqlSessionTemplate = ApplicationUtils.getBean(SqlSessionTemplate.class);
assert sqlSessionTemplate != null;
//关键在于这里,获取SqlSessionTemplate中的Configuration,这里面当前Sql seesion会话的所有参数
//Configuration的getResultMap方法就可以获取指定的ResultMap,所以是该方法需要指定ResultMap的ID
Configuration configuration = sqlSessionTemplate.getConfiguration();
//获取所有的ResultMap的名字:以xml的命名空间如:xx.xx.dao.StudentMapper加resultMap的id如:BaseResultMap组合:xx.xx.dao.StudentMapper.BaseResultMap这样的全定名
//注意会存在一个默认的BaseResultMap,为上面那个的短名称,所以我们会拿到项目所有的ResultMap
Collection<String> resultMapNames = configuration.getResultMapNames();
//利用Stream流快速筛查
List<ResultMap> resultMaps = resultMapNames.parallelStream()
.filter(name -> name.contains("."))//要全定名不要短名
.map(configuration::getResultMap)//根据全定名找到匹配的ResultMap
.filter(resultMap -> Objects.equals(resultMap.getType(), clazz))//匹配xml中type属性和实体类一致的
//排序,按字段数量来;这里还是会有多个,为什么:比如上面的xml中就有两个ResultMap
.sorted(Comparator.comparing(resultMap -> resultMap.getPropertyResultMappings().size()))
.collect(Collectors.toList());
//翻转,毕竟resultMap包含的字段多的属性映射更全嘛
Collections.reverse(resultMaps);
//找出那个type属性一致的,其实这个list里面所有的resultMap属性都是一致的了,毕竟上面过滤了,只不过Stream过滤就算只有一个也是那list装的
if (BeanUtils.isNotEmpty(resultMaps)) {
// return resultMaps.get(0);TODO 所以这里这样写和下面没毛病
for (ResultMap resultMap : resultMaps) {
Class<?> type = resultMap.getType();
if (Objects.equals(type, clazz)) {
return resultMap;
}
}
}
return null;
}
/**
* 根据实体类的属性名获取对应的数据表的字段列名
*
* @param property 属性名
* @param clazz 实体类
* @return 字段名
*/
public static String property2Field(String property, Class<?> clazz) {
ResultMap resultMap = getBaseResultMap(clazz);
if (BeanUtils.isNotEmpty(resultMap)) {
for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
if (resultMapping.getProperty().equals(property)) {
property = resultMapping.getColumn();
return property;
}
}
}
//找不到resultMap就转下划线处理
String column = StringUtils.camelToUnderline(property, false);
log.warn("没有查询到Mapper中的ResultMap:" + property + "字段映射信息!将使用驼峰命名法转换下划线属性名:" + column);
return column;
}
当然,这样的方式是不能保证100%找到字段匹配的,如果resultMap没有配置是找不到的,那么就默认转下划线处理了。
转换方法
/**
* 驼峰转下划线
*
* @param param 字符串
* @param upperCase 是否全大写
* @return 结果
*/
public static String camelToUnderline(String param, boolean upperCase) {
if (param == null || "".equals(param.trim())) {
return "";
}
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (Character.isUpperCase(c)) {
sb.append(UNDERLINE);
}
if (upperCase) {
//统一都转大写
sb.append(Character.toUpperCase(c));
} else {
//统一都转小写
sb.append(Character.toLowerCase(c));
}
}
return sb.toString();
}
Bean工具类代码
@Component
public class ApplicationUtils implements ApplicationContextAware {
private static ApplicationContext context;
public static <T> T getBean(Class<T> tClass) {
try {
return context.getBean(tClass);
} catch (Exception e) {
System.err.println("获取bean失败!");
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
PS:注意以上操作还是有很大风险,如果保证ResultMap的正常配置和遵循命名法要求的话是没问题的。
关于Stream流操作不懂的可以参考博文