2

I've a client that sends 3 requests to a Service Layer which uses Hibernate.

Each single request causes Hibernate to begin a transaction (session.beginTransaction()).

I find that, sometimes, one transaction (from the 2+ running concurrent transactions) fails with createQuery is not valid without active transaction.

Here's Hibernate configurations that I use (running in Tomcat 6.0.x and OC4j 10.1.3.4):

<property name="hibernate.connection.pool_size">5</property>
        <!--  <property name="hibernate.current_session_context_class">thread</property> -->
        <property name="hibernate.current_session_context_class">org.hibernate.context.ThreadLocalSessionContext</property>
        <property name="connection.autoReconnect">true</property>
        <property name="connection.autoReconnectForPools">true</property>
        <property name="connection.is-connection-validation-required">true</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">false</property>

        <!-- auto commit -->
        <!-- <property name="connection.autocommit">true</property> -->

        <!-- configuration pool via c3p0 -->
        <property name="c3p0.idleConnectionTestPeriod">1000</property>
        <property name="c3p0.initialPoolSize">5</property>
        <property name="c3p0.maxPoolSize">10</property>
        <property name="c3p0.maxIdleTime">1</property>
        <property name="c3p0.maxStatements">30</property>
        <property name="c3p0.minPoolSize">1</property>

        <property name="cache.use_query_cache">true</property>
        <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        <property name="cache.use_second_level_cache">true</property>

EDIT: I am using the following Proxy to manage all transactions:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;


/**
 * http://stackoverflow.com/questions/2587702
 * 
 * @author mohammad_abdullah
 */
public class ServiceProxy implements InvocationHandler {

    private Object object;
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    private static final String SESSION_FIELD = "session";

    public static final Map<Long, Transaction> ACTIVE_TRANSACTIONS = new HashMap<Long, Transaction>();

    private ServiceProxy(Object object) {
        this.object = object;
    }

    public static Object newInstance(Object object) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new ServiceProxy(object));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = null;
        Session session = null;
        boolean joined = false;
        try {
            if (Modifier.isPublic(method.getModifiers())) {

                session = HibernateUtil.getSessionFactory().getCurrentSession();

                Field sessionField = object.getClass().getSuperclass().getDeclaredField(SESSION_FIELD);
                if (sessionField == null)
                    throw new UPSAdminException("Service Implementation should have field named: \"" + SESSION_FIELD + "\".");
                sessionField.setAccessible(true);
                sessionField.set(object, session);

                if (session.getTransaction().isActive()) {
                    joined = true;
                    logger.info("Using Already Active transaction" + " Method: " + method.getName() + " Thread: "
                            + Thread.currentThread().getId());
                    ACTIVE_TRANSACTIONS.put(Thread.currentThread().getId(), session.getTransaction());
                } else {
                    logger.info("Transaction Began" + " Method: " + method.getName() + " Thread: " + Thread.currentThread().getId());
                    Transaction newTnx = session.beginTransaction();
                    ACTIVE_TRANSACTIONS.put(Thread.currentThread().getId(), newTnx);
                }
                result = method.invoke(object, args);

                if (!joined) {
                    ACTIVE_TRANSACTIONS.get(Thread.currentThread().getId()).commit();
                    ACTIVE_TRANSACTIONS.remove(Thread.currentThread().getId());
                    logger.info("Transaction Commited" + " Method: " + method.getName() + " Thread: " + Thread.currentThread().getId());
                }
            } else {
                result = method.invoke(object, args);
            }

            return result;

        } catch (InvocationTargetException _ex) {
            Throwable cause = _ex.getCause();
            logger.severe("Caller Exception: " + cause + " Method: " + method.getName() + " Thread: " + Thread.currentThread().getId());

            if (!joined && session != null && session.getTransaction().isActive()) {
                ACTIVE_TRANSACTIONS.get(Thread.currentThread().getId()).rollback();
                ACTIVE_TRANSACTIONS.remove(Thread.currentThread().getId());
            }

            if (cause instanceof HibernateException) {
                logger.severe("Hibernate Error. Rollbacked Back. Method: " + method.getName() + " Thread: "
                        + Thread.currentThread().getId());
                throw new DBException(cause.getCause().getMessage());

            } else if (cause instanceof SetRollbackException) {
                logger.severe("Transaction marked for Rollback. Rollbacked Back. Method: " + method.getName() + " Thread: "
                        + Thread.currentThread().getId());
                return result;

            } else {
                logger.severe("Error in Business Method : " + method + ". Rollbacked Back." + " Thread: " + Thread.currentThread().getId());
                throw cause;
            }
        } catch (Exception ex) {
            logger.severe("Error in Proxy code :" + ex + " Method :" + method + " Thread: " + Thread.currentThread().getId());

            if (!joined && session != null && session.getTransaction().isActive()) {
                ACTIVE_TRANSACTIONS.get(Thread.currentThread().getId()).rollback();
                ACTIVE_TRANSACTIONS.remove(Thread.currentThread().getId());
            }

            if (ex instanceof HibernateException)
                throw new DBException(ex.getCause().getMessage());

            throw ex;
        }
    }
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219

2 Answers2

0

Since you have create a connection pool of size 5, it should run without much issues for concurrent transactions. The createQuery() method updates dirty persistant the objects it needs to be run inside a transaction. I think that is the reason for your error.

When it comes to the transactions and connections, every transaction must hold a connection but since the connection is pooled, if the transaction is in wait status it will release the connection back to the pool. If there are so many concurrent transactions, and less number of connection it will delay the processing. For long transactions there is even a chance for issues like deadlock etc...

You can find hibernate API for transaction and Contextual sessions in the links.

ManuPK
  • 11,623
  • 10
  • 57
  • 76
0

Do you test your beginTransaction() success ?

Do you close your connection after to free your pool without waiting for it to close automaticaly ?

Sample from http://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/Session.html :

Session sess = factory.openSession();
Transaction tx;
try {
   tx = sess.beginTransaction();
   //do some work
   ...
   tx.commit();
}
catch (Exception e) {
   if (tx!=null) tx.rollback();
      throw e;
   }
finally {
   sess.close();
}
Le Droid
  • 4,534
  • 3
  • 37
  • 32