I am writing a new spring boot app that is using existing jar files that work fine in other non spring boot applications (they are using spring).
In our service layer, we are using spring transactions and spring AOP that have their configurations defined in an XML file. For example:
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'find' are read-only -->
<tx:method name="find*" read-only="true" propagation="REQUIRED"/>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation on any service -->
<aop:config>
<aop:advisor pointcut="com.company.app.common.aspect.SystemArchitecture.serviceTransaction()"
advice-ref="dbTransaction" order="1010" />
<aop:aspect ref="verifyDatasourceSwitch" order="1001">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.serviceOperation()"
method="verifyDatasource" />
</aop:aspect>
<aop:aspect ref="appDatasourceSwitch" order="12">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.controlDatasource()"
method="switchToControl" />
</aop:aspect>
<aop:aspect ref="controlMongoDatabaseSwitch" order="13">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.controlMongoDatabase()"
method="switchToControl" />
</aop:aspect>
</aop:config>
The aspects and pointcuts are defined like this:
#Aspect
public class SystemArchitecture
{
/**
* A join point in the service layer if the method is defined
* in a type in the com.company.app.service package or
* any sub-package under that
*/
#Pointcut("within(com.company.app.service..*)")
public void inServiceLayer() {}
#Pointcut("inServiceLayer() && this(com.company.app.service.Service)")
public void serviceRegularOperation() {}
/**
* A service operation is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementations are in sub-packages.
*/
#Pointcut("serviceRegularOperation() || serviceBrandOperation() || serviceCustomerOperation() || serviceWithChangeLoggingOperation() || serviceNonStandardService()")
public void serviceOperation() {}
#Pointcut("serviceOperation() && !#annotation(com.company.app.service.advice.NoTransaction)")
public void serviceTransaction() {}
}
This is all working correctly in other apps that are using it.
In the spring boot app, I have a configuration class that includes the xml config containing the above.
#Configuration
#PropertySource("classpath:config/application.properties")
#ImportResource({ "classpath:aspect.xml",
"classpath:rabbit.xml",
"classpath:email.xml",
"classpath:approval.xml",
"classpath:event.xml",
"classpath:facades.xml",
"classpath:general.xml",
"classpath:velocity.xml",
"classpath:mongo.xml",
"classpath:services.xml",
"classpath:velocity-service.xml",
"classpath:velocity-facade.xml",
"classpath:sharedservices.xml",
"classpath:jobs.xml",
"classpath:cachesharedfacades.xml",
"classpath:sharedContext-security.xml",
"classpath:hibernate-common-session-factory.xml",
"classpath:hibernate-datasource.xml",
"classpath:hibernate-local-transaction-manager.xml",
"classpath:hibernate-mysql-session-factory.xml",
"classpath:hibernate-repositories.xml",
"classpath:mysql-repositories.xml",
"classpath:hibernate-template.xml",
"classpath:repository-mbeans.xml",
"classpath:spring-transaction.xml" })
public class ApplicationConfiguration
{
}
The main application is created as:
#SpringBootApplication
public class GraphqlApplication
{
public static void main( String[] args )
{
SpringApplication.run( GraphqlApplication.class, args );
}
}
When the application is started up, I see warnings such as:
Bean 'adminTypeService' of type [com.company.app.service.impl.AdminTypeServiceImpl]
is not eligible for getting processed by all BeanPostProcessors
(for example: not eligible for auto-proxying)
Which I assume is causing the proxying not to happen, which is probably what is stopping the AOP calls from working.
Am I missing something? I can see the beans defined in the services.xml are created, but when calling a service methods, they interface isn't proxied, so there are no transactions or other supporting functions called via AOP that need to happen.
Thanks in advance!
Related
I defined and successfully plugged in a Hibernate DB Interceptor which catches all Transactions.
public class HibernateTransactionInterceptor extends EmptyInterceptor {
#Override
public void afterTransactionBegin(Transaction tx) {
System.out.println("Intercepted");
// ...
super.afterTransactionBegin(tx);
}
}
applicationContext.xml:
<bean id="transactionInterceptor" class="myapp.interceptor.HibernateTransactionInterceptor" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- Plug into SessionFactory the Interceptor bean define above -->
<property name="entityInterceptor" ref="transactionInterceptor" />
...
</bean>
Now, the Interceptor fires on all #Transaction service methods. But I need to only intercept #Transaction(readOnly=FALSE) methods (i.e., filter out all Read-Only methods). Is there a way to configure that?
I found the solution. There's an easy way to check for Read-Only/non-Read-Only Transactions with a Spring-level transaction listener. On the Spring level, rather than Hibernate, there's a method called TransactionSynchronizationManager.isCurrentTransactionReadOnly(). It can be called from the following implementation of TransactionSynchronization which uses a pointcut to intercept all #Before #Transactional Spring processing.
#Component
#Aspect
public class TransactionSynchronizationAspect implements TransactionSynchronization {
#Before("#annotation(org.springframework.transaction.annotation.Transactional)")
public void registerTransactionSynchronization() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (!isReadOnly) {
// ... logic goes here...
}
// Continue
TransactionSynchronizationManager.registerSynchronization(this);
}
#Override
public void afterCompletion(int status) {
// No custom code here
}
}
As for the original Hibernate Transaction Listener in the Original Post, that strategy can still be used, but requires a tx:advice to restrict the Listener to specified (mapped) Service method names, like so:
<aop:config>
<aop:advisor id="managerTx" advice-ref="txAdvice"
pointcut="execution(* com.app.*.*.service.*.*(..))"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="customTransactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
This XML file can be imported with #ImportResource. With this strategy, all Write operations would be intercepted with the Listener, while Reads would always go through automatically.
I have migrated Spring from 3.1 to 4.1.3 and Hibernate 3.2 to 4.3.9
As part of migration I have modified below import and code
old import
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
new Import
import org.springframework.orm.hibernate4.support.HibernateDaoSupport;
and code changes
In hibernate 3 I have following code
public Session getCurrentSession() {
Session session = getSession();
session.setFlushMode(FlushMode.MANUAL);
return session;
}
now I have modified according to new jars as below
public Session getCurrentSession() {
Session session = currentSession();
session.setFlushMode(FlushMode.MANUAL);
return session;
}
after the above changes I am getting below exception
Could not obtain transaction-synchronized Session for current thread
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
at org.springframework.orm.hibernate4.support.HibernateDaoSupport.currentSession(HibernateDaoSupport.java:129)
I am not using annotations in my application
I am not able to resolve the issue. Please help me in knowing possible reasons for the exception
I guess you need to add transactionManager for your sesssionFactory:
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean>
where, mySessionFactory is your Session Factory Bean Id.
As you said, you are not using Annotations in your project. Then, you must use AOP to enable Transaction Management at Method Level.
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="select*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* example.MyClass.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>
Add Transaction Support for your method like this.
Add following entry in your POM, if you are using MAVEN or gradle or you can simply download and add jar to your classpath.
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
I have a JUnit test method which makes call to two different services and both services are running with different data sources and of course in two transaction managers.
So i have configured Flyway to use H2 data base and implemented JUnit test method as below.
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
public class DataControllerTest extends BaseIntegrationTest {
#Test
public void testDataSave() throws Exception {
// test code to call controller which internally calls two services.
assertEquals(1, JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "Database1Table1"));
assertEquals(1, JdbcTestUtils.countRowsInTable(this.anotherjdbcTemplate, "Database2Table1"));
}
}
So my question is this test method is creating some records in H2 database and i want to clear that data after running this test.
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
and other transaction manager to work on different data source are configured as below.
<bean id="txManager2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="anotherDataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
Since i gave this test class as Transactional it would clean database after each test method run.
But currently it is clearning only one database. but i want to clean another database which is part of anotherDatasource.
How do i need to proceed to do that..?
If possible and How to mention these two transaction managers in #Transactional annotation.
Hello friend i'm developing spring(4.0.3) and hibernate(4.3.6) based application.
I'm facing following error when I saved any object in session factory:
org.hibernate.HibernateException: save is not valid without active transaction
20:38:59,881 ERROR [STDERR] at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:348)
And following is the entry which I have used in my application-context.xml
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactoryAthena" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
On more thing I'd like to bring here if I used any value in transaction-manager attribute instead actual transactionManager for bean reference then its not throwing error.
So i think its not taking reference bean value.
Please help me!!
You should take a look in this link but follows an example using xml.
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactoryAthena" />
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<!-- the transactional advice (what happens; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with get are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name=""/>
</tx:attributes>
</tx:advice>
But nowadays I have been seeing the spring community using declarative transactions with annotations. Like the example below:
#Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void updateFoo(Foo foo) {
// do something
}
}
Use #EnableTransactionManagement at the top of your class:
#Component
#EnableTransactionManagement
public class Abc{
}
transaction has been initiated using following codes in Application Context file:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*InOwnTransaction" propagation="REQUIRES_NEW"
rollback-for="com.dummy.common.exception.DummyException" />
<tx:method name="*" propagation="REQUIRED"
rollback-for="com.dummy.common.exception.DummyException" />
</tx:attributes>
</tx:advice>
In Service impl, function for updating entity
public Offering selectiveUpdateInOwnTransaction(Map<String, String> offeringData) throws DummyException
{
// search data from DB
AbstractJpaEntity priceObj = selectEntityByCriteria(Price.class, paramMap);
// now "priceObj" contain requied data;
..
priceObj.setPublishedStatus(true);
// some businesslogic which is throwing exception <-- Point 1 : This is throwing exception
priceObj.setPrice(23);
..
updateEntity(priceObj);
}
Two function calling same above mentioned function "selectiveUpdateInOwnTransaction"
public boolean updateOffering(Offering offering) // In same Service Impl
public boolean updateOfferingInOwnTransaction(....) throws DummyException // In different Service Impl
In Both these function, call to "selectiveUpdateInOwnTransaction" is as follow:
try
{
selectiveUpdateInOwnTransaction(dataMap);
}
catch (DummyException e)
{
....
}
Issue:
Point 1 is throwing exception and "priceObj" should not be updated in DB, but when "selectiveUpdateInOwnTransaction" is called from "updateOffering", it get persisted in DB.
Also only contents that are persisted in DB are contents which are updated in object before exception throw.
Call from "updateOfferingInOwnTransaction" is showing no such error.
I am not able to understand why "updateOffering" is not working as per expectation.
A workaround
As quick fix, i did following changes in "selectiveUpdateInOwnTransaction", and after that, it worked fine (entity not persisted in DB on exception throw, as expected):
Price priceObj1 = new Price();
BeanUtils.copyProperties(priceObj, priceObj1);
updateEntity(priceObj1);
But in this also, I do not understand, why this is working ?
Other configuration details
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* com.dummy.offering.db.service..*Service.*(..)) || execution(* com.dummy.common.db.service..*Service.*(..))" />
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice" />
</aop:config>
If you rely on name="*InOwnTransaction" propagation="REQUIRES_NEW" to be working for the method call from updateOffering then this won't happen. You make an internal call (self-invocation) for a proxied class: the internal call to selectiveUpdateInOwnTransaction from updateOffering will be a call to a regular (non-proxied) method.
I strongly suggest to read carefully this section of the documentation.
To directly apply what you have in your code with the sample in the documentation: SimplePojo is your ServiceImpl, foo() is your updateOffering and bar() is your selectiveUpdateInOwnTransaction. Think about a proxy as an entirely new class that intercepts calls to your own methods and classes.
So, basically, when you call updateOffering from your controller you are calling updateOffering on a different class (which is not ServiceImpl) instance.
This new class applies the transactional behavior (starting a new transaction, associating transactional resources with the current thread etc) and then calls the real updateOffering from your own ServiceImpl.
updateOffering then calls selectiveUpdateInOwnTransaction, but since this call is like this.selectiveUpdateInOwnTransaction then the call would be on your ServiceImpl, not the newly created class that acts as a proxy. Because of this, Spring treats your selectiveUpdateInOwnTransaction as a regular, nothing special method.
On the other hand, if selectiveUpdateInOwnTransaction is called from anther class, that another class will call that method on the proxy, and this is why it works if you call it from a different ServiceImpl.
In that section of the documentation, there is an ugly solution to this restriction
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
but the real acceptable approach would be to redesign your classes a bit: move updateOffering to another class.