I am using Hibernate in my app, and noticed that whenever a Hibernate error is thrown, the application eventually crashes with DB-related errors. From what I've read, the issue is that the Hibernate session is in some sort of unusable state, and therefore needs to be discarded. However, it is being returned to the pool and being re-used on the next call, instead of being discarded.
What is the correct way to ensure, at the start of each function, that the session is usable, and if not, to make sure it is discarded properly and a new session started?
My Hibernate config file includes the following:
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">10</property>
<property name="hibernate.c3p0.timeout">60</property>
<property name="hibernate.c3p0.idle_test_period">45</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.preferredTestQuery">SELECT 1;</property>
<property name="hibernate.c3p0.testConnectionOnCheckout">true</property>
<property name="hibernate.c3p0.acquireRetryAttempts">3</property>
and my code that has this error (complete function calls) is:
public static ReturnCodes newUser(Long id, String username, String country) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
//check for duplicate user
User checkUser = getUserByExternalID(id, true);
if (checkUser != null && checkUser.getId().intValue() == id.intValue()) {
transaction.commit();
return ReturnCodes.DUPLICATE;
}
User newUser = new User();
newUser.setUsername(username);
newUser.setExternalID(id);
newUser.setCountry(country);
session.save(newUser);
transaction.commit();
return ReturnCodes.SUCCESS;
} catch (ConstraintViolationException e) {
log.info("The user must already exists, so cleanup and return DUPLICATE.");
return ReturnCodes.DUPLICATE;
} catch (Exception e) {
log.info("An unknown error occurred, so cleanup and return GENERAL FAILURE.");
return ReturnCodes.GENERAL_FAILURE;
}
}
public static User getUserByExternalID(Long userid, boolean leaveTransaction) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
User user = null;
try {
session.beginTransaction();
Criteria c = session.createCriteria(User.class);
c.add(Restrictions.eq("externalID", userid));
user = (User)c.uniqueResult();
if (!leaveTransaction)
session.getTransaction().commit();
} catch (Exception e) {
log.error("Could not retrieve user with External ID " + userid, e);
}
return user;
}
Session and transaction management is a bit complex.
Spring has implemented it, by annotating methods with #Transactional.
If you want to handle that manually, read this. You can use a Filter to start a session, or some proxy and a ThreadLocal. Most commonly a session is created per-request.
The most common approach is to manage your transactions and Hibernate sessions in the services, a layer above DAO. In that case you can perform several database operations in the same transaction. For example, in your case you can explicitly check whether user with the given username already exists before trying to create a new one. So, you can let Hibernate handle all SQLExceptions and to convert them to a runtime exception.
This is usually done by injecting transaction management code via a dependency injection library. The most popular DI library is Spring.
What I ended up doing is to add the following line to every catch block:
session.clear();
which, according to the documentation,
Completely clear the session. Evict all loaded instances and cancel all pending saves, updates and deletions. Do not close open iterators or instances of ScrollableResults.
The result of this is that the session is returned to a usable state, and future uses of the same session will not be impacted by the earlier error.
This does not, of course, solve any issues surrounding proper demarcation of transactional scope, which Olaf and Bozho correctly explain is best handled outside the scope of any particular DAO.
Related
I am currently coding a webservice for user management. This project involves both a Oracle DB and a XMPP chat server. I am using Eclipselink, JPA and JTA for transaction management.
When registering a new user I've run into a problem which I can't seem to be able to fix with my knowlege of JTA.
#Transactional(rollbackOn = XMPPException.class)
public User registerUser(User user) throws InvalidArgumentException, NullValueException, ConstraintViolationException, XMPPException {
if (user == null) {
throw new NullValueException(new ErrorBuilder(Error.USER_NULL));
}
if (user.getEmail() != null && !EmailUtils.isEmailValid(user.getEmail())) {
throw new InvalidArgumentException(new ErrorBuilder(Error.INVALID_EMAIL).withParams(user.getEmail()));
}
Character gender = getGenderOrDefault(user.getGender());
user.setGender(gender);
user.setRole(UserRole.USER.getChar());
em.persist(user);
//only add xmpp user if registration was successful (if transaction is not marked for rollback)
if (!em.getTransaction().getRollbackOnly()) { //throws Exception because I'm using JTA
xmppService.createUser(user);
}
return user;
}
The problem is that I need to create the new user in both the Oracle DB and the XMPP server (using their API). This means that if one of the two seperate creation processes fails I need to rollback the other one as well.
However, since em.persist() doesn't throw an Exception immediately but rather mark the transaction for rollback the method execution continues and the user is created on the XMPP server.
My initial idea was to just check if the transaction was marked for rollback, but that doesn't seem to be able when using JTA.
So my question is: How should I go about this problem? I really don't want to split it in 2 seperate methods and just delete the user from the Oracle DB if XMPP fails.
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());
}
I'm developing a JavaFX Hibernate desktop application. I configured my application so that I use the same session from the application runs till it close.
This is how I did it:
public static Session getCurrentSession() {
if (sessionFactory != null) {
Session session = sessionFactory.getCurrentSession();
if (session != null) {
return sessionFactory.getCurrentSession();
} else {
session = sessionFactory.openSession();
session.beginTransaction();
return session;
}
}
return null;
}
And when the User close the App, I run a second method that closes the session and the SessionFactory.
public static void closeConnections() {
if (sessionFactory != null) {
if (sessionFactory.getCurrentSession().isOpen()) {
sessionFactory.getCurrentSession().close();
}
sessionFactory.close();
}
}
I'm newB on Hibernate so be kind with me PEACE
Hibernate session should be thought of as a "single-unit-of-work". One or more transactions can live in a session. The best practice is in most cases session-per-request.Open a session in the beginning of handling a request, and close it in the end.
Hibernate's 1st level cache is mantained in the "Session" so that if you keep it around in the user session it will be hitting "cached" objects.
Hibernate has 2nd level cache that you have to configure (if you want) to take advantage of. The 2nd level basically supports the following: queries, objects with load and get, and collections/associations and provides cached objects across Sessions, SessionFactories, and Clusters.
You are not using one session per application, you are using the "current session" concept, which opens a session and stores it in a context.If you don't close your session it will stay there.
OK, for your basic question:
Is it a good practice to use the same Hibernate Session over and over?
Answer is no,the session objects should not be kept open for a long time because they are not usually thread safe and they should be created and destroyed them as needed.
What it is usually done in hibernate is:
You open a session (which is basically a database connection), do transactions, and then close your connection (close your session). In other words, you can check some other post's analogy :
What is the difference between a session and a transaction in JPA 2.0?
Hope it helps
I have a question about this Hibernate samples. I didn't found an answer in Hibernate docs or in Manning Persistence with JPA. May be someone can explain what happens when I use plain JDBC.
Session session = null;
Transaction tx = null;
try {
session = sessionFactory.openSession();
tx = session.beginTransaction();
// Transaction actions
tx.commit();
}
catch (RuntimeException ex) {
try {
tx.rollback();
}
catch (RuntimeException rbEx) {
log.error("Couldn’t roll back transaction", rbEx);
}
throw ex;
}
finally {
session.close();
}
My question is what will happen if transaction rollback method throw an exception? Will some transaction data be stored in database? How can I handle this exception?
My question is what will happen if transaction rollback method throw an exception?
It depends on what the exception is.
Will some transaction data be stored in database?
Unspecified. One would hope that the database will be able to recover to a point corresponding to the start of the transaction. However, there are scenarios where even that may not be possible; e.g. if you lost a disc drive, and you don't have a hot standby.
How can I handle this exception?
In general, you can't. If you get an exception and have no idea what it means or what caused it, the only sensible thing you can do (in a typical database application) is to shut the application down and get a human being to investigate the problem.
I am using hibernate and spring and I am getting this exception upon executing the following code:
Session oSession = getSession();
try
{
oSession.setFlushMode(FlushMode.COMMIT);
getHibernateTemplate().delete(oProject);
oSession.flush();
bResult = true;
}
catch (Exception e)
{
bResult = false;
logger.error(e);
}
I've read some session management regarding hibernate and I have a hunch that this code has poor session handling. What could be the reason of having two open sessions? Hibernate APIs says that the getSession() function returns either a new session object or an existing. In this case, where does it get the existing session object? and if it creates a new session object would that still be fine to process the collection involved?
Any help would be greatly appreciated. Thanks in advance! more power stackoverflow.
HibernateTemplate.delete(Object entity) will delete the object using other Session inside HibernateTemplate but you open a new Session object by your own, which is the root cause of the error. So you should try as following:
try
{
getHibernateTemplate().delete(oProject);
bResult = true;
}
catch (Exception e)
{
bResult = false;
logger.error(e);
}
Hibernate checks if there is a session already in opened for the current thread. If not, it creates a new one using the SessionFactory; otherwise it reuses the already opened session. Well you can reattach entity objects and the same goes for collections containing entity objects too.
But you have not mentioned how is Hibernate configured to handle sessions. It could be that you manually open sessions and the code you posted actually opens 2 sessions.