Elie
Elie

Reputation: 13843

How to clean up a Hibernate Session properly

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

Answers (3)

Elie
Elie

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

Olaf
Olaf

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

Bozho
Bozho

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

Related Questions