Tentative illégale d'associer une collection à deux sessions ouvertes
-
03-07-2019 - |
Question
J'essaie d'ajouter un pojo à une collection d'un autre pojo. Je suis sûr que je commets une erreur vraiment stupide quelque part le long des lignes, mais je ne peux pas comprendre comment le résoudre.
J'ai un tableau LookupTable contenant une liste de colonnes:
public class LookupTable {
private long id;
// More properties go here...
private List<Column> columns;
public void addColumn(Column column) {
this.columns.add(column);
}
// More methods go here...
}
Dans ma configuration d'hibernation, j'ai:
<class name="LookupTable" table="ARR_LOOKUP_TABLE">
<id name="id" column="ID">
<generator class="native"/>
</id>
<!-- Some properties here -->
<bag name="columns" cascade="all,delete-orphan" access="field">
<key column="LOOKUP_TABLE" not-null="true"/>
<one-to-many class="Column"/>
</bag>
</class>
<class name="Column" table="ARR_LOOKUP_COLUMN">
<id name="id" column="ID">
<generator class="native"/>
</id>
<!-- Some properties here -->
</class>
Dans mon fichier de configuration Spring, j'ai:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="managers" expression="execution(public * com.foo.*Manager.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="managers"/>
</aop:config>
Et enfin, le code où tout cela échoue dans ma classe de gestionnaire (com.foo.LookupTableManager):
public void addColumnToTable(Column column, long tableId) {
LookupTable lookupTable = this.lookupTableDao.findById(tableId);
lookupTable.addColumn(column);
this.lookupTableDao.saveOrUpdate(lookupTable);
}
La variable lookupTableDao fait ici référence à une classe DAO simple qui étend HibernateDaoSupport.
L'erreur que je reçois est la suivante:
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
at org.hibernate.collection.AbstractPersistentCollection.setCurrentSession(AbstractPersistentCollection.java:410)
at org.hibernate.event.def.OnUpdateVisitor.processCollection(OnUpdateVisitor.java:43)
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:101)
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:61)
at org.hibernate.event.def.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:55)
at org.hibernate.event.def.AbstractVisitor.process(AbstractVisitor.java:123)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:293)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:495)
at com.foo.AbstractDao.saveOrUpdate(AbstractDao.java:29)
at com.foo.LookupTableManager.addColumnToTable(LookupTableManager.java:338)
... etc ...
OK, je comprends le message de base que je reçois. Mais ce que je ne comprends pas, c’est où j’obtiens la deuxième session ... Quelqu'un peut-il m'aider à cet égard?
J'utilise Hibernate 3.2.6.ga, Spring 2.5.5 et Tomcat 6.0
La solution
Il s’est avéré que je n’avais pas du tout de transaction. J'ai utilisé presque la même configuration de transaction dans l'un de mes autres fichiers de configuration.
La coupure de point située là-bas était également appelée "gestionnaires", de sorte que mon conseiller ici faisait référence à la coupure de point dans l'autre fichier.
Renommer le pointcut a résolu mon problème.
Autres conseils
Je suppose que l'appel lookupTableDao.findById
récupère votre objet dans une session, mais que le lookupTableDao.saveOrUpdate
est différent - comment obtenez-vous le < code> Session , via Spring?
D'où provient l'objet Column
- cet objet existe-t-il déjà dans la base de données ou est-il neuf?
Le problème était lié au mappage de filtre de la vue Open Session In. Il créait une session sur getSession Et autre sur la sauvegarde.
Vous ne pouvez modifier que singleSession comme faux défaut son vrai
Apparemment, l’utilisation de la fusion résoudra probablement le problème
myInstance = (myInstanceClass) Session.merge(myInstance);
J'ai eu le même problème avec le code suivant: Je voulais mettre à jour une certaine entité Store. Je le récupérais de cette méthode:
@Override
public Store getStoreByStoreId(final long storeId) {
getHibernateTemplate().execute(new HibernateCallback<Store>() {
@Override
public StoredoInHibernate(Session session) throws HibernateException, SQLException {
return (Store) session.createCriteria(Store.class)
.add(Restrictions.eq(Store.PROP_ID, storeId))
.uniqueResult();
}
});
}
Ensuite, je mettais à jour le magasin selon la méthode suivante:
@Override
public void updateStoreByStoreId(final long storeId) {
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
Store toBeUpdated = getStoreByStoreId(storeId);
if (toBeUpdated != null){
// ..change values for certain fields
session.update(toBeUpdated);
}
return null;
}
});
}
Il s’avère que j’ai eu la "tentative illégale ...". erreur car la première session de la méthode get ne fermait pas et j'essayais de mettre à jour le magasin avec l'autre session. La solution pour moi était de déplacer l’appel de récupération du magasin à mettre à jour dans la méthode de mise à jour.
@Override
public void updateStoreByStoreId(final long storeId) {
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
Store toBeUpdated = (Store) session.createCriteria(Store.class)
.add(Restrictions.eq(Store.PROP_ID, storeId))
.uniqueResult();
if (toBeUpdated != null){
// ..change values for certain fields
session.update(toBeUpdated );
}
return null;
}
});
}
Si vous voulez seulement une transaction sur une partie de votre code, vous pouvez utiliser quelque chose comme ceci:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
et dans la classe:
@Autowired private SessionFactory sessionFactory;
Plus tard, autour du code devant faire les choses dans la même transaction:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Stuff stuff = getStuff();
manipulate(stuff);
send(stuff);
save(stuff);
tx.commit();
Je l'utilise à l'intérieur d'une boucle dans des travaux par lots de longue durée.
J'utilisais le serveur IIS Windows Server et le simple fait de changer le mode Pipeline géré AppPool de Intégré à Classique a résolu mon problème.
Merci