2. 如何由PoJo类生成数据库中的表
首先可以根据实体间关系及对应关系设计表,写出相应建表SQL语句,执行生成表,或者可以用hibernate映射来生成表,在http://kylinsoong.iteye.com/admin/blogs/739502一开始说明怎样通过hibernate mapping生成表;
我们例子生成表如下图所示:
共生成11张表,7个实体对应7张表及5个一对多关联表(User和Event,User和Friend,Event和Property,Wife和Pet,Pet和Property)。
3. FetchType Lazy及Eager测试
一对一下FetchType Lazy
设定User和UserCard,Friend和UserCard,Wife和UserCard对应FetchType 设为lazy,User和Wife设为Eager,运行如下代码;
public static void main(String[] args) throws Throwable {
EntityManagerFactory emf = JPAUtil.createEMF("oracle");
EntityManager em = emf.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
User user = em.find(User.class, new Long(1));
System.out.println(user.getFriends().get(0).getUserCard().getClass());
System.out.println(user.getUserCard().getClass());
System.out.println(user.getWife().getClass());
System.out.println(user.getWife().getUserCard().getClass());
t.commit();
em.close();
emf.close();
}
运行结果:
class com.tibco.hibernate.po.UserCard_$$_javassist_0
class com.tibco.hibernate.po.UserCard_$$_javassist_0
class com.tibco.hibernate.po.Wife
class com.tibco.hibernate.po.UserCard_$$_javassist_0
从结果可以看到一对一下FetchType 设为Lazy,对应熟悉值为该属性的代理,当然设为Eager后就不为代理,下面代码通过反射对代理类进行研究
public static void main(String[] args) throws Throwable {
EntityManagerFactory emf = JPAUtil.createEMF("oracle");
EntityManager em = emf.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
User user = em.find(User.class, new Long(1));
showProxyClass(user.getUserCard());
t.commit();
em.close();
emf.close();
}
private static void showProxyClass(Object bean) throws Throwable {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
System.out.println(propertyDescriptor.getDisplayName() + " " + propertyDescriptor.getReadMethod());
}
}
运行结果:
cardNumber public final java.lang.String com.tibco.hibernate.po.UserCard_$$_javassist_0.getCardNumber()
class public final native java.lang.Class java.lang.Object.getClass()
handler null
hibernateLazyInitializer public final org.hibernate.proxy.LazyInitializer com.tibco.hibernate.po.UserCard_$$_javassist_0.getHibernateLazyInitializer()
id public final java.lang.Long com.tibco.hibernate.po.UserCard_$$_javassist_0.getId()
可以看出除去CardNumber属性cardNumber ,id 外,又多出了handler和hibernateLazyInitializer属性,且cardNumber ,id 对应的Get方法不是getId()和getCardNumber(),而是UserCard_$$_javassist_0.getId()和UserCard_$$_javassist_0.getCardNumber(),是其代理类中的Get方法;继续研究.UserCard_$$_javassist_0类:
private static void showProxyClass(Object bean) throws Throwable {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if(propertyDescriptor.getDisplayName().compareTo("hibernateLazyInitializer")== 0) {
Class c = Class.forName("com.tibco.hibernate.po.UserCard_$$_javassist_0");
for(Class cl :c.getInterfaces()) {
System.out.println(cl);
}
for(Method m : c.getMethods()) {
if(m.getName().startsWith("get")){
System.out.println(m.getName());
}
}
Object obj = propertyDescriptor.getReadMethod().invoke(bean, new Object[]{});
System.out.println(obj.getClass());
}
}
}
运行结果:
interface org.hibernate.proxy.HibernateProxy
interface javassist.util.proxy.ProxyObject
getHibernateLazyInitializer
getCardNumber
getId
getClass
class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer
看到UserCard_$$_javassist_0实现了两个接口getHibernateLazyInitializer()方法可以获取
一对多下FetchType Lazy
User和Event,Event和Property,Wife和Pet,Pet和Property FetchType为Lazy,User和Friend FetchType为Eager,运行如下代码
public static void main(String[] args) throws Throwable {
EntityManagerFactory emf = JPAUtil.createEMF("oracle");
EntityManager em = emf.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
User user = em.find(User.class, new Long(1));
System.out.println(user.getEvents().getClass());
System.out.println(user.getFriends().getClass());
System.out.println(user.getEvents().get(0).getProperties().getClass());
System.out.println(user.getWife().getPets().getClass());
System.out.println(user.getWife().getPets().get(0).getProperties().getClass());
t.commit();
em.close();
emf.close();
}
运行结果:
class org.hibernate.collection.PersistentBag
class org.hibernate.collection.PersistentBag
class org.hibernate.collection.PersistentBag
class org.hibernate.collection.PersistentBag
class org.hibernate.collection.PersistentBag
发现一对多和一对一以Lazy的处理方式是不一样的。
说明一个常见错误:
上述例子中总共有User和Event,User和Friend,Event和Property,Wife和Pet,Pet和Property 5对一对多关系,已经将User和Friend FetchType为Eager,此时在将剩余4对其中任何一项设为Eager,运行程序,会出现“Unable to build EntityManagerFactory”错误,错误的根本原因是“cannot simultaneously fetch multiple bags”
如下所示错误堆栈的打印输出:
Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: com.tibco.hibernate.po] Unable to build EntityManagerFactory
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:677)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:126)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:52)
at com.tibco.hibernate.jpa.JPAUtil.createEMF(JPAUtil.java:20)
at com.tibco.hibernate.jpa.JPAClient.main(JPAClient.java:26)
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:94)
at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:98)
at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:66)
at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:56)
at org.hibernate.loader.entity.BatchingEntityLoader.createBatchingEntityLoader(BatchingEntityLoader.java:126)
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1765)
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1769)
at org.hibernate.persister.entity.AbstractEntityPersister.createLoaders(AbstractEntityPersister.java:3002)
at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(AbstractEntityPersister.java:2995)
at org.hibernate.persister.entity.SingleTableEntityPersister.postInstantiate(SingleTableEntityPersister.java:712)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:329)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1341)
at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:867)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:669)
... 4 more
错误的原因是:如果一个实体里要映射多个集合实体时,我们不能把两个集合的的FetchType设置为EAGER,此时只能设置为LAZY,否则会报:cannot simultaneously fetch multiple bags。
解决办法:将一个FetchType设置为Lazy
4 Lazy和Eager的主要区别在于效率,当效率要求高时必须用Lazy,但有些时候既要要求高效率(Lazy),但相应Lazy标记的属性不要是相关的代理,这时就需要把相关属性代理转换成原属性,下面是一个实现这个功能的类,主要思想是Java反射:
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Kylin Soong
*
*/
public class POPropertyUtil {
public static Object setBeanProperty(Object bean) throws Throwable {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if(propertyDescriptor.getReadMethod() == null) {
continue;
}
Object obj = propertyDescriptor.getReadMethod().invoke(bean, new Object[]{});
if(obj == null) {
continue;
}
if(obj.getClass().getPackage().getName().compareTo("com.staffware.frameworks.po") == 0) {
setBeanProperty(obj);
}
if(obj instanceof List) {
List lists = (List) obj;
for(Object o : lists) {
setBeanProperty(o);
}
}
if(isProxyProperty(obj)) {
if(propertyDescriptor.getWriteMethod() != null) {
Object invokeObj = clone(obj, propertyDescriptor);
propertyDescriptor.getWriteMethod().invoke(bean, new Object[]{invokeObj});
} else {
throw new RuntimeException(propertyDescriptor.getDisplayName() + "has a null getWriteMethod");
}
}
}
return bean;
}
private static Object clone(Object obj, PropertyDescriptor propertyDescriptor) throws Throwable {
if(obj instanceof List) {
List lists = (List) obj;
List newList = new ArrayList();
for(Object o : lists) {
newList.add(o);
}
return newList;
} else {
Object newObj = propertyDescriptor.getPropertyType().newInstance();
return replication(newObj, obj);
}
}
private static void invokeCollectionEntity(Object o) throws Throwable {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if(propertyDescriptor.getReadMethod() != null) {
propertyDescriptor.getReadMethod().invoke(o, new Object[]{});
}
}
}
private static Object replication(Object newObj, Object oldObj) throws Throwable {
PropertyDescriptor[] newPropertyDescriptors = Introspector.getBeanInfo(newObj.getClass()).getPropertyDescriptors();
PropertyDescriptor[] oldPropertyDescriptors = Introspector.getBeanInfo(oldObj.getClass()).getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : newPropertyDescriptors) {
if(propertyDescriptor.getWriteMethod() != null) {
Method readMethod = getReadMethod(propertyDescriptor.getDisplayName(), oldPropertyDescriptors);
propertyDescriptor.getWriteMethod().invoke(newObj, new Object[]{readMethod.invoke(oldObj, new Object[]{})});
}
}
return newObj;
}
private static Method getReadMethod(String displayName,
PropertyDescriptor[] oldPropertyDescriptors) {
for(PropertyDescriptor propertyDescriptor : oldPropertyDescriptors) {
if(propertyDescriptor.getDisplayName().compareTo(displayName) == 0) {
if(propertyDescriptor.getReadMethod() == null){
throw new RuntimeException("Get Read method return a null value: " + propertyDescriptor.getPropertyType() + " " + propertyDescriptor.getDisplayName());
}
return propertyDescriptor.getReadMethod();
}
}
throw new RuntimeException("can not find read method in:" + oldPropertyDescriptors);
}
private static boolean isProxyProperty(Object obj) throws Throwable {
String name = obj.getClass().getName();
return name.contains("_$$_javassist_") || name.contains("org.hibernate.collection.PersistentBag");
}
}
对上述类进行测试,如下代码:
public static void main(String[] args) throws Throwable {
EntityManagerFactory emf = JPAUtil.createEMF("oracle");
EntityManager em = emf.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
User user = em.find(User.class, new Long(1));
System.out.println(user.getEvents().getClass());
System.out.println(user.getUserCard().getClass());
System.out.println(user.getWife().getClass());
POPropertyUtil.setBeanProperty(user);
System.out.println(user.getEvents().getClass());
System.out.println(user.getUserCard().getClass());
System.out.println(user.getWife().getClass());
// t.commit();
em.close();
emf.close();
}
运行结果:
class org.hibernate.collection.PersistentBag
class com.tibco.hibernate.po.UserCard_$$_javassist_0
class com.tibco.hibernate.po.Wife_$$_javassist_3
class java.util.ArrayList
class com.tibco.hibernate.po.UserCard
class com.tibco.hibernate.po.Wife
既将相应的代理还原了;
5 说明一个常见的错误:“detached entity passed to persist”,先还原错误:
如上在获取User对象后给其设定ID值,然后保存如下代码
public static void main(String[] args) throws Throwable {
EntityManagerFactory emf = JPAUtil.createEMF("oracle");
EntityManager em = emf.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
User user = JPAClient.getUser();
user.setId(new Long(1212));
em.persist(user);
t.commit();
em.close();
emf.close();
}
运行代码抛出异常,异常堆栈信息如下:
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.tibco.hibernate.po.User
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:226)
at com.tibco.hibernate.jpa.JPAClient.main(JPAClient.java:33)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.tibco.hibernate.po.User
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:646)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:620)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:624)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
错误原因是:实体的状态变化EntityManager是根据实体的ID来判断,一般一个实体Persist的过程就是给他分配一个ID的过程,当一个实体有唯一ID时,EntityManager就认为它已经处于游离(detached )状态,而游离态实例不能够持久化