Spring data JPA只查询部分字段问题及解决
作者:北极象
这篇文章主要介绍了Spring data JPA只查询部分字段问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
背景
在JPA查询中,有时只需要查部分字段,这时jpa repository查出的是map,无法映射到Entity类。
会提示错误:
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type
网上搜索有多种解决方案。这里列举一下。
经过验证,本人采取了第一种方案,证明是可行的。
JPA 2.1以上的解决办法
实体中增加named query和result map
@SqlResultSetMapping(name = "EBookInfo",
classes = @ConstructorResult(targetClass = EBookInfo.class,
columns = {
@ColumnResult(name = "book_id", type = Long.class),
@ColumnResult(name = "book_name", type = String.class),
@ColumnResult(name = "file_type", type = String.class)
}))
@NamedNativeQuery(name = "listExpressEbooks",
query = "select book_id, book_name, file_type from ebook order by update_date desc",
resultSetMapping = "EBookInfo")
@Entity
@Table(name = "ebook")
public class Ebook {
private Long bookId;
private Integer authorId;
private String authorName;
private Integer categoryId;
private String bookName;
private String subTitle;
private String tags;
private String isbn;
private String edition;
private Byte bookType;
private Integer star;
private Integer downloadCount;
private Byte status;
private String fileType;
private String outline;
private String introduction;
private String preface;
private String cover;
private Float price;
private String publisher;
private String bgColor;
private String foreColor;
private String titleColor;
private String coverBackgroundId;
private String coverPictureId;
private Integer coverTemplateId;
private String coverPictureMode;
private Integer pageMode;
private Timestamp requestDate;
private Timestamp publishDate;
private Timestamp updateDate;
定义一个新的DTO对象
字段和查询的字段对应,需要提供构造函数:
@Data
public class EBookInfo {
private Long bookId;
private String bookName;
private String fileType;
public EBookInfo(Long bookId, String bookName, String fileType) {
this.bookId = bookId;
this.bookName = bookName;
this.fileType = fileType;
}
}
repository中定义查询接口
@Query(name = "listExpressEbooks", nativeQuery = true)
public List<EBookInfo> listExpressEbooks();其它方案
查询中构造新对象
public List<Blog> selectByYearMonth(String year, String month, int status) {
String sql = String.format("select new Blog(blog.id, blog.title, blog.abs, blog.createtime) from Blog blog where blog.status = %d and YEAR(createtime) = %s and MONTH(createtime) = %s order by blog.createtime desc", status, year, month);
//Query query = this.em.createNativeQuery(sql, "ExpressedResult");
Query query = this.em.createQuery(sql);
List results = query.getResultList();
return results;
}上述方法是之前我项目中代码库里的写法,Blog需要提供相应的构造函数。
自己写convertor
repository 返回 Tuple 对象,自己写代码手动转换为指定对象,repository层使用native查询。
这里要借助辅助类:
class NativeResultProcessUtils {
/**
* tuple转实体对象
* @param source tuple对象
* @param targetClass 目标实体class
* @param <T> 目标实体类型
* @return 目标实体
*/
public static <T> T processResult(Tuple source,Class<T> targetClass) {
Object instantiate = BeanUtils.instantiate(targetClass);
convertTupleToBean(source,instantiate,null);
return (T) instantiate;
}
/**
*
* tuple转实体对象
* @param source tuple对象
* @param targetClass 目标实体class
* @param <T> 目标实体类型
* @param ignoreProperties 要忽略的属性
* @return 目标实体
*/
public static <T> T processResult(Tuple source,Class<T> targetClass,String... ignoreProperties) {
Object instantiate = BeanUtils.instantiate(targetClass);
convertTupleToBean(source,instantiate,ignoreProperties);
return (T) instantiate;
}
/**
* 把tuple中属性名相同的值复制到实体中
* @param source tuple对象
* @param target 目标对象实例
*/
public static void convertTupleToBean(Tuple source,Object target){
convertTupleToBean(source,target,null);
}
/**
* 把tuple中属性名相同的值复制到实体中
* @param source tuple对象
* @param target 目标对象实例
* @param ignoreProperties 要忽略的属性
*/
public static void convertTupleToBean(Tuple source,Object target, String... ignoreProperties){
//目标class
Class<?> actualEditable = target.getClass();
//获取目标类的属性信息
PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
//忽略列表
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
//遍历属性节点信息
for (PropertyDescriptor targetPd : targetPds) {
//获取set方法
Method writeMethod = targetPd.getWriteMethod();
//判断字段是否可以set
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
//获取source节点对应的属性
String propertyName = targetPd.getName();
Object value = source.get(propertyName);
if(value!=null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) {
try {
//判断target属性是否private
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
//写入target
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
使用entityManager的Transformers.aliasToBean
未验证,Spring data jpa未必支持
使用entityManager的Transforms.ALIAS_TO_ENTITY_MAP
未验证
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
