1. 程式人生 > >Spring Data Jpa框架自定義查詢語句返回自定義實體的解決方案

Spring Data Jpa框架自定義查詢語句返回自定義實體的解決方案

在使用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最好寫全路徑,程式有可能無法定位到該類

其它解決方案:持續更新中······