Search code examples
javajpajdbceclipselink

JPA Eclipselink - Multiple EntityManagerFactory instances returning same session


I have understanding an instance of EntityManagerFactory(EMF1) has its own EntityManager and session. If i create another instance of EntityManagerFactory(EMF2) using same credentials as EMF1 then it should have its own connection pool and so session. But it is not and because of this reason session customizer is also called once if everything is same

package test.jpa.factory;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.sessions.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestEntityManagerFactory {
    private static Logger logger = LoggerFactory.getLogger(TestEntityManagerFactory.class);
    public static final String JDBC_URL = "javax.persistence.jdbc.url";
    public static final String JDBC_USER = "javax.persistence.jdbc.user";
    public static final String JDBC_PASSWD = "javax.persistence.jdbc.password";
    public static final String WAREHOUSE_PERSISTENCE_UNIT = "etl-dw";

    public static void main(String[] args) throws Exception {
        String host = "xxxxcccc";
        String port = "1521";
        String user = "skipvpd";
        String pwd = "abcd";
        String service = "test_svc1";
        System.out.println("Same user multiple EMF");
        printSession(host, port, user, pwd, service);
        printSession(host, port, user, pwd, service);

        System.out.println("Different user multiple EMF");
        user = "1032";
        pwd = "abcd";
        printSession(host, port, user, pwd, service);
        user = "1033";
        printSession(host, port, user, pwd, service);

    }

    private static void printSession(String host, String port, String user, String pwd, String serviceName)
            throws Exception {
        EntityManagerFactory emf = getEntityManagerFactory(host, port, user, pwd, serviceName);
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Session session = (Session) em.unwrap(org.eclipse.persistence.sessions.Session.class);
        Connection connection = (Connection) em.unwrap(java.sql.Connection.class);
        System.out.println("con_hashcode=" + connection.hashCode() + ",session_hashcode=" + session.hashCode()
                + ",em_hashcode=" + em.hashCode() + ",emf_hashcode=" + emf.hashCode());
        em.getTransaction().commit();
        em.close();

    }

    private static EntityManagerFactory getEntityManagerFactory(String host, String port, String user, String pwd,
            String serviceName) {

        Map<String, String> jpaConfig = new HashMap<String, String>();
        try {
            String jdbcURL = "jdbc:oracle:thin:@" + host + ":" + port + "/" + serviceName;

            jpaConfig.put("javax.persistence.jdbc.url", jdbcURL);
            jpaConfig.put(JDBC_USER, user);
            jpaConfig.put(JDBC_PASSWD, pwd);

            jpaConfig.put(PersistenceUnitProperties.SESSION_CUSTOMIZER,
                    TestEntityManagerFactory.ETLSessionCustomizer.class.getName());

        } catch (Throwable t) {
            logger.error("ERROR=\"Error occured in getJPAConfiguration\" EXCEPTION={}", t);
        }
        return Persistence.createEntityManagerFactory(WAREHOUSE_PERSISTENCE_UNIT, jpaConfig);

    }

    public static class ETLSessionCustomizer implements SessionCustomizer {

        public void customize(Session session) {
            System.out.println("customize is called for session_hash_code=" + session.hashCode());
        }
    }
}

Same user multiple EMF

customize is called for session_hash_code=1193471756

con_hashcode=675100200,session_hashcode=1193471756,em_hashcode=627727856,emf_hashcode=179294202

con_hashcode=675100200,session_hashcode=1193471756,em_hashcode=166919726,emf_hashcode=1305777754

Different user multiple EMF

customize is called for session_hash_code=1240796303 con_hashcode=738369543,session_hashcode=1240796303,em_hashcode=1017841629,emf_hashcode=1760715967

customize is called for session_hash_code=1161255903

con_hashcode=92699135,session_hashcode=1161255903,em_hashcode=1535875885,emf_hashcode=2054926467


Solution

  • After debugging JPA code, i resolved my problem by supplying unique session name for each entity manager factory.

    Problem:

    If PersistenceUnitProperties.SESSION_NAME/eclipselink.session-name is not provided then EntityManagerSetupImpl.getOrBuildSessionName method try to create session name using connection properties that is same for both factories.

    First time EntityManagerSetupImpl object is created and cached using session name as key inside EntityManagerFactoryProvider. For second instance of EntityManagerFactory(with same session name), it finds the instance already in cache hence doesn't create new instance instead shared the existing one created for first EntityManagerFactory.

    In my example, if you add following code, you will see different sessions and hashcode with session customizer called twice.

    jpaConfig.put(PersistenceUnitProperties.SESSION_NAME, String.valueOf(System.nanoTime()));
    

    Here is the output after change

    Same user multiple EMF
    customize is called for session_hash_code=1173577197
    con_hashcode=1091009906,session_hashcode=1173577197,em_hashcode=1569793443,emf_hashcode=1681793106
    customize is called for session_hash_code=649715211
    con_hashcode=1908828843,session_hashcode=649715211,em_hashcode=327304249,emf_hashcode=1296892976