I'm writing a simple project, a business app written in Swing, using Hibernate for back-end. I come from Spring, that gave me easy ways to use hibernate and transactions. Anyway I managed to have Hibernate working. Yesterday, while writing some code to delete a bean from DB, I got this:
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
The deletion code is simply:
Session sess = HibernateUtil.getSession();
Transaction tx = sess.beginTransaction();
try {
tx.begin();
sess.delete(ims);
} catch (Exception e) {
tx.rollback();
throw e;
}
tx.commit();
sess.flush();
and my HibernateUtil.getSession() is:
public static Session getSession() throws HibernateException {
Session sess = null;
try {
sess = sessionFactory.getCurrentSession();
} catch (org.hibernate.HibernateException he) {
sess = sessionFactory.openSession();
}
return sess;
}
additional details: I never close a hibernate session in my code, just on application closing. Is this wrong? Why do I get this on delete (only for that bean, others do work), and I don't on other operations (Insert, query, update)?
I read around and I tried to modify my getSession method simply in a sessionFactory.getCurrentSessionCall(), but I got: org.hibernate.HibernateException: No CurrentSessionContext configured!
Hibernat conf:
<hibernate-configuration>
<session-factory >
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/joptel</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">******</property>
<property name="hibernate.connection.pool_size">1</property>
<property name="show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
..mappings..
</session-factory>
</hibernate-configuration>
I wanted to ask you one thing, why are you trying to use "OpenSession" method?
public static Session getSession() throws HibernateException {
Session sess = null;
try {
sess = sessionFactory.getCurrentSession();
} catch (org.hibernate.HibernateException he) {
sess = sessionFactory.openSession();
}
return sess;
}
You don't have to call openSession(), because getCurrentSession() method is always returns current session (Thread in case if you have configured it to be).
I got it!...
You have to specify current context in your hibernate.cfg.xml file
it should be:
<property name="hibernate.current_session_context_class">thread</property>
No CurrentSessionContext configured
Read the reference guide on Contextual Sessions. You're required to configure some provided or custom strategy for this. In a hibernate.cfg.xml, you'd configure it with
<property name="hibernate.current_session_context_class">...</property>
You'd probably want to use "thread" as the value to get per-thread sessions. When using Spring, it automatically sets this to a SpringSessionContext, allowing Spring to easily integrate Hibernate with its transaction management framework.
I come from Spring, that gave me easy ways to use hibernate and transactions.
If you're familiar with Spring, why aren't you using it to manage Hibernate here? You must already know how simple and foolproof it makes it.
I never close a hibernate session in my code, just on application closing. Is this wrong?
Yes, this is very wrong. Every session not closed is an open database connection, so your app is currently hemorrhaging connections.
Illegal attempt to associate a collection with two open sessions
That means exactly what it says. You tried to do some persistence operation (save(), update(), delete()) on something that was already associated to a different session. That's what will happen when you go randomly opening new sessions whenever, which is what's happening since SessionFactory.getCurrentSession() will always fail when no "current session context" is set. In general, never open a session just because one wasn't already there. You need to have well-defined strategies for opening and closing sessions and never let anything open a session outside of these "strategies". That's a sure path to resource leaks and errors like the one you've encountered.
I faced the same problem when I am working on a portal where I am using spring remoting with hibernate.
This kind of problem arise only if when the called service method contains multiple DAO calls that hit database with hibernate session.
And the solution is set the #Transaction annotation for those methods with multiple DAO calls. (Implies all the DOA calls with in this method should be under one transaction.)
Related
I have a requirement to use 2 different databases within single DAO class. One of the databases is read/write while the other is read only.
I have created 2 data sources, 2 session factories and 2 transaction managers (transaction manager for the read/write database is the platform transaction manager) for these databases. I am using #Transactional on the service method to configure Spring for transaction management.
We are getting random Session is closed! exceptions when we call sessionFactory.getCurrentSession() in the DAO class ( I can not always produce it, it sometimes works ok, sometimes gets error) :
org.hibernate.SessionException: Session is closed!
at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:133)
at org.hibernate.internal.SessionImpl.setFlushMode(SessionImpl.java:1435)
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:99)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
I don't have a requirement to use global transaction (XA), I just want to query 2 different databases.
I have read this thread, it suggests injecting two separate session factories in the DAO layer as we do now: Session factories to handle multiple DB connections
Also AbstractRoutingDataSource does not work for single Dao class as per this answer: https://stackoverflow.com/a/7379048/572380
Example code from my dao looks like this:
Criteria criteria = sessionFactory1.getCurrentSession().createCriteria(MyClass.class);
criteria.add(Restrictions.eq("id", id));
criteria.list();
criteria = sessionFactory2.getCurrentSession().createCriteria(MyClass2.class); // generates random "Session is closed!" error.
criteria.add(Restrictions.eq("id", id));
criteria.list();
I have also tried using "doInHibernate" method. But the session passed to it is also randomly throwing "Session is closed!" exceptions:
#Autowired
protected HibernateTemplate hibernateTemplate;
#SuppressWarnings("unchecked")
protected List<Map<String, Object>> executeStaticQuery(final String sql) {
HibernateCallback<List<Map<String, Object>>> hibernateCallback = new HibernateCallback<List<Map<String, Object>>>() {
#Override
public List<Map<String, Object>> doInHibernate(Session session) throws HibernateException {
SQLQuery query = session.createSQLQuery(sql);
query.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
return query.list();
}
};
return hibernateTemplate.execute(hibernateCallback);
}
So you do have the below code in your application? If you don't you should add it,might be it is causing the problem.
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
Remove this property as mentioned below
<property name="current_session_context_class">thread</property>
You are overriding Spring which sets this to SpringSessionContext.class. This is almost certainly at least part of your problem.
Spring manages your session objects. These session objects that it manages are tied to Spring transactions. So the fact that you are getting that error means to me that it is most likely due to how you are handling transactions.
in other words don't do this
Transaction tx = session.beginTransaction();
unless you want to manage the life cycle of the session yourself in which case you need to call session.open() and session.close()
Instead use the framework to handle transactions. I would take advantage of spring aspects and the declarative approach using #Transactional like I described earlier its both cleaner and more simple, but if you want to do it pragmatically you can do that with Spring as well. Follow the example outlined in the reference manual. See the below link:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/orm.html#orm-hibernate-tx-programmatic
Above error suggest, you are not able to get the session as session is closed sometimes. You can use openSession() method instead of getCurrentSession() method.
Session session = this.getSessionFactory().openSession();
session.beginTransaction();
// Your Code Here.
session.close();
Drawback with this approach is you will explicitly need to close the session.
In single threaded environment it is slower than getCurrentSession().
Check this Link Also:- Hibernate Session is closed
The problem is that you have a single hibernate session and two data stores. The session is bound to the transaction. If you open a new transaction towards the other database this will effectively open a new session for this database and this entity manager.
this is equivalent to #Transactional(propagation = Propagation.REQUIRES_NEW)
You need to ensure that there are two different transactions/sessions bound to each of the persistent operations towards the two databases.
If all configurations are correct, then every thing should work fine without error
I think you missed #Qualifier(value="sessionFactory1") and #Qualifier(value="sessionFactory2") at your DAO
kindly look at those examples
Hibernate configuring multiple datasources and multiple session factories
https://medium.com/#joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7
HibernateTemplate usage is discouraged already. The clear explanation is given here https://stackoverflow.com/a/18002931/1840818
As stated over there, declarative transaction management has to be used.
I have a spring managed web app where we're using Hibernate. In my hibernate.cfg.xml we have
<property name="hibernate.current_session_context_class">thread</property>
It's also configured to use HikariCP connection pooling.
When we open sessions, we follow the paradigm of this. Note that I'm not explicitly closing the session.
Transaction tx = null;
Session session = sessionFactory.getCurrentSession();
try {
tx = session.beginTransaction();
// do DB stuff.
tx.commit(); // or, sometimes session.getTransaction().commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new CustomDbException(e);
}
In some cases, when closing the session, we do session.getTransaction().commit() at a minimum, this is incosistent, but I'm not sure if it's giving a different trasancation or otherwise causing some problems.
We've been seeing intermittent issues with LockAcquisitionException: could not execute statement, StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect), and TransactionException: nested transactions not supported. We're not trying to do nested exceptiosn and always clean up the transaction, but I'm starting to think that maybe the sessions is being shared.
The application is multi threaded, and I'm starting to suspect that getCurrentSession is actually being shread across threads. I was under the impression that by setting hibernate.current_session_context_class to thread, then getCurrentSession() would provide a separate session per thread. Should I be taking a different approach here? Should I be using openSession() instead, or somehow configuring hibernate to give me unique sessions per thread?
We use #JmsListener methods, and the transactional exceptions seem most prevalent there, but are not localized there.
Exact hibernate versions are as follows:
hibernate: [ 'org.hibernate.common:hibernate-commons-annotations:4.0.4.Final',
'org.hibernate:hibernate-core:4.3.11.Final',
'org.hibernate:hibernate-hikaricp:4.3.11.Final'],
hikari: 'com.zaxxer:HikariCP:2.4.1',
I´m starting with Hibernate.
Since I read that I need to configure the pool connection I start using C3P0
everything is fine, but when I reach the maximum connections the application freezes, I have to close the application and start it again.
this is my C3P0 part in hibernate.cfg.xml
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_size">15</property>
<property name="hibernate.c3p0.timeout">3000</property>
<property name="hibernate.c3p0.max_statements">20</property>
<property name="hibernate.c3p0.idle_test_period">300</property>
in my function when I save the object I close the session.
public void Save item(Item item)throws Exception{
try{
SessionFactory sf= NewHibernateUtil.getSessionFactory();
Session session;
session = sf.openSession();
Transaction tx= session.beginTransaction();
session.save(item);
tx.commit();
session.close();
}
catch(Exception ex){
throw new Exception(ex);
}
}
if I check the connections in MySql I see that all the connections are sleep, but the application freezes.
what am I missing here?
You are not reliably close()ing Connections (wrapped within Sessions), so they leak. Consider what happens when an Exception occurs.
Either use Java 7+ try-with-resources, if Session supports that, or use the old robust cleanup idiom, see the Appendix to my answer here.
If you fix this and are still experiencing a Connection leak, c3p0 has configuration parameters to help you track down the leak.
I am facing one weird issue when I update the table and after a couple of seconds when I try to fetch that then I still receive the old data. When I again fetch with same query after couple of second then I receive refreshed data. Basically what I see is it takes some time to return the fresh data.
I have disabled all caching from hibernate also while fetching I am making session.clear() and marked query as uncachable.
I also look into mysql query log and I figured out that hibernate is querying to mysql, but I am receiving old data.
How can I make sure that at any given point of time I receive refreshed data only
Below is my hibernate config file
<hibernate-configuration>
<session-factory>
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="show_sql">true</property>
<property name="connection.url">jdbc:mysql://127.0.0.1:4804/aluminidb?autoReconnect=true</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- Example mapping file inclusion -->
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.use_query_cache">false</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<mapping resource="com/alumini/spring/model/Alumini.hbm.xml"/>
<mapping resource="com/alumini/spring/model/Question.hbm.xml"/>
<mapping resource="com/alumini/spring/model/Events.hbm.xml"/>
</session-factory>
Below is the code to fetch the object
#Override
public Alumini login(String email, String password) {
Session session=sessionFactory.openSession();
session.clear();
Transaction t;
try{
t=session.beginTransaction();
Query query = session.getNamedQuery("chkLogIn");
query.setParameter("email",email);
query.setParameter("password",password);
query.setCacheMode(CacheMode.REFRESH);
query.setCacheable(false);
List<Alumini> aluminiList=query.list();
if(aluminiList!=null && aluminiList.size()>0){
System.out.println(aluminiList.get(0).getLastUpdated());
t.commit();
return aluminiList.get(0);
}else{
t.rollback();
return null;
}
}finally{
session.close();
}
}
So I am clearing the session, also in my config I have set all cache disabled. still when I update the record and if with in couple of seconds if I fetch the record using above method then I receive old data for once. After that it gives me latest data.
If some entities are loaded in the current Session and you run a native query, the Session might not flush automatically.
Hibernate Session offers application-level repeatable reads, so if other database transaction changes an entity, Hibernate will not refresh the current entity states.
Now, since you did not post the UPDATE part, it's hard to tell what you are doing there. The best way to address this issues is to simply log all JDBC statements as explained in this article. Then, you will know for sure whether the update was executed or not.
More, the way you do transaction and Session management is flawed as well. You don't even rollback in a finally block, and since you are using MySQL, this can lead to locks being held and causing deadlocks.
Just use a framework like Spring or Java EE to handle the Persistence Context and transaction management for you.
In your example:
Session session=sessionFactory.openSession();
session.clear();
How can one tell whether this is a new Session, and calling clear would not make any sense, or it is the same Session you used for the update?
From this code, I would assume that this is the case:
if(aluminiList!=null && aluminiList.size()>0){
System.out.println(aluminiList.get(0).getLastUpdated());
t.commit();
return aluminiList.get(0);
}else{
t.rollback();
return null;
}
But it points out that you might have skipped the Hibernate User Guide and jumped to coding Hibernate.
The aluminiList can never be null. It can only be empty.
Logging via System.out is wrong. Use a Logging framework for that.
Why do you want to commit after the query was executed? Maybe the change was not flushed at all and the query did not trigger the flush because either you set the FlushMode.MANUAL or the query is a native SQL, not a JPQL. Check out this article for more details about the difference.
You call rollback on else? What's the point? You don't trust the database that it issued the UPDATE properly and now you want to roll back that change. Or, you suspect that Hibernate did not flush, but then, why do you would you roll it back if the change didn't happen, right? But if it happened, then you should read-your-writes because that's how ACID isolation levels work.
All in all, there are many issues in the code that you posted. So, read the Hibernate User Guide and these tutorials, and you will fix all your issues. There's no other way.
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
lazy-init="true">
<property name="dataSource" ref="dataSource" />
</bean>
While building an app using spring and hibernate , if I use DataSourceTransactionManager, then on exception , it does not roll back. Seems like it uses auto-comit in different session. However , if I change the transaction manager to org.springframework.orm.hibernate3.HibernateTransactionManager, the roll back works as expected.
Or is it that if we use hibernate , then we need to use HibernateTransactionManager?
N.b: My service annotated with #Transactional(rollbackFor = { Throwable.class} )
When working with plain hibernate the following is needed to manage transactions
Session s = sessionfactory.openSession();
Transaction tx = null;
try {
tx = s.beginTransaction();
// Your data manipulation here
tx.commit();
} catch (Exception e) {
if (tx != null) { tx.rollback();}
} finally {
s.close();
}
This is also what the HibernateTransactionManager does (open a session if needed, start transaction, afterwards commit/rollback).
Now what you are trying to do is the following (which is kind-of similair to the DataSourceTransactionManager, that operates on the `DataSource instead of the session.)
Session s = sessionfactory.openSession();
Connection conn = null;
try {
conn = s.connection();
// Your data manipulation here
conn.commit();
} catch (Exception e) {
if (conn != null) {
try {
conn.rollback();
catch (IOExceptin) {}
}
} finally {
s.close();
}
Which isn't going to work as the actual transactional unit, the session, is never getting informed of the commit or rollback. So in the worst case, depending in your flush-mode, everything (or partially) gets committed.
In short always use the transaction manager which fits your technology.
When using plain hibernate use the HibernateTransactionManager, when using JPA use the JpaTransactionManager, don't use the DataSourceTransactionManager in those cases as that is usable only in situations where only plain JDBC is used.
The DataSourceTransactionManager clearly states that it operates on the DataSource and underlying connection. Whereas when one uses Hibernate the transaction is controlled by the hibernate Session, this is the level where the HibernateTransactionManager operates on. For JPA this is the EntityManager and this is what the JpaTransactionManager recognizes.
According to Spring documentation:
PlatformTransactionManager implementations normally require knowledge
of the environment in which they work: JDBC, JTA, Hibernate, and so
on.
If you use JTA in a Java EE container then you use a container
DataSource, obtained through JNDI, in conjunction with Spring’s
JtaTransactionManager.
You can also use Hibernate local transactions easily...
In this case, you need to define a Hibernate LocalSessionFactoryBean,
which your application code will use to obtain Hibernate Session
instances ... in this case is of the HibernateTransactionManager type.
In the same way as the DataSourceTransactionManager needs a reference
to the DataSource, the HibernateTransactionManager needs a reference
to the SessionFactory.
Although:
DataSourceTransactionManager will binds a JDBC Connection from the
specified DataSource to the current thread, potentially allowing for
one thread-bound Connection per DataSource.
the Session won't be bound to the current transaction and you need both for local transactions.
This is what hibernate or JPA specific TM would do for you. They will associate the persistence context and one connection per transaction per thread.
If you choose JTA transactions than an external TM will coordinate transactions. DB connections will be released aggressively after each statement, which is fine as long as the external TM will always return the same connection to the same thread during a global transaction life.