Reputation: 13843
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;
}
Upvotes: 0
Views: 4140
Reputation: 13843
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.
Upvotes: 0
Reputation: 6289
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.
Upvotes: 1
Reputation: 597016
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.
Upvotes: 1