在使用Spring Data Jpa框架时,根据业务需求我们通常需要进行复杂的数据库查询,并返回我们自定义的实体类,而在该框架下,目前仅仅支持返回与数据库映射进行持久化的POJO实体。虽然在框架上我们可以使用@Query注解执行我们自定义的sql语句,但是其返回值为List<Object[]> 类型,即多个Object数组的List集合。
下面我们介绍一下关于在Spring Data Jpa框架下使用自定义查询语句返回自定义实体的解决方案。
解决方案一:
例如我们有如下向关联实体:
Book实体
@Entity(name = "Book")
public class BookInfo implements Serializable {
@Id
@Column(name = "BOOKPKID")
private BigDecimal bookpkid;
@Column(name = "BOOKCODE")
private String bookcode;
@Column(name = "BOOKNAME")
private String bookname;
@Column(name = "AUTHORID")
private BigDecimal authorid;
public BookInfo(BigDecimal bookpkid, String bookcode, String bookname, BigDecimal authorid) {
this.bookpkid = bookpkid;
this.bookcode = bookcode;
this.bookname = bookname;
this.authorid = authorid;
}
public BookInfo() {
}
//此处省略getter和setter方法
Author实体
@Entity(name = "Author")
public class AuthorInfo implements Serializable {
@Id
@Column(name = "AUTHORID")
private BigDecimal authorid;
@Column(name = "AUTHORNAME")
private BigDecimal authorname;
@Column(name = "AUTHORSEX")
private String authorsex;
public AuthorInfo(BigDecimal authorid, BigDecimal authorname, String authorsex) {
this.authorid = authorid;
this.authorname = authorname;
this.authorsex = authorsex;
}
public AuthorInfo() {
}
//此处省略getter和setter方法
下面我们自定义自己想要查询的实体:
public class CustomModel {
private BigDecimal bookpkid; //书籍主键
private String bookcode; //书籍编号
private String bookname; //书籍名称
private BigDecimal authorname; //作者姓名
public CustomModel(BigDecimal bookpkid, String bookcode, String bookname, BigDecimal authorname) {
this.bookpkid = bookpkid;
this.bookcode = bookcode;
this.bookname = bookname;
this.authorname = authorname;
}
public CustomModel() {
}
//此处省略getter和setter方法
}
下面我们来看下DAO层的JPA处理接口类
public interface BookInfoRepository extends JpaRepository<BookInfo, BigDecimal> {
/**
* 根据书籍主键查询书籍详细信息
*
* @param pkid 书籍主键值
* @return 书籍详细信息
*/
@Query(value = "SELECT a.bookpkid,a.bookcode,a.bookname,b.authorname FROM BookInfo a " +
"LEFT JOIN AuthorInfo b ON a.authorid=b.authorid " +
"WHERE a.bookpkid=:pkid", nativeQuery = true)
List<Object[]> selectByPkid(@Param("pkid") BigDecimal pkid);
}
我们来解释一下上面这个处理接口类中的要点:
①nativeQuery=true,属性的设置,是表明该方法中的sql以数据库的sql语句格式对待。
②返回值为List<Object[]>,由于我们之前说过Jpa无法自动完成查询结果到自定义实体的映射,所以我们要使用改对象接收。
最后我们看下将该List<Object[]>对象转换为我们自定义实体的工具类:
public class EntityUtils {
private static Logger logger = LoggerFactory.getLogger(EntityUtils.class);
/**
* 将数组数据转换为实体类
* 此处数组元素的顺序必须与实体类构造函数中的属性顺序一致
*
* @param list 数组对象集合
* @param clazz 实体类
* @param <T> 实体类
* @param model 实例化的实体类
* @return 实体类集合
*/
public static <T> List<T> castEntity(List<Object[]> list, Class<T> clazz, Object model) {
List<T> returnList = new ArrayList<T>();
if (list.isEmpty()) {
return returnList;
}
//获取每个数组集合的元素个数
Object[] co = list.get(0);
//获取当前实体类的属性名、属性值、属性类别
List<Map> attributeInfoList = getFiledsInfo(model);
//创建属性类别数组
Class[] c2 = new Class[attributeInfoList.size()];
//如果数组集合元素个数与实体类属性个数不一致则发生错误
if (attributeInfoList.size() != co.length) {
return returnList;
}
//确定构造方法
for (int i = 0; i < attributeInfoList.size(); i++) {
c2[i] = (Class) attributeInfoList.get(i).get("type");
}
try {
for (Object[] o : list) {
Constructor<T> constructor = clazz.getConstructor(c2);
returnList.add(constructor.newInstance(o));
}
} catch (Exception ex) {
logger.error("实体数据转化为实体类发生异常:异常信息:{}", ex.getMessage());
return returnList;
}
return returnList;
}
/**
* 根据属性名获取属性值
*
* @param fieldName 属性名
* @param modle 实体类
* @return 属性值
*/
private static Object getFieldValueByName(String fieldName, Object modle) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = modle.getClass().getMethod(getter, new Class[]{});
Object value = method.invoke(modle, new Object[]{});
return value;
} catch (Exception e) {
return null;
}
}
/**
* 获取属性类型(type),属性名(name),属性值(value)的map组成的list
*
* @param model 实体类
* @return list集合
*/
private static List<Map> getFiledsInfo(Object model) {
Field[] fields = model.getClass().getDeclaredFields();
List<Map> list = new ArrayList(fields.length);
Map infoMap = null;
for (int i = 0; i < fields.length; i++) {
infoMap = new HashMap(3);
infoMap.put("type", fields[i].getType());
infoMap.put("name", fields[i].getName());
infoMap.put("value", getFieldValueByName(fields[i].getName(), model));
list.add(infoMap);
}
return list;
}
}
在执行DAO层方法,获得相应的List<Object[]>对象调用工具类中的静态方法castEntity,即可将数据转换为自定义实体。
在使用该解决方案时,需注意以下几点要求:
①自定义查询语句中的查询字段的顺序一定要和自定义实体的构造方法中的属性顺序一致。
②此种方案在解决特别复杂的查询语句时很高效,因为只需自定义查询语句,与数据库进行一次交互即可,效率可观。但对程序的规范性要求比较高。
③此方案在解决当前项目数据库中数据表在业务需求下创建而不符合使用JPA框架创建持久化实体之间的关联关系(即因为业务需求,所建立库表不符合数据库建库规范),而又需要进行多表关联进行复杂查询时,很实用。
特别说明:上面所举的例子只是单纯为了演示此方案,因为上面表关联之简单要获得如上的结果使用JPA框架也可轻松实现。
解决方案二:
修改DAO层的JPA处理接口类:
/**
* 根据书籍主键查询书籍详细信息
*
* @param pkid 书籍主键值
* @return 书籍详细信息
*/
@Query(value = "select new com.Vo.CustomModel(a.bookpkid,a.bookcode,a.bookname,b.authorname) " +
"FROM BookInfo a LEFT JOIN AuthorInfo b ON a.authorid=b.authorid WHERE a.bookpkid=:pkid")
List<CustomModel> selectModelByPkid(@Param("pkid") BigDecimal pkid);
注意:这次的CustomModel最好写全路径,程序有可能无法定位到该类
其它解决方案:持续更新中······