說明
項目如果需要動態的生成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流操作不懂的可以參考博文