要点:通过反射机制得到父类的泛型参数
如果你用过 Seam,那就一定知道 Seam 又对JPA进行了封装,使得我们只需要让实体bean继承 Home<T>类而不需要编写任何代码就能实现CRUD操作。例如我们想持久化Customer实体bean,则只需要编写如下代码:
public class CustomerHome extends Home<Customer> { }
customer.setInstance(new Customer()); // 让此CustomerHome对象管理一个新的Customer实体 customer.persist(); // 持久化 customer.delete(); // 同 entityManager.remove() customer.save(); // 同 entityManager.merge()
先把最终代码贴出来:
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; } }
entityManager.find(Customer.class, 1); // 查找主键为1的Customer
public class CustomerHome extends Home<Customer> { }
// 通过反射得到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的源码自己实现一个玩玩。