首页 > 代码库 > 对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的实际类型。

这一部分参考了 Seam 的源码(https://github.com/VaclavDedik/jboss-seam/blob/Seam_2_3/jboss-seam/src/main/java/org/jboss/seam/framework/Home.java),这给了我很大启发,反射机制让Java如此优秀,无数的框架都建立在反射之上。


有了我们自己编写的 Home<T> 类之后,我们就可以在 Spring 环境下方便地使用 JPA了。以上代码就是针对 Spring 编写的。如果你用过 Seam 框架,你肯定知道它还有一个 Query<T> 组件,用于执行JPA查询操作。大家可以参考着Seam的源码自己实现一个玩玩。

对EntityManager进行封装以简化JPA操作