Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject persistence context to different data source programmatically

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?

like image 576
Primk Avatar asked Feb 24 '11 11:02

Primk


People also ask

How do you inject persistence context?

You can use the @PersistenceContext annotation to inject an EntityManager in an EJB 3.0 client (such as a stateful or stateless session bean, message-driven bean, or servlet). You can use @PersistenceContext attribute unitName to specify a persistence unit by name, as Example 29-13 shows.

Does persistence xml with JPA allow multiple database integration?

The Java Persistence API allows you to define multiple persistence units, each of which can map to a separate database.

Can we have multiple persistence xml?

xml files. Have multiple persistence units in one persistence.

What is PersistenceUnitInfo?

PersistenceUnitInfo. The JPA specification PersistenceUnitInfo interface encapsulates everything is needed for bootstrapping an EntityManagerFactory . Typically, this interface is implemented by the JPA provider to store the info retrieved after parsing the persistence. xml configuration file.


4 Answers

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"

like image 89
Monnie Avatar answered Oct 09 '22 09:10

Monnie


  • 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.

like image 30
Nayan Wadekar Avatar answered Oct 09 '22 10:10

Nayan Wadekar


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)
like image 28
Julien Kronegg Avatar answered Oct 09 '22 11:10

Julien Kronegg


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.

like image 43
tsh Avatar answered Oct 09 '22 10:10

tsh