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?
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"
- 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.
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)
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.