1. 程式人生 > >對EntityManager進行封裝以簡化JPA操作

對EntityManager進行封裝以簡化JPA操作

要點:通過反射機制得到父類的泛型引數

如果你用過 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的原始碼自己實現一個玩玩。