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 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(); Class<?> clz = targetType.getType(); try { Object target = clz.getDeclaredConstructor().newInstance(); for (Field field : clz.getDeclaredFields()) { field.setAccessible(true ); namingMatcher.match(field.getName(),sourceNames) .ifPresent(key->{ Object sourceValue = sourceMap.get(key); 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 @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 ; } } @FunctionalInterface public interface StringConverter { String convert (String name) ; default StringConverter andThen (StringConverter then) { return (name)-> then.convert(this .convert(name)); } } public class NoChangeStringConverter implements StringConverter { @Override public String convert (String name) { return name; } } @RequiredArgsConstructor public class DelPrefixStringConverter implements StringConverter { private final String prefix; @Override public String convert (String name) { return name.replaceFirst(prefix,"" ); } } public class UnderscoresStringConverter implements StringConverter { @Override public String convert (String name) { return StrUtil.toCamelCase(name); } } 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); } 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 <>(); public Builder addConverter (int sort , StringConverter converter) { converters.add(new SortObj <>(sort,converter)); return this ; } public Builder addConverters (Collection<SortObj<StringConverter>> converters) { this .converters.addAll(converters); return this ; } public Builder useDefaultConfig () { return useDefaultConfig(null ); } 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 ()); } public NamingMatcher build () { return new NamingMatcher (converters); } } }
4、编写代码测试!
5、注意:自定义实体需要有一个无参构造器。