使用JPA和Hibernate延遲載入實體屬性的最佳方法 - Vlad Mihalcea

banq發表於2019-02-21

獲取實體時,也會載入所有屬性。這是因為每個隱式使用@Basic實體屬性提取策略都預設FetchType.EAGER。
但是,屬性獲取策略可以設定為FetchType.LAZY,在這種情況下,實體屬性只有在第一次訪問時才載入,透過select語言的執行。

@Basic(fetch = FetchType.LAZY)

僅此配置是不夠的,因為Hibernate需要位元組碼檢測來攔截屬性訪問請求並按需發出select語句。

使用Maven位元組碼增強外掛時,enableLazyInitialization必須將配置屬性設定true為以下示例中所示:

<plugin>
    <groupId>org.hibernate.orm.tooling</groupId>
    <artifactId>hibernate-enhance-maven-plugin</artifactId>
    <version>${hibernate.version}</version>
    <executions>
        <execution>
            <configuration>
                <failOnError>true</failOnError>
                <enableLazyInitialization>true</enableLazyInitialization>
            </configuration>
            <goals>
                <goal>enhance</goal>
            </goals>
        </execution>
    </executions>
</plugin>


有了這個配置,所有JPA實體類都將使用延遲屬性獲取進行檢測。此過程發生在構建時,就在從關聯的原始檔編譯實體類之後。
與儲存大量資料的列型別處理當屬性延遲抓取機構是非常有用的(例如BLOB,CLOB,VARBINARY)。這樣,可以在不自動從基礎大型列型別中載入資料的情況下獲取實體,從而提高效能。

演示
為了演示屬性延遲提取的工作原理,以下示例將使用Attachment可以儲存任何媒體型別的實體(例如PNG,PDF,MPEG)。

@Entity @Table(name = "attachment")
public class Attachment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    @Enumerated
    @Column(name = "media_type")
    private MediaType mediaType;
 
    @Lob
    @Basic(fetch = FetchType.LAZY)
    private byte[] content;
 
    //Getters and setters omitted for brevity
}


在每個實體負載上都要急切地獲取諸如實體識別符號,名稱或媒體型別之類的屬性。另一方面,只有在被應用程式程式碼訪問時,才應該懶惰地獲取媒體檔案內容。
在檢測Attachment實體後,類位元組碼更改如下:

@Transient
private transient PersistentAttributeInterceptor
    $$_hibernate_attributeInterceptor;
 
public byte[] getContent() {
    return $$_hibernate_read_content();
}
 
public byte[] $$_hibernate_read_content() {
    if ($$_hibernate_attributeInterceptor != null) {
        this.content = ((byte[])
            $$_hibernate_attributeInterceptor.readObject(
                this, "content", this.content));
    }
    return this.content;
}


執行以下測試用例時:

Attachment book = entityManager.find(
    Attachment.class, bookId);
 
LOGGER.debug("Fetched book: {}", book.getName());
 
assertArrayEquals(
    Files.readAllBytes(bookFilePath),
    book.getContent()
);


Hibernate生成以下SQL查詢:

SELECT a.id AS id1_0_0_,
       a.media_type AS media_ty3_0_0_,
       a.name AS name4_0_0_
FROM   attachment a
WHERE  a.id = 1
 
-- Fetched book: High-Performance Java Persistence
 
SELECT a.content AS content2_0_
FROM   attachment a
WHERE  a.id = 1


因為它標記有FetchType.LAZY註釋並且啟用了延遲提取位元組碼增強,所以content不會提取該列以及初始化Attachment實體的所有其他列。只有當資料訪問層嘗試訪問該content屬性時,Hibernate才會發出輔助選擇以載入此屬性。
就像FetchType.LAZY關聯一樣,這種技術很容易出現N + 1個查詢問題,因此建議謹慎行事。位元組碼增強機制的一個細微缺點是所有實體屬性,而不僅僅是標記有FetchType.LAZY註釋的屬性,將被轉換,如前所述。

獲取子實體
另一種避免載入相當大的表列的方法是將多個子實體對映到同一個資料庫表。
比如BaseAttachment有兩個子類Attachment實體和AttachmentSummary。

使用JPA和Hibernate延遲載入實體屬性的最佳方法 - Vlad Mihalcea

無論是Attachment實體和AttachmentSummary子實體繼承BaseAttachment所有公共屬性

@MappedSuperclass
public class BaseAttachment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    @Enumerated
    @Column(name = "media_type")
    private MediaType mediaType;
 
    //Getters and setters omitted for brevity
}

雖然AttachmentSummary擴充套件BaseAttachment而沒有宣告任何新屬性:

@Entity @Table(name = "attachment")
public class AttachmentSummary
    extends BaseAttachment {}


Attachment實體繼承超類BaseAttachment所有基本屬性並對映content列。

@Entity @Table(name = "attachment")
public class Attachment
    extends BaseAttachment {
 
    @Lob
    private byte[] content;
 
    //Getters and setters omitted for brevity
}


當抓取AttachmentSummary子實體時:

AttachmentSummary bookSummary = entityManager.find(
    AttachmentSummary.class, bookId);


產生SQL:

SELECT a.id as id1_0_0_,
       a.media_type as media_ty2_0_0_,
       a.name as name3_0_0_
FROM attachment a
WHERE  a.id = 1


當抓取Attachment 實體時:

Attachment book = entityManager.find(
    Attachment.class, bookId);

Hibernate將從底層資料庫表中獲取所有列:

SELECT a.id as id1_0_0_,
       a.media_type as media_ty2_0_0_,
       a.name as name3_0_0_,
       a.content as content4_0_0_
FROM attachment a
WHERE  a.id = 1


結論
對於延遲獲取實體屬性,您可以使用位元組碼增強或子實體兩種方式。
雖然位元組碼檢測允許您每個表只使用一個實體,但子實體更靈活,甚至可以提供更好的效能,因為它們在讀取實體屬性時不涉及攔截器呼叫。
子實體方式其實就是將由大資料如圖片或檔案的物件和文字小資料的物件分開。
在讀取資料時,子實體與DTO投影非常相似。但是,與DTO投影不同,子實體可以跟蹤狀態更改並將它們傳播到資料庫。

相關文章