今天碰到了一个 奇怪的问题 Illegal attempt to associate a collection with two open sessions。 在最后提交逻辑里。 以前也说过, 现在这个项目的事务是利用AOP 加到DAO 层上的, 在EJB 的方法中很容易在一个方法里面多次调用DAO 层。 并且由于是在做mass change 之前要取出原业务对象 然后在merge 上mass change 的数据。 然后在做N 多的校验, 到最后校验通过提交数据。
这个异常的 log 如下:
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
at org.hibernate.exception.NestableRuntimeException.<init>(NestableRuntimeException.java:104) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.collection.AbstractPersistentCollection.setCurrentSession(AbstractPersistentCollection.java:403) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.OnUpdateVisitor.processCollection(OnUpdateVisitor.java:43) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:101) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:61) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:55) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.AbstractVisitor.process(AbstractVisitor.java:123) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:293) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:495) ~[hibernate-3.2.6.ga.jar:3.2.6.ga]
进入 OnUpdateVisitor.java的第 43 行代码 可以看到
PersistentCollection wrapper = (PersistentCollection) collection;
if ( wrapper.setCurrentSession(session) ) {
的确在我们的DAO 中要保存的对象有一个 List 成员 所以 这个 wrapper 就是Hibernate 在 load 出那个 entity 的时候产生出的对应的PersistentCollection 集合了, 它应该还保留当时的那个DAO 用到的。 我们再到AbstractPersistentCollection.setCurrentSession 方法里看看:
public final boolean setCurrentSession(SessionImplementor session) throws HibernateException { if (session==this.session) { return false; } else { if ( isConnectedToSession() ) { CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this); if (ce==null) { throw new HibernateException( "Illegal attempt to associate a collection with two open sessions" ); } else { throw new HibernateException( "Illegal attempt to associate a collection with two open sessions: " + MessageHelper.collectionInfoString( ce.getLoadedPersister(), ce.getLoadedKey(), session.getFactory() ) ); } } else { this.session = session; return true; } } }
在这个方法里面程序的行为应该是:
1, isConnectedToSession() 应该返回为true, 当然这个Session 不是当前这个Session,而是开始load 的那个DAO 对应的Session,
2,接下来一行 CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(this);就返回 null 了,
3 ,if (ce==null) {
throw new HibernateException(
"Illegal attempt to associate a collection with two open sessions"
);
}
解决的方法 有两个:
1, 首先 Object mergedEntity = Session.merge(entity), 然后 saveOrUpdate 这个 mergedEntity。
2, Session.lock (entity, Lock.NONE)。
其实这个 lock 的用法还是从 这个地方google来的, 在此留个记号,需要做进一步的学习。