How to inject persistence context to different data source programmatically - jpa

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.

Related

SpringBoot 2.2.6 JUnit dataSource lookup error

I'm working on a big project written with java8 and SringBoot 2.2.6. The project uses Envers and, the girl builds the architecture say to me that she doesn't manage to put in the application.properties the Envers configuration. Than she do as follows:
#Configuration
public class JPAConfig {
#Autowired
private DataSource dataSource;
#Bean(name="entityManagerFactory")
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setHibernateProperties(getHibernateProperties());
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("it.xxxx.xxxxx.xxxxx.common.model");
return factoryBean;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", PostgreSQL82Dialect.class.getName());
properties.put("hibernate.default_schema", "test");
properties.put("hibernate.listeners.envers.autoRegister", true);
properties.put("org.hibernate.envers.revision_field_name", "rev");
properties.put("org.hibernate.envers.revision_type_field_name", "rev_type");
properties.put("org.hibernate.envers.audit_table_prefix", "aud_");
properties.put("org.hibernate.envers.store_data_at_delete", true);
properties.put("org.hibernate.envers.audit_table_suffix", "");
return properties;
}
}
Problem is that without dataSource class name I can't start my #SpringBootTest classes and I don't know how to add it in a scenario like this (without change the configuration I mean).
I also tries to add this row inside the application.properties:
spring.profiles.active=#spring.profile#
spring.datasource.driver-class-name=org.postgresql.Driver
#JPA
spring.datasource.jndi-name=jdbc/test
But doesn't work at all..
If I run the App with JUnit I obtain this error:
org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: Failed to look up JNDI DataSource with name 'jdbc/test'; nested exception is javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
Can you help me??
Thanks a lot
You need to register your Datasource as JNDI resource in the spring-boot embedded tomcat.
You can add it as test scope configuration.
This answer shows how to register a JNDI resource: https://stackoverflow.com/a/26005740/5230585

Test the remote client jndi lookup using arquillian

Setup: arquillian, jboss as 7.1.1.final as a managed Container
I am currently migrating an EJB application from EJB 2.x to 3.x and JBoss 3.x to JBoss AS 7.1.
During this process i would like to get most classes under test and stumbled over arquillian.
While arquillian seems to offer some nice features on inter-bean-functionality i cannot figure out whether or not the testing of remote client features using jndi lookups works or not.
I used the Arquillian Getting started guides on my beans which worked, but since these are using #Inject and in my application jndi lookups are used everywhere i (at least think that i) need to swerve from that path.
Here is the TestCase i created based on Arquillian Getting Started. I explicitly left in all attempts using jndi properties of which i thought they might help.
The Test
should_create_greeting()
works if the Greeter bean using a separate Producer.
#RunWith(Arquillian.class)
public class GreeterTest {
public static final String ARCHIVE_NAME = "test";
Logger logger = Logger.getLogger(GreeterTest.class.getName());
#Deployment
public static Archive<?> createDeployment() {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class, ARCHIVE_NAME + ".jar").addPackage(Greeter.class.getPackage())
.addAsManifestResource("test-persistence.xml", "persistence.xml").addAsManifestResource("OracleGUIDS-ds.xml")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
return jar;
}
/**
* #Inject works using a producer with {#code #Produces}
*/
// #Inject
// Greeter greeter;
#ArquillianResource
Context context;
GreeterRemote greeter;
#Before
public void before() throws Exception {
Map<String, String> env = new HashMap<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.as.naming.InitialContextFactory");
env.put("jboss.naming.client.ejb.context", "true");
// env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT",
// "false");
// env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS",
// "false");
// env.put("jboss.naming.client.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED",
// "false");
for (Map.Entry<String, String> entry : env.entrySet()) {
context.addToEnvironment(entry.getKey(), entry.getValue());
}
greeter = (GreeterRemote) context.lookup(ARCHIVE_NAME + "/" + Greeter.class.getSimpleName() + "!"
+ GreeterRemote.class.getName());
}
#Test
public void should_create_greeting() {
Assert.assertEquals("Hello, Earthling!", greeter.createGreeting("Earthling"));
greeter.greet(System.out, "Earthling");
}
}
Is it possible to get this test running with jndi lookup? Am i missing something?
If you want to test the Remote features of a EJB you probably want to run on the client side and not in container.
You can configure the Deployment to be only client side by using #Deployment(testable=false). The #Test methods will then run as if you were a remote client.
Beyond that you can just lookup the bean via the injected Context if you want.
I had the same issue, so in a workaround i just added on the method to be tested the remoteejb as a parameter.
On my ejb:
public List localBean.obtain(RemoteEJB remoteEjb){
return remoteEjb.obtain();
}
Then on the arquillian test :
#Inject
private LocalBean localBean;
#Inject
private RemoteEJB remoteEjb;
#Test
public void test(){
List<Vo>voList = localBean.obtain(remoteEjb);
}
The best part is the remote ejb its injected and on the caller method original
#EJB(lookup="java:global/ear/ejb/RemoteEjb")
private RemoteEJB remoteEjb;

PersistenceUnitInfo implementation in EclipseLink

EntityManagerFactory can be created without a persistence unit xml using
org.eclipse.persistence.jpa.PersistenceProvider {
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
java.util.Map properties)
}
but what is the implementation class of javax.persistence.spi.PersistenceUnitInfo in eclipselink
I am struggling on this problem too. I think that a PersistenceUnitInfo must be provided by the container(i.e. Application Server). It means that Eclipselink do not create one itself. If you are using Spring ORM, it uses a DefaultPersistenceUnitManager and call its obtainPersistenceUnitInfo(String unitName) method to get a instance of PersistenceUnitInfo. The unitName must be defined in persistence.xml. This means that you still needs an xml file.
By digging into the source code of Spring ORM, I found that Spring provides several implementations of PersistenceUnitInfo. In fact they are generally a Java Bean. You may be interested in SmartPersistenceInfo, MutablePersistenceInfo and SpringPersistenceUnitInfo. View them on Github.
EDIT:
I found the implementation of Eclipselink: It's SEPersistenceUnitInfo in org.eclipse.persistence.internal.jpa.deployment. Also found the method that reads every persistence unit in the configuration xml file.
public static Set<SEPersistenceUnitInfo> getPersistenceUnits(ClassLoader loader, Map m, List<URL> jarFileUrls) {
String descriptorPath = (String) m.get(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML);
if(descriptorPath == null) {
descriptorPath = System.getProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML_DEFAULT);
}
Set<Archive> archives = findPersistenceArchives(loader, descriptorPath, jarFileUrls);
Set<SEPersistenceUnitInfo> puInfos = new HashSet();
try {
for(Archive archive : archives) {
List<SEPersistenceUnitInfo> puInfosFromArchive = getPersistenceUnits(archive, loader);
puInfos.addAll(puInfosFromArchive);
}
} finally {
for(Archive archive : archives) {
archive.close();
}
}
return puInfos;
}
Java EE platform spec 6 says : the container is responsible for finding persistence.xml condensing the information into PersistenceUnitInfo and supplying that with a call to createContainerEntityManagerFactory.
PersistenceUnitInfo is defined by the Spec, refer to the JPA spec code or JavaDoc for its implementation.
http://www.eclipse.org/eclipselink/api/2.5/javax/persistence/spi/PersistenceUnitInfo.html

JPA - Is there a way/method to retrieve Persistence Unit information

I'd like to find out my data source name in the code. Is there a way of doing that?
I am using eclipselink.
thanks
To be more specific, my aim is to get an jdbc connection object.
I know i can do that thru:
datasource = (DataSource) (new InitialContext()).lookup("my_data_source_name")
connection = dataSource.getConnection();
But I don't want to hard code the data source name in my code.
I also tried
java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
and it always return null.
.unwrap() should be the way to go, as written in EclipseLink wiki.
I also used to get null when calling em.unwrap(java.sql.Connection.class); because it was not inside a transaction. When called like this:
em.getTransaction().begin();
java.sql.Connection conn = em.unwrap(java.sql.Connection.class);
// ...
em.getTransaction().commit();
everything works fine!
java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
Should work, what version are you using? Ensure that a transaction is active.
To get the data source name you should be able to use,
((JNDIConnector)em.unwrap(JpaEntityManager.class).getSession().getLogin().getConnector()).getName();
Here's what I've found helpful:
private DataSource createDataSource() {
ClientDataSource dataSource = new ClientDataSource();
dataSource.setServerName("localhost");
dataSource.setPortNumber(1527);
dataSource.setDatabaseName("sample");
dataSource.setUser("app");
dataSource.setPassword("app");
return dataSource;
}
private EntityManagerFactory getEntityManagerFactory() {
if (emf == null) {
Map properties = new HashMap();
properties
.put(PersistenceUnitProperties.NON_JTA_DATASOURCE,createDataSource());
emf = Persistence.createEntityManagerFactory(PU_NAME, properties);
}
return emf;
}
Can you create your datasource in the code, rather than configure via persistence.xml?

JPA default persistence unit

I get exception "There is no default persistence unit in this deployment." can I somehow mark unit as default?(I have only one persistence unit, so Id rather not call it by name)
No, you have to call PU's by name.
You are probably doing it through code rather than letting the container manage it. In which case you have to specify by name.
My unit test code has this code block to do this.
#Before
public void createEntityManagerFactory() throws IOException {
final Properties p = new Properties();
p.load(getClass().getResourceAsStream("/inmemory.properties"));
emf = Persistence.createEntityManagerFactory("default", p);
}
However, my application code looks like this.
/**
* Injected persistence context.
*/
#PersistenceContext
private EntityManager em;