jpa原生sql返回非entity对象

参考文章:Spring Data Jpa 原生SQL返回自定义对象最简洁方式 - evan888的个人空间 - OSCHINA - 中文开源技术交流社区

在使用spring-data-jpa的过程中有一个问题一直困扰于我,就是在repository中写自定义sql时需要返回自定义对象时,一直没有发现有良好的接收方式。今天我在这里分享下我的已知解决办法。

一、使用map接收

1
2
3
@Query(nativeQuery = true,value = """  
select id , name , sub_count , 'test_one' as test_one , 'testTwo' as testTwo , create_time from sys_dept """)
List<Map<String,Object>> myFindAllDeptMap();

这种方式虽然简单,但是代码不易于后续维护。

二、使用对象数组

1
2
3
@Query(nativeQuery = true,value = """  
select id , name , sub_count , 'test_one' as test_one , 'testTwo' as testTwo , create_time from sys_dept """)
List<Object[]> myFindAllDeptArray();

这种方式和map差不了多少,不过我们可以通过一个编写一个对象数组转对象的工具类,原理就是通过构造函数来。

三、直接使用实体,但需要有对应的构造函数。

这种方式应该是方法2的简化版,条件是而且只有一个构造函数,有些工具类需要用到默认构造函数,不方便。

四、使用接口的方式

1
public interface UserName { String getNname(); }

这种方式编写麻烦,且面对实体的属性需要进行一些修改时显得无从下手。

五、配置一个jpa的全局转换器(推荐)

在我们不进行任何配置的时候将自定义类作为自定义查询的返回值是会出现如下错误。

1
No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [top.xia17.study.orm.jpa.model.DeptVo]

提示我们缺失一个转换器,按照解决问题的思路就是添加一个转换器即可。现在我们就需要找到这个转换器。

想看方法直接点这里。 [[#5 2 实例代码]]

5.1 通过查看源代码找到可以添加转换器的地方

通过查看报错信息,可以找到抛出异常的类。

这里提一下为啥不是GenericConversionService.java ,一开始我是点的这个并且发现了里面的addConverter方法,但是不知道该如何调用。于是从上一级在找如何获取到GenericConversionService。

点进去发现是这行代码 return conversionService.convert(source, targetType);

现在我们就需要找到 conversionService 是如何生成的,找到发现有两个构造函数如下。

通过打断点在启动的时候发现都是使用的第一个构造器,并且有多少个自定义查询就会实例几个ResultProcessor对象。

在点进去 new ProjectingConverter方法。

发现service是使用的 DefaultConversionService.getSharedInstance();

到这里,我们只需要在项目启动的时候添加转换器即可。

5.2 实例代码

实例代码引用hutool工具包的相关工具类。

1、编写转换器

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**  
* jpa
* @author xia17
* @date 2022/9/28
*/public class JpaMapToObjectConverter implements GenericConverter {

private final NamingMatcher namingMatcher;

public JpaMapToObjectConverter(){
this.namingMatcher = NamingMatcher.DEFAULT_INSTANCE;
}

public JpaMapToObjectConverter(NamingMatcher namingMatcher) {
this.namingMatcher = namingMatcher;
}


@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Map.class,Object.class));
}

@Override
public Object convert(Object source, @NonNull TypeDescriptor sourceType, @NonNull TypeDescriptor targetType) {
if (!sourceType.isMap()){
throw new RuntimeException("源类型不是Map");
}
Map<String,Object> sourceMap = (Map<String, Object>) source;
Set<String> sourceNames = sourceMap.keySet();
// 使用反射将map转对象。 目标对象必须有无参构造器
Class<?> clz = targetType.getType();
try {
// 使用无参构造器新建实例
Object target = clz.getDeclaredConstructor().newInstance();
// 获取所有字段并遍历
for (Field field : clz.getDeclaredFields()) {
// 设置权限为可访问
field.setAccessible(true);
// 从map中的keySet匹配对应字段
namingMatcher.match(field.getName(),sourceNames)
.ifPresent(key->{
// 从map中找到了对应的key,将进行值的转换
Object sourceValue = sourceMap.get(key);
// 使用Hutool的转换工具类进行值的转换
Object targetValue = Convert.convertQuietly(field.getType(), sourceValue);
// 反射设置字段的值
try {
field.set(target,targetValue);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
return target;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

}

2、项目启动时添加转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Configuration
@RequiredArgsConstructor
public class JpaConfig implements InitializingBean {

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("hello this is jpa config");
ConversionService sharedInstance = DefaultConversionService.getSharedInstance();
GenericConversionService sharedInstance1 = (GenericConversionService) sharedInstance;
sharedInstance1.addConverter(new JpaMapToObjectConverter());
}
}

3、NamingMatcher相关代码

NamingMatcher 是本人编写的一个命名匹配的工具,主要是为了解决map转对象的时候key和字段的对应关系。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

/**
* 排序包装类
* @author xia17
* @date 2022/9/29
*/
@Getter
@Setter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SortObj<T> implements Comparable<SortObj<T>>{

private int sort;

private T value;

@Override
public String toString() {
return value.toString();
}


@Override
public int compareTo(SortObj<T> sortObj) {
if (this.sort == sortObj.sort){
return value.toString().compareTo(sortObj.value.toString());
}
return this.sort - sortObj.sort > 0 ? 1 : -1 ;
}
}

/**
* 字符串转换器
* @author xia17
* @date 2022/9/29
*/
@FunctionalInterface
public interface StringConverter {

/**
* 转换
* @param name 名称
* @return /
*/ String convert(String name);


/**
* and then * @param then /
* @return /
*/ default StringConverter andThen(StringConverter then){
return (name)-> then.convert(this.convert(name));
}


}


/**
* 不改变
* @author xia17
* @date 2022/9/29
*/public class NoChangeStringConverter implements StringConverter {


@Override
public String convert(String name) {
return name;
}

}

/**
* 删除前缀
* @author xia17
* @date 2022/9/29
*/@RequiredArgsConstructor
public class DelPrefixStringConverter implements StringConverter {

private final String prefix;

@Override
public String convert(String name) {
return name.replaceFirst(prefix,"");
}


}

/**
* 下划线转驼峰
* @author xia17
* @date 2022/9/29
*/public class UnderscoresStringConverter implements StringConverter {


@Override
public String convert(String name) {
return StrUtil.toCamelCase(name);
}
}


/**
* 命名匹配工具
* @author xia17
* @date 2022/9/29
*/public class NamingMatcher {

private final List<SortObj<StringConverter>> converters;

/**
* 默认实例
*/
public static NamingMatcher DEFAULT_INSTANCE = new Builder().useDefaultConfig().build();

private NamingMatcher(List<SortObj<StringConverter>> converters) {
Collections.sort(converters);
this.converters = Collections.unmodifiableList(converters);
}

/**
* 匹配
* @param targetName /
* @param sourceNames /
* @return /
*/
public Optional<String> match(String targetName, Set<String> sourceNames) {
for (SortObj<StringConverter> converterSortObj : converters) {
Optional<String> any = sourceNames.stream()
.filter(e -> targetName.equals(converterSortObj.getValue().convert(e)))
.findAny();
if (any.isPresent()){
return any;
}
}
return Optional.empty();
}


public static class Builder{

private final List<SortObj<StringConverter>> converters = new ArrayList<>();


/**
* 添加一个
* @param sort /
* @param converter /
* @return /
*/ public Builder addConverter(int sort , StringConverter converter){
converters.add(new SortObj<>(sort,converter));
return this; }

/**
* 添加多个
* @param converters /
* @return /
*/
public Builder addConverters(Collection<SortObj<StringConverter>> converters){
this.converters.addAll(converters);
return this; }

/**
* 1、不变
* 2、下滑线转驼峰
* @return /
*/
public Builder useDefaultConfig(){
return useDefaultConfig(null);
}

/**
* 1、不变
* 2、去除前缀
* 3、去除前缀后下滑线转驼峰
* @param prefix 前缀
* @return /
*/
public Builder useDefaultConfig(String prefix){
this.addConverter(10,new NoChangeStringConverter());
if (!StrUtil.isEmpty(prefix)){
DelPrefixStringConverter delPrefixStringConverter = new DelPrefixStringConverter(prefix);
this.addConverter(20,delPrefixStringConverter)
.addConverter(30,delPrefixStringConverter.andThen(new UnderscoresStringConverter()));
return this; }
return this.addConverter(30,new UnderscoresStringConverter());
}

/**
* 构建
* @return /
*/
public NamingMatcher build(){
return new NamingMatcher(converters);
}

}


}


4、编写代码测试!

5、注意:自定义实体需要有一个无参构造器。