How to inject persistence context to different data source programmatically
Asked Answered
T

4

10

In standard EJB 3, when injecting entity manager, persistence unit (which refers to datasource) is hardcoded into annotation: (or alternatively xml file)

@PersistenceContext(unitName = "myunit")
private EntityManager entityManager;

Is there a way to use an entity manager but to select data source by name at runtime?

Treacherous answered 24/2, 2011 at 11:46 Comment(0)
P
5

Using EclipseLink, You can set a DataSource configured in your app server.

import org.eclipse.persistence.config.PersistenceUnitProperties;
...


....
Map props = new HashMap();  
props.put(PersistenceUnitProperties.JTA_DATASOURCE, "dataSource");  
EntityManagerFactory  emf = Persistence.createEntityManagerFactory("UNIT_NAME", props);
EntityManager em = emf.createEntityManager();

PU_NAME refers to the name used in the file persistence.xml
dataSource refers name used in the app server for the jdbc Resource as "jdbc/sample"

Petr answered 16/2, 2012 at 16:16 Comment(1)
Please note that this would create an application managed EntityManager. The original question cites code using a container managed EntityManager.Whoosh
S
2
  • Configure required data-sources & persistent-units in persistence.xml.
<persistence-unit name="UNIT_NAME" transaction-type="JTA">
      <provider>PERSISTENCE_PROVIDER</provider>
          <jta-data-source>java:DATA_SOURCE_NAME</jta-data-source>
</persistence-unit>

 -- other units  

Now at runtime you can build entity-manager for the required persistence-unit. Create separate persistence-units for each data-source.

//---
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
//---
  • Else you can also build factory by providing a map of properties like db-url, userName etc.
createEntityManagerFactory(persistenceUnitName,propertiesMap);

This will create and return an EntityManagerFactory for the named persistence unit using the given properties. Therefore you can change the properties at runtime accordingly.

Supply answered 12/3, 2011 at 4:43 Comment(2)
Please note that this also would create an application managed EntityManager. The original question cites code using a container managed EntityManager. @Petr answer too is for application managed EntityManager.Whoosh
@MozartBrocchini OP has shown current code which is container managed, but haven't mentioned that it is necessary. What is required is to be able to select data source programmatically.Supply
F
2

It is possible! I've done it and it works under JBoss AS and WebSphere.

I use a custom persistence provider which extends org.hibernate.ejb.HibernatePersistence (you need to modify a private static final field to set your persistence provider name into org.hibernate.ejb3.Ejb3Configuration.IMPLEMENTATION_NAME: this is a kind of black magic but it works). Make sure your persistence.xml's persistence units have the custom provider set in the <provider> tag and your custom provider is registered in META-INF/services/javax.persistence.spi.PersistenceProvider.

My provider overrides the createContainerEntityManagerFactory(PersistenceUnitInfo,Map) method called the Java EE container as such (for JTA datasource but it would be easy to do it also for non JTA datasource):

@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {

    // load the DataSource
    String newDataSourceName = ...; // any name you want
    DataSource ds = (DataSource)(new InitialContext().lookup(newDataSourceName));

    // modify the datasource
    try {
        try {
            // JBoss implementation (any maybe other Java EE vendors except IBM WebSphere)
            Method m = info.getClass().getDeclaredMethod("setJtaDataSource", DataSource.class);
            m.setAccessible(true);
            m.invoke(info, ds);

        } catch (NoSuchMethodException e) {
            // method does not exist (WebSphere?) => try the WebSphere way

            // set the datasource name
            Method m = info.getClass().getDeclaredMethod("setJtaDataSource", String.class);
            m.setAccessible(true);
            m.invoke(info, newDataSourceName);

            // do the lookup
            Method m2 = info.getClass().getDeclaredMethod("lookupJtaDataSource", String.class);
            m2.setAccessible(true);
            m2.invoke(info);
        }
    } catch (Throwable e) {
        throw new RuntimeException("could not change DataSource for "+info.getClass().getName());
    }

    // delegate the EMF creation
    return new HibernatePersistence().createContainerEntityManaferFactory(info, map);
}

The createEntityManagerFactory(String,Map) also overriden but is much simpler:

@Override
public EntityManagerFactory createEntityManagerFactory(String persistenceUnitInfo, Map map) {

    // change the datasource name
    String newDataSourceName = ...; // any name you want
    if (map==null) map = new HashMap();  
    map.put(HibernatePersistence.JTA_DATASOURCE, newDataSourceName);  

    // delegate the EMF creation
    return new HibernatePersistence().createEntityManaferFactory(persistenceUnitInfo, map);
}

Note that I only wrote here the core code. In fact, my persistence provider has a lot of other functionalities:

  • check that the DataSource is up and running
  • set the transaction manager for JBoss or WebSphere
  • cache the EMF for lower memory usage
  • reconfigure the Hibernate query plan cache for smaller memory usage
  • register JMX bean (to allow more than one EAR to get the same persistence unit name)
Fontanez answered 7/11, 2013 at 8:29 Comment(0)
B
1

I want to indicate that the usage of

Persistence.createEntityManagerFactory(persistenceUnitName)

recommended in the answer of Nayan is classified by the JPA Specification (JSR 317) as follows (footnote in "9.2 Bootstrapping in Java SE Environments"):

"Use of these Java SE bootstrapping APIs may be supported in Java EE containers; however, support for such use is not required."

So this isn't a standard solution for EJB. Anyway, I can confirm that this is working in EclipseLink.

Note: I'm not yet allowed to post this as a comment.

Berrios answered 9/12, 2012 at 21:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.