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?
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.
The Java Persistence API allows you to define multiple persistence units, each of which can map to a separate database.
xml files. Have multiple persistence units in one persistence.
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.
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"
<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();
//---
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With