There are many variations on the setup of multiple persistence contexts within spring, but none that explain the problem that I am facing.
I have an application context structure like this:
core AC
`-- web-root AC
In my core application context i have defined both my two datasources and a transactionManager as well as a single EntityManagerFactory bean:
<!-- core applicationContext.xml -->
<beans ...>
<context:annotation-config/>
...
<!-- Container managed datasources -->
<jee:jndi-lookup id="module1DS" jndi-name="jdbc/module1DS" expected-type="javax.sql.DataSource"/>
<jee:jndi-lookup id="securityDS" jndi-name="jdbc/securityDS" expected-type="javax.sql.DataSource"/>
<bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager"/>
<bean id="securityEMF" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="securityPU"/>
<property name="jtaDataSource" ref="securityDS"/>
<property name="packagesToScan" value="com.example.security.model"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"/>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.websphere.WebSphereLoadTimeWeaver"/>
</property>
</bean>
</beans>
In the web-root application context i have defined an additional EMF:
<!-- module1 applicationContext.xml -->
<beans...>
....
<bean id="module1EMF" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="module1PU"/>
<property name="jtaDataSource" ref="module1DS"/>
<property name="packagesToScan" value="com.example.module1.model"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"/>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.websphere.WebSphereLoadTimeWeaver"/>
</property>
</bean>
</beans>
Now I have a couple entities defined under the com.example.core.model package in the core module and also a single entity defined in the com.example.module1.model package in module1 (which is a war in an ear).
There is a service defined in core that gets the EntityManager injected into with the securityPU PersistenceContext and performs a named query on an entity in the database:
#Service("coreThingService")
public class CoreEntityThingServiceImpl implements CoreEntityThingService {
#PersistenceContext(unitName = "securityPU")
#Qualifier("securityEMF")
private EntityManager em;
#Override
public List<CoreEntityThing> getCoreEntityThingById(String id) {
// select a from CoreEntityThing a where a.id = :id
TypedQuery<CoreEntityThing> query = em.createNamedQuery("CoreEntityThing.findById", CoreEntityThing.class);
query.setParameter("id", id);
return query.getResultList();
}
}
nevermind about my unusual logic here, the point is that when this service is injected into the web-tier inside a #Controller and invoked, I get the following error:
org.apache.openjpa.persistence.ArgumentException: An error occurred while parsing the
>query filter "select a from CoreEntityThing a where a.id = :id". Error message: The
>name "CoreEntityThing" is not a recognized entity or identifier. Perhaps you meant
>Module1Entity, which is a close match. Known entity names: [Module1Entity]
I have Inspected the injected em:
#PersistenceContext(unitName = "securityPU")
#Qualifier("securityEMF")
private EntityManager em;
and have found it to be the securityPU as expected, what i didn't expect however is when it said that it couldn't find the entities (even though the packagesToScan property of the securityEMF was correctly pointing to the right package).
More strangely, it suggested an entity in the 'child' PU -- Module1Entity -- that should not have been picked up in the securityEMF.
What am i doing wrong?
UPDATE 1: I have updated my application context xml descriptions to make clear how i have created my PUs
UPDATE 2: It appears my classes are being scanned as I can see them in the persistence unit at runtime, what is really intriguing, is that up until the invokation of the createNamedQuery method on the EntityManager proxy is called (using method.invoke(targetPU, name) where method is the createNamedQuery function and targetPU is the securityEMF PU), The PU remains as the securityEMF. I can't go any deeper into code to see what createNamedQuery is actually doing, but during this invokation, the described error is thrown! what on earth is happening?!
To make things clearer, The error message's query string:
"select a from CoreEntityThing a where a.id = :id"
sits on top of my CoreEntityThing class!
package com.example.core.model.security;
...
#Entity
#NamedQuery(name = "CoreEntityThing.findById", query = "select ...")
public class CoreEntityThing { ... }
so it is finding the entity, then claiming it knows nothing about it!
UPDATE 3: I've read in this blog post about a supposed limitation of JPA PersistenceUnits having limited modularity. can anyone point me to more documentation about this limitation and any examples of multi module enterprise solutions using multiple datasources. This has to be a common concern for a large number of sizable projects.
Related
I have a Spring MvC project using JPA and Oracle as DB, with this entity:
#Entity
#Table(name = "AUTORISATION_TAURU")
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder
#EqualsAndHashCode(of = {"autorisationTaurusId"})
public class AutorisationTauru implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_TAURU")
#SequenceGenerator(sequenceName = "SEQ_AUTORISATION_TAURUS", allocationSize = 1, name = "SEQ_TAURU")
#Column(name = "AUTORISATION_TAURUS_ID")
private Long autorisationTaurusId;
..
}
in my xml config file, I have this;
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:~/test2;DB_CLOSE_DELAY=-1;MODE=Oracle;INIT=RUNSCRIPT FROM 'classpath:create_db.sql'\;
RUNSCRIPT FROM 'classpath:create_db2.sql'\;
RUNSCRIPT FROM 'classpath:create_func.sql'" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="database" value="H2" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="bonanza-entities" />
<property name="packagesToScan">
<array>
<value>com.bonanza.model</value>
</array>
</property>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="jpaProperties">
<props>
<prop key="eclipselink.target-database">org.eclipse.persistence.platform.database.OraclePlatform</prop>
</props>
</property>
</bean>
I have created the table I am doing the INSERT using the AUTO_INCREMENT option:
CREATE TABLE IF NOT EXISTS AUTORISATION_TAURU
(
AUTORISATION_TAURUS_ID NUMBER ,
but when I run my local tests, I got this error:
Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLSyntaxErrorException:
Syntax error in SQL statement "SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM[*] DUAL"; expected "identifier"; SQL statement:
SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL [42001-200]
Error Code: 42001
Call: SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL
Query: ValueReadQuery(sql="SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL")
If I add the sequel creation:
CREATE SEQUENCE SEQ_AUTORISATION_TAURUS
MINVALUE 1 MAXVALUE 9223372036854775807
START WITH 1 INCREMENT BY 1 CACHE 8 NOCYCLE;
I got this error when running the test:
... 43 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Exception [EclipseLink-28019] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Deployment of PersistenceUnit [bonanza-entities] failed. Close all factories for this PersistenceUnit.
Internal Exception: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLSyntaxErrorException: Sequence "SEQ_AUTORISATION_TAURUS" already exists; SQL statement:
Probably the problem with your setup consists in that your tests are creating several times the Spring application context.
Every time a new test is run it will recreate the dataSource bean and, in addition, it will try to launch the H2 database initialization scripts.
In a normal case, the first test will create the H2 database folder and related stuff, the next will reuse it.
Depending of the content of those scripts it will work most of the times, but not always as in your case.
To avoid that problem you have several options.
On one hand, in this specific case, you can include the clause IF NOT EXISTS in your sequence creation code:
CREATE SEQUENCE IF NOT EXISTS SEQ_AUTORISATION_TAURUS...
In a general case, you can modify your script to take into account this fact and create if not exists the different H2 elements or first DROP and then CREATE every one you need.
On the other hand, Spring Test also offers you the #DirtiesContext annotation for a similar purpose:
Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache.
And:
#DirtiesContext may be used as a class-level and method-level annotation within the same class or class hierarchy. In such scenarios, the ApplicationContext will be marked as dirty before or after any such annotated method as well as before or after the current test class, depending on the configured methodMode() and classMode().
As you can see, you only need to annotate your class or test methods with this annotation and Spring will recreate the context accordingly:
#DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
Please, be aware that this approach will have impact on the performance of your tests because the Spring application context need to be recreated although, on the other hand, it will provide you always a clean and deterministic - according to your initialization scripts - database state.
Premise
I have a Spring 5.1.5 project with Hibernate 5.4.1
The compilation goes through fine but while running test cases for a particular package I see multiple tests failing. All with the same error:
javax.persistence.TransactionRequiredException: no transaction is in progress at com.project.server.package.dao.impl.SomeDAOImplTest.someTest(SomeDAOImplTest.java:54)
The Problem
Now I know that Hibernate 5 enforces the check for a transaction and it doesn't find one here and throws an exception. My question is why does it do that given I have transactions initialized via spring context.
My test case:
#ContextConfiguration({ "classpath:/spring/applicationContext-package-dao--test.xml" })
public class SomeDAOImplTest extends AbstractDAOTest {
#Autowired
private SomeDAO someDAO;
private className obj;
#Before
public void setUp() {
obj = new ClassName();
obj.setId(3);
someDAO.saveOrUpdate(obj);
}
My applicationContext-package-dao--test.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:/spring/applicationContext-persistence-datasource-test.xml" />
<import resource="classpath:/spring/applicationContext-package-dao.xml" />
</beans>
The imported context applicationContext-persistence-datasource-test.xml has a bean txProxyTemplate as below:
<beans>
.
.//other beans
.
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="transactionManager" class="com.desktone.transaction.DtResourcelessTransactionManager" />
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
.
.//other beans
.
</beans>
The other applicationContext-package-dao.xml has the bean definition for SomeDAO which has txProxyTemplate as it's parent.
<bean id="SomeDAO" parent="txProxyTemplate">
<property name="target">
<bean class="com.project.server.package.dao.impl.SomeDAOImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="saveOrUpdate">PROPAGATION_REQUIRED</prop>
<prop key="delete">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
The saveorupdate call from SomeDAOImplTest calls SomeDAOImpl whose spring context config is applicationContext-package-dao.xml and has the Hibernate saveorupdate() call.
Things I have Tried:
Manually adding #Transactional tag to SomeDAOImplTest. (still throws no tx error)
Experimenting with PROPAGATION.REQUIRES_NEW (still throws no tx error) & PROPAGATION.MANDATORY(says marked mandatory but no tx).
Made sure the autowire is initialzing the bean.
Primary Suspicion
I suspect somehow the someDAO bean is initialized but txProxyTemplate bean isn't, so no transactionManager is in place. However, I have found no clues to coroborate this.
For me, this was happening because of a very fundamental issue. I'll explain the issue and the fix will be intuitive to everyone afterward.
When a Spring application runs all the beans are loaded in a single/global context. So even if some spring bean configuration depends on a transaction bean(for me it was txProxyTemplate) which isn't present in the same package at runtime it will be able to access it.
However, that's not true for a test case. My tests were reporting no transaction in progress since they couldn't load the txProxyTemplate and actually start a transaction. So my tests never ran in a transaction and I didn't know any better until I upgraded to Hibernate 5 and they put a hard constraint on this.
As you might have guessed defining the txProxyTemplate in the same spring config helped me work past this issue.
Good learning.
I am able to inject EntityManager in my Service class without defining any persistence-unit.
This is my configuration:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<jpa:repositories base-package="com.example"></jpa:repositories>
In my Service class:
#PersistenceContext
private EntityManager entityManager;
// THIS WORKS!
final Session session = entityManager.unwrap(Session.class);
Is it because of the jpa:repositories ?
Spring ORM package's LocalContainerEntityManagerFactoryBean class makes this possible. It uses DefaultPersistenceUnitManager class to build an instance of PersistenceUnitInfo which can then be provided to PersistenceProvider class's createContainerEntityManagerFactory. Hibernate implements this interface so that Spring can create an instance of EntityMangerFactory class.
DefaultPersistenceUnitManager class is the one that creates a persistenceUnit called default and finds all the entity classes going through the classes on the classpath. Same information acquired from the persistence.xml has been taken in with a alternative approach like this without having a physical persistence.xml file.
Finally Spring uses JpaVendorAdapter configured to get the JPA provider- specific EntityManagerFactory instance.
Spring data JPA specific <jpa:repositories base-package="...." /> or #EnableJpaRepositories uses to scan all the beans annotated with #Repository to provide dynamic queries and other features. This is not an exhaustive explanation but you see how this "magic" happens.
You can find more on grepcode or download the sources to explore more.
I'm using java 1.6 and spring 3.0.4, I want to realize a java functionality that
calculate new data values
update one-by-one the existing values on the database
If in any of this step there's an error I want to rollback the whole transaction and come back to the previous state.
I already realized all this pieces of code, I just want to put them together. How I can manage this with the existing spring values that are working with #Entity and #Column annotations?
Thanks!
Short answer: as you're using Spring, the easiest would be to use the transaction management, creating a service that represents this transaction unit and annotate the method with #Transactional
In practice, you need to configure a PlatformTransactionManager in your application. As you seem to use JPA, the JpaTransationManager seems like an obvious choice. To enable the processing of the #Transactional annotation, you can either use #EnableTransactionManagement or the <tx:annotation-driven/> namespace. Both are explained in the Javadoc of #EnableTransactionManagement
By default, a runtime exception thrown from that annotated method will manage a transaction rollback. If your code is using checked exceptions, you'll have to configure the rollbackFor attribute of the annotation.
There are more details and examples available in the documentation
For people that need the same configuration, here you can find how I solved this problem, integrating Hibernate with Spring.
<!-- session factory activate the transaction modules for the specified classes -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="configLocation" value="classpath:config-hibernate.xml" />
<property name="packagesToScan">
<list>
<!-- Additional packages required to be added if entities located elsewhere -->
<value>com.some.package.dao</value>
<value>com.some.package.model</value>
<value>com.some.package.SpecificClass</value>
</list>
</property>
<property name="mappingResources" ref="mappingResources"/>
<bean id="mappingResources" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<!-- here you can add your hibernate mapping query that you want to use on transaction -->
<value>config-hibernate-mapping.xml</value>
</list>
</property>
</bean>
<!-- This will activate transactional annotation -->
<tx:annotation-driven transaction-manager="transactionManager" />
#Service
#Transactional
public class SpecificClass {
// write your method, everyone of them will be transactional
// and there will be a commit in case of success or rollback in case of exception
}
i'm facing a problem i really dunno how to catch the cat tail (if you hollow me the joke :o))
i have a webapp in war, deploy in tomcat. the war contains 4 Jars.
4 jars have 4 applicationContext, with 4 entityManager, and 4 TransactionManager.
declare like this (change the number 1..):
<bean id="entityManagerFactory1"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource1"
p:persistence-unit-name="com.xxxxxx.domain" >
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:databasePlatform="${ds1.dbdialect}" p:generate-ddl="false"
p:showSql="${ds1.showsql}" />
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<bean id="transactionManager1" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory1">
depends-on="entityManagerFactory1" name="transactionManager1"/>
<tx:annotation-driven transaction-manager="transactionManager1" />
the context is load like this :
my problem i discover that when i'm using a BO of 3, the transaction is open with the datasource of 2.
moreover, if i do a persist i have the message :
AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
BUT if i launch the jar 1 alone (for example), everything is working perfectly.
thanks a lot for your enlightement.
Let me guess:
in your servlet-context.xml, do you import the contexts? e.g.:
<import location="classpath:context1.xml" />
<import location="classpath:context2.xml" />
etc.
If you do, all bean definitions are copied from the imported context into the root context, which means that you have four different <tx:annotation-driven /> declarations, with different transaction managers. Probably the last one wins.
Possible solutions: Either use Qualifiers or use the XML style of transaction declaration.
What I'd do is probably to introduce a custom #Transactional annotation per context:
#Transactional("tx1")
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE,ElementType.METHOD})
public #interface Transactional1 {}
Now annotate all methods in jar 1 with #Transactional1, in jar 2 with #Transactional2 etc. This mechanism is documented in the Section 10.5.6.3 Custom shortcut annotations