對EntityManager進行封裝以簡化JPA操作
阿新 • • 發佈:2019-01-05
要點:通過反射機制得到父類的泛型引數
如果你用過 Seam,那就一定知道 Seam 又對JPA進行了封裝,使得我們只需要讓實體bean繼承 Home<T>類而不需要編寫任何程式碼就能實現CRUD操作。例如我們想持久化Customer實體bean,則只需要編寫如下程式碼:
public class CustomerHome extends Home<Customer> {
}
就可以直接注入 CustomerHome 物件,然後呼叫
customer.setInstance(new Customer()); // 讓此CustomerHome物件管理一個新的Customer實體 customer.persist(); // 持久化 customer.delete(); // 同 entityManager.remove() customer.save(); // 同 entityManager.merge()
也就是說,只需要把你要進行CRUD操作的 entity bean 繼承自Home類,然後就可以無需編寫任何程式碼直接操作 EntityManager 進行持久化操作,從而減少了程式碼量。現在我們就自己實現一個簡化的Home類。
先把最終程式碼貼出來:
package cn.fh.orm; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; /** * 對EntityManager操作進行封裝. * <p>只需簡單地繼承此類,就可以實現CRUD操作 * @author whf * * @param <T> 指定此Home類要管理的實體物件 */ @Repository public abstract class Home<T> { /** * 實體主鍵 */ private Integer id; /** * 實體物件 */ private T instance; /** * 實體的Class物件 */ private Class<T> instanceClass; @PersistenceContext private EntityManager em; @Transactional(readOnly = false) public void persist() { if (null == instance) { throw new RuntimeException("未指定實體"); } em.persist(instance); } @Transactional(readOnly = false) public void update() { if (null == instance) { throw new RuntimeException("未指定實體"); } em.merge(instance); } @Transactional(readOnly = false) public void delete() { if (null == instance) { throw new RuntimeException("未指定實體"); } instance = em.merge(instance); em.remove(instance); } /** * 清空實體 */ public void clear() { instance = null; instanceClass = null; id = null; } /** * 根據主鍵查詢指定實體 * @return */ @Transactional(readOnly = true) private T find() { if (null == id) { throw new RuntimeException("未指定實體id"); } return em.find(instanceClass, id); } public Integer getId() { return id; } public void setId(Integer id) { clear(); this.id = id; } /** * 如果有實體,則返回該物件. * 如果沒有,則根據實體id執行查詢. * 如果Class物件為空,則通過反射得到範形引數的Class物件 * * @return 返回Home所管理的實體物件 * @throws IlligalArgumentException 無法得到範形引數的Class物件 */ @SuppressWarnings("unchecked") public T getInstance() { if (null != instance) { return instance; } // 通過反射得到T的實際Class物件 if (null == instanceClass) { // 得到父類的範型引數的Class物件 Type type = getClass().getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType parmType = (ParameterizedType)type; instanceClass = (Class<T>)parmType.getActualTypeArguments()[0]; } else { throw new IllegalArgumentException("Could not guess entity class by reflection"); } } instance = find(); return instance; } public void setInstance(T instance) { clear(); this.instance = instance; } }
以上程式碼實現了之前說提到的功能。這個類最核心的部分是 getInstance()方法。我們知道,要想呼叫 EntityManager 的 find() 方法,則需要提供一個型別為Class的引數。如:
entityManager.find(Customer.class, 1); // 查詢主鍵為1的Customer
所以,要想實現只需讓實體繼承Home<T>類就能進行CRUD操作,核心的問題就是如何在子類中獲取 Home<T> 中的泛型引數<T>的真實型別。如,
public class CustomerHome extends Home<Customer> { }
Home<T>類必須知道是 T 實際上是Customer,才能給 EntityManager的 find() 方法傳遞正確的 Class 引數。這裡可以通過反射實現:
// 通過反射得到T的實際Class物件
if (null == instanceClass) {
// 得到父類的範型引數的Class物件
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType parmType = (ParameterizedType)type;
instanceClass = (Class<T>)parmType.getActualTypeArguments()[0];
} else {
throw new IllegalArgumentException("Could not guess entity class by reflection");
}
}
我們來分析一下,為什麼在父類中編寫 getClass().getGenericSuperclass() 得到的恰好就是 Home<T> 中T的實際型別呢?因為當我們讓 CustomerHome 繼承 Home<T> 後,通過 CustomerHome 呼叫 getInstance() 時,由於子類沒有重寫該方法,因此會執行父類的 getInstance()程式碼,而這時的執行環境是子類,因此getClass().getGenericSuperclass() 得到的恰好就是 Home<T> 中T的實際型別。
有了我們自己編寫的 Home<T> 類之後,我們就可以在 Spring 環境下方便地使用 JPA了。以上程式碼就是針對 Spring 編寫的。如果你用過 Seam 框架,你肯定知道它還有一個 Query<T> 元件,用於執行JPA查詢操作。大家可以參考著Seam的原始碼自己實現一個玩玩。