Reputation: 531
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?
Upvotes: 10
Views: 23325
Reputation: 5261
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:
Upvotes: 2
Reputation: 303
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.
Upvotes: 1
Reputation: 11622
<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.
Upvotes: 2
Reputation: 51
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"
Upvotes: 5