SpringDataJPA使用@Query Native查询返回Map与对象转换

在使用JPA来操作数据库时,在遇到复杂查询时(包括JOIN嵌套等),需要使用到@Query来使用原生SQL或者Hibernate的HQL进行查询,Repository中编写如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 仅为演示样例
public interface UserRepository extends JpaRepository<User, Integer> {
@Query(value = "select * from user as u where u.group_id = :gid",
countQuery = "select count(*) from user as u where u.group_id = :gid",
nativeQuery = true)
Page<Map<String, Object>> findAllUserByGroupId(@Param("gid") Integer groupId, Pageable pageable);

@Query(value = "select * from user as u where u.group_id = :gid",
countQuery = "select count(*) from user as u where u.group_id = :gid",
nativeQuery = true)
Page<Object[]> findAllUserByGroupId2(@Param("gid") Integer groupId, Pageable pageable);
}

@Query支持使用两种方式进行接收:

  • Object[]:
    通过使用 Object[0], Object[1] 来获取结果字段值,需要手动将字段转换为对应的Bean字段,非常的不优雅。
  • Map<String, Object>:
    Map中的Key为对应的数据库字段String名,Object为字段对应的值,通过使用Map.get(key)来获取对应字段的值,相比Object[]会优雅一些。

那么有没有一种办法,可以将Map转换为对应的Object?

首先会遇到第一个问题,数据库字段和实体类字段命名规则不同,在Hibernate默认模式下,数据库字段为下划线格式,即:

userGroupId => user_group_id

Map<String,Object>中,取到的Key是下划线模式(数据库字段名),那么首先要解决的问题,就是将下划线命名,转换为驼峰,参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static Pattern linePattern = Pattern.compile("_(\\w)");
/**
* 下划线转驼峰
*/
public static String lineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}

通过正则匹配,去掉下划线并且将首字母大写来恢复驼峰命名,注意,这里可以正确转换的前提是,实体类字段名,是规范的驼峰命名法,否则会出现无法转换、匹配异常等问题。


规避掉下划线与驼峰问题之后,就可以通过Spring集成的cglib中的BeanMap来进行对象的转换,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 将属性键值对map集合 转为 对象集合
*
* @param maps 注意 MapKey: 字段名称,如果字段名称为下划线模式,会自动转换为驼峰进行匹配
* @param clz 转换目标的类型
* @param <T>
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> List<T> maps2Bean(List<Map<String, Object>> maps, Class clz)
throws IllegalAccessException, InstantiationException {
BeanMap beanMap;
T bean;
List<T> list = new ArrayList<>();

for (Map<String, Object> map : maps) {
bean = (T) clz.newInstance();
beanMap = BeanMap.create(bean);
for (Map.Entry<String, Object> entry : map.entrySet()) {
// 将Key的下划线命名规则,转换为驼峰规则
beanMap.put(lineToHump(entry.getKey()), entry.getValue());
}
list.add(bean);
}

return list;
}

通过BeanMap将对象进行赋值操作,完成Map到对象的转换,使用方法如下:

1
2
3
4
5
6
try {
List<Map<String, Object>> result = userResList.getContent();
List<UserDTO> objects = BeanMapUtil.maps2Bean(result, UserDTO.class);
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}

这样就可以简化Query结果的转换操作了。

参考

#
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×