Im developing a java web application using hibernate and I came across a basic problem:
Given user A triggers some Hibernate transaction. Start transaction, load, commit transaction.
At the same time, user B triggers a similar transaction. Then, the will get an exception: nested transactions not supported.
It seems that no more than one transaction can be active at one time.
I researched for a solution and found a lot of overview explainations like transaction-per-session pattern, but nothing tangible.
So my question is: What is a proper and simple way to handle hibernate transactions for multiple concurrent users?
the transaction management is quite standard, just remember any exceptions thrown by Hibernate are fatal , you have to roll back the transaction and close the current session immediately.
You have to close the each and every transaction.
Session session = null;
Transaction t = null;
try{
session = HibernateUtil.getSessionFactory().openSession();
t = session.beginTransaction();
//do what ever you want(session);
t.commit();
}catch(RuntimeException e){
System.out.println(e.getMessage());
}
Related
We have a FlushEventListener to do audit functionality. While updating some entity, hibernate will callback our audit code just before flushing. The audit code needs to query the database.
If we try to do it in the same session apparently we mess up the session's state: we get a NullPointerException from inside hibernate, at some point when it's validating naturalIds inside a class named NaturalIdXrefDelegate.
We currently solved it by opening a new session for the audit query. The problem with this is we're losing the benefit of getCurrentSession (a session for the whole request, managed by hibernate). This way we're going back to opening one session per query.
Is there an elegant solution for this or we basically need to re-implement getCurrentSession to manage our own session #2 in the request?
You don't have to open new session. It's enough to temporarily disable flush.
Session session = entityManager.unwrap(Session.class);
session.setHibernateFlushMode(FlushMode.MANUAL);
// do your db stuff
session.setHibernateFlushMode(FlushMode.AUTO);
It's actually a lot faster than
session.getSessionFactory().openSession()
Which works to btw.
When you first attempt to use a session Hibernate will create one and
attach it to your local thread. When you commit the transaction in the
session Hibernate will automatically close the session meaning it
can’t be reused. - got this quote from this site
how ever this is what my code looks like ,and i can i do close my hibernateSession every time i commited transaction:
Session session = HibernateUtil.getSessionFactory().openSession();
session.setFlushMode(FlushMode.AUTO);
session.beginTransaction();
session.getTransaction().commit();
session.close();
All of my code works fine, but there is issue :
For example if i add row in to database,saving success,if i add another one after 1-10 seconds. Hibernate Exception occurs saying Session is closed. but this not happen if i add another one if i wait upto 1 minute. Is this somewhat wrong in my code or the server im connecting is slow(I do have this idea because updates on my java servlet code is sometimes delay)? Any idea?
You did a good thing by opening a session whenever you need to commit a transaction but:
In general hibernate manages all the session closing and opening for you, so if you need to take the responsibility on your shoulders you need to change the following in hibernate config file : hibernate.cfg.xml
Property Name: current_session_context_class
Property Value: managed
To create a session and start a transaction you need this code:
org.hibernate.classic.Session session = HibernateUtil.getSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
ManagedSessionContext.bind(session);
session.beginTransaction();
And to commit a transaction do the following:
ManagedSessionContext.unbind(HibernateUtil.getSessionFactory());
session.flush();
session.getTransaction().commit();
session.close();
Where you can be 100% sure that whenever you commit you un-attach the session from your thread and the gc will take care of it.
So whenever you need to do another transaction you need to run the first part of the code again.
try using .getCurrentSession() instead of openSession()
Instead of using
Session session = HibernateUtil.getSessionFactory().openSession();
use
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
To automatically close session when transaction is committed set below property as true in you hibernate configuration
hibernate.transaction.auto_close_session=true
I'm creating a hibernate Session and try to start a new [Jta]Transaction. Though, the transaction cannot be started because the JtaTransaction that is used in the background seems to be rolled back.
Here is what I'm doing.
Session session = sessionFactory.openSession();
CustomSessionWrapper dpSession = new CustomSessionWrapper(session, this);
if (!session.isClosed() && !session.getTransaction().isActive()) {
session.beginTransaction();
}
Nevertheless the transaction is still not active after the beginTransaction is called. When I debug the beginTransaction method I come to the doBegin method of the JtaTransaction (I do not override this method, I'm just posting the original code of this method).
#Override
protected void doBegin() {
LOG.debug( "begin" );
userTransaction = locateUserTransaction();
try {
if ( userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION ) {
userTransaction.begin();
isInitiator = true;
LOG.debug( "Began a new JTA transaction" );
}
}
catch ( Exception e ) {
throw new TransactionException( "JTA transaction begin failed", e );
}
}
The userTransaction.getStatus() returns Status.STATUS_ROLLEDBACK and no transaction is started. Does anyone know how I can fix that?
UPDATE 1 (you can skip that since that was a mistake, see UPDATE 2)
I found out that there are two threads, one using the main session and another using smaller sessions for logging. The main session (and transaction) is open for a longer period of time, so basically until the operation is finished. It seems that locateUserTransaction always returns the same userTransaction. This means that the main session opens this userTransaction and one of the side transactions commit/rollback that transaction. Does anyone know what to do so that different transactions are retrieved?
UPDATE 2
I found out that I don't have two threads, it is only one threads that opens two sessions in parallel. Each session should then open their own transaction, though both get the same UserTransaction. How can I tell hibernate that each session should get its own [User]Transaction?
Hibernate abstracts both local as JTA transactions behind its own abstraction layer, so I don't see why you'd have to write such low level transaction handling code.
In Java EE you have the app server to manage transactions, in stand alone apps Bitronix + Spring do a job too.
Although you can manage to write your own transaction management logic, I always advice people to reuse what they have already available. Xa/JTA and Hibernate require extensive knowledge to work seamlessly.
Update 1
Two different threads shouldn't use the same user transaction, you should use different transactions for each thread.
Noticed that if I want to read some data and if I do not have a transaction context I will not be able to do so because
org.hibernate.HibernateException: No Session found for current thread
For reading data , is not required a transaction normally.
So in order for Spring to manage the session it needs to have a transaction even for read only operations like selects... ?
Is that not an overhead ?
PS.I do not want to open and close session manually...
Thanks a lot.
#Transactional tells spring to open and close a session, in addition to instructing it to start and commit a transaction. This is not very straightforward, but that's how it works. So if you don't have #Transactional, no session gets opened. Here are your options:
use #Transactional(readOnly=true) - the purpose is to have a read-only transaction. I recommend that one
use JPA EntityManager injected with #PersistenceContext. It will open a new underlying session for each invocation. Not that good option. But you should consider using EntityManager with a readOnly=true transaction
Use an additional aspect/interceptor/filter to open and close session. That would be hard, and you may end up confused by the spring implementation of hibernate's current session concept.
I have a Hibernate-based platform, built from stateless servlets (one is used to register a user and the rest to query the db).
I'm using Hibernate's sessions as follows:
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
if ((null == session) || (session.isOpen() == false)) {
session = HibernateUtil.getSessionFactory().openSession();
}
Currently I do not close the session at the end of the servlet in order to avoid openSession() call (trying to use opened sessions if possible).
What's the best practice ? when am I supposed to close these sessions ?
Can you please give an example ?
Thanks in advance !
The best practice is in most cases session-per-request. That is, open a session in the beginning of handling a request, and close it in the end. You can do that in a Servlet Filter, for example.
Having one session for the entire application is bad, because it will accumulate a lot of entities in its 1st level cache, which is a memory leak. It may also produce undeterministic results when multiple clients use it at the same time.
Your code, however, is not using one session for the entire application - it is using the "current session" concept, which opens a session and stores it in a context (a ThreadLocal for example). But if you don't close it, it will stay there forever. Plus, it will cause the same problems as described above, because threads are reused in a web application, and a new request will get an old, unclosed session at some point.
Its always better to open a new session for every request, and close the session once the request is processed. Like
Session session = HibernateUtil.getSessionFactory().openSession();
instead of
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
If we use the getCurrentSession() method , tansaction.commit() / rollback() closes the connection.
The best is to manage a hibernate session is to open a new session for every request.
It all depends on how you obtain the session.
if you use sessionFactory.getCurrentSession(), you'll obtain a
"current session" which is bound to the lifecycle of the transaction
and will be automatically flushed and closed when the transaction
ends (commit or rollback)
if you decide to use sessionFactory.openSession(), you'll have to
manage the session yourself and to flush and close it "manually".
if (!session.isOpen()) {
session = session.getSessionFactory().openSession();
session.beginTransaction();
}
I better recommend you to use spring framework. Cause in spring you can use #Transactional at method level and session will be automatically created and closed by transaction manager (unless you are using any open session in view interceptor) using AOP which is internally handled by framework.
#Autowired
EntityManager em;
#Transactinal
void save(User user){
em.persist(user);
}
thats all.spring is fun :D