Spring #Transactional in an Aspect (AOP) - java

I've created an Aspect which contains an #Transactional annotation. My advice is being invoked as expected, but the new entity AuditRecord is never saved to the database, it looks like my #Transactional annotation is not working.
#Aspect
#Order(100)
public class ServiceAuditTrail {
private AppService appService;
private FooRecordRepository fooRecordRepository;
#AfterReturning("execution(* *.app.services.*.*(..))")
public void logAuditTrail(JoinPoint jp){
Object[] signatureArgs = jp.getArgs();
String methodName = jp.getSignature().getName();
List<String> args = new ArrayList<String>();
for(Object arg : signatureArgs){
args.add(arg.toString());
}
createRecord(methodName, args);
}
#Transactional
private void createRecord(String methodName, List<String> args){
AuditRecord auditRecord = new AuditRecord();
auditRecord.setDate(new Date());
auditRecord.setAction(methodName);
auditRecord.setDetails(StringUtils.join(args, ";"));
auditRecord.setUser(appService.getUser());
fooRecordRepository.addAuditRecord(auditRecord);
}
public void setAppService(AppService appService) {
this.appService = appService;
}
public void setFooRecordRepository(FooRecordRepository fooRecordRepository) {
this.fooRecordRepository= fooRecordRepository;
}
}
The bean context is as follows:
<tx:annotation-driven transaction-manager="txManager.main" order="200"/>
<aop:aspectj-autoproxy />
<bean id="app.aspect.auditTrail" class="kernel.audit.ServiceAuditTrail">
<property name="appService" ref="app.service.generic" />
<property name="fooRecordRepository" ref="domain.repository.auditRecord" />
</bean>
My pointcut is intercepting only interfaces (service interfaces). The service methods may or may not be transactional. If the service method is transactional, I would like that transaction to be rolled back if the Advice fails for some reason.
My question: Why is the transactional annotation being ignored? This is my first time building an AOP service with Spring, I would also welcome any architectural or implementation improvements.
Thanks!

In Spring, #Transactional works by creating a proxy of your class (either a Java or cglib proxy) and intercepting the annotated method. This means that #Transactional doesn't work if you are calling the annotated method from another method of the same class.
Just move the createRecord method to a new class (don't forget to make it a Spring bean too) and it will work.

A very good question. If you have to rollback/commit the transactions you can directly configure the spring transactional as mentioned here.
Doing this method does not require you to add #Transactional on each of the class/method manually.
Spring configuration is below(copied from the reference link ). Instead of writing custom advisors/pointcuts just add configuration in spring-application context.xml file with your advisors/pointcuts.
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 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>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
ref:
Spring transactional : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

Related

saveorupdate call in Junit test case throws no transaction in progress error

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.

Spring AOP for restful web service

I am refering to Spring AOP by Mkyong http://www.mkyong.com/spring/spring-aop-example-pointcut-advisor/
It worked when tried running from main (i.e. App.java) as given on above link,
I want to integrate it in restful webservice where i have multiple service like CutomerService in mkyong's example.
For example i have controller which calls CustomerService,
#RestController
#RequestMapping(value = "/customer")
public class CustomerController{
#Autowired CustomerService customerService;
#RequestMapping(value = "/getCustomer", method = RequestMethod.GET")
public ResponseEntity<CommonResponse> getService(){
customerService.printName();
}
}
It didn't worked.
i have tried this also:
#Autowired
private ProxyFactoryBean customerServiceProxy;
#RequestMapping(value = "/getCustomer", method = RequestMethod.GET")
public ResponseEntity<CommonResponse> getService(){
CustomerService customerService = (CustomerService) customerServiceProxy
.getTargetSource().getTarget();
customerService.printName();
}
}
this dosent work either.
Any Solution for this?
my bean-config.xml is same as mkyong's example.
It worked ! Just need to change proxy class from "org.springframework.aop.framework.ProxyFactoryBean" to
"org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
Auto proxy creator identifies beans to proxy via a list of names. So we don,t need to specify target bean instead list of beans can be specified and it will intercept automatically.
My config look as below:
<bean id="adviceAround" class="com.tdg.ess.semantic.event.log.AdviceAround" />
<!-- Event logging for Service -->
<bean id="testServicePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>process</value>
</list>
</property>
</bean>
<bean id="testServiceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="testServicePointCut" />
<property name="advice" ref="adviceAround" />
</bean>
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<!-- Differnt beans which have certain service defined like CustomerService-->
<value>testService1</value>
<value>testService2</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>testServiceAdvisor</value>
</list>
</property>
</bean>
Thanks everyone for looking into.

JPA find method persists data

I am using spring jpa. I have implemented transaction management using
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<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" />
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="transactionalServiceOperation"
expression="execution(* com.test..*ServiceImpl.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionalServiceOperation" />
</aop:config>
Recently i noticed that although in some cases transaction management is working but in other cases it is not. For example , i have this piece of code inside a service method which is annotated by #org.springframework.transaction.annotation.Transactional.
The code inside the method
guardianService.save(newFather);
parentsSet.add(newFather);
Guardian oldMother = guardianService.findById(motherId);
In the above case the data in the database does not persis till the 3rd line. As soon as the application finishes executing the 3rd line the data of newFather gets comited to the database even if the application generates Exception after the 3rd line.
Code snippet of guardianService.findById(motherId)
#Override
public Guardian findById(long guardianId) {
return guardianRepository.findByGuardianId(guardianId);
}
Inside GuardianRepository
public interface GuardianRepository extends JpaRepository<Guardian, Long> {
Guardian findByGuardianId(long id);
}
Code snippet of guardianService.save(newFather);
#Override
#Transactional
public Guardian save(Guardian guardian) {
return guardianRepository.save(guardian);
}
So my question is does the find() method somehow calls the flush() or commit()?
The find() method actually does invoke the flush method. By default in JPA, FlushModeType is set to AUTO, which means that if query to database occurs, the data in database has to be up to date for current transaction. From definition:
When queries are executed within a transaction, if FlushModeType.AUTO
is set on the Query object, or if the flush mode setting for the
persistence context is AUTO (the default) and a flush mode setting has
not been specified for the Query object, the persistence provider is
responsible for ensuring that all updates to the state of all entities
in the persistence context which could potentially affect the result
of the query are visible to the processing of the query.

#Guarded not validating #NotNull constructor fields

I'm trying to use Oval 1.84 for getting around some validation constraints without boilerplates. The validation works when I mark fields with #NotNull (javax.validation.constraint and net.sf.oval.validator).
But doesn't work in the case of implementing constarints to method and constructor parameters.
Parameter validation requires the use of some method invocation intercepting bytecode. OVal provides ready to use implementations for AspectJ and Spring AOP.
With AspectJ
How to use it with AspectJ is documented in detail at http://oval.sourceforge.net/userguide.html#programming-by-contract
With Spring AOP
The usage with Spring AOP is outlined in the test case at
https://svn.code.sf.net/p/oval/code/trunk/src/test/java/net/sf/oval/test/integration/spring/SpringAOPAllianceTest.java
In Spring you need to configure your beans for which you want to have method parameter validation, e.g.:
<bean id="myService" class="com.example.MyService" />
And an Invocation Interceptor:
<bean id="ovalGuardInterceptor" class="net.sf.oval.guard.GuardInterceptor" />
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="false" />
<property name="interceptorNames">
<list>
<value>ovalGuardInterceptor</value>
</list>
</property>
<!-- the next line tells which beans you want to use validation for -->
<property name="beanNames" value="myService" />
</bean>

Why does not "#Transactional(propagation = propagation.NEVER)" work?

In the Spring docs, for NEVER propagation:
Execute non-transactionally, throw an exception if a transaction
exists.
I wanted to try like following:
#Transactional(propagation = Propagation.NEVER)
public void getDeps(long ID) {
System.out.println(databaseImp.getDepartmentByID(ID));
}
#Transactional(propagation = Propagation.REQUIRED)
public void allProcessOnDB_second(long ID) {
getDeps(ID);
operation(ID);
}
#Transactional
public void operation(long id){
System.out.println(databaseImp.getDepartmentByID(id));
}
Well, it is not important what code wants to do.
I use the #Transactional(propagation = Propagation.NEVER) and I use this method in any transactional method but it doesn't work. I mean it must throw an exception, but it doesn't.
My Spring meta configuration file (XML) contains the following:
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="database.transactionmanagement"/>
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="datasource2"/>
</bean>
<bean id="datasource2" class="org.apache.tomcat.dbcp.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/hr"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>
Thanks for your answers.
#Transactional annotations only apply to the Spring proxy objects. For example, if you call allProcessOnDB_second() from some spring bean which injects your service like this
#Autowired
private MyService myService;
...
myService.allProcessOnDB_second();
then myService is Spring proxy, and its #Transactional(propagation = Propagation.REQUIRED) is applied. If you were to call myService.getDeps(id) then #Transactional(propagation = Propagation.NEVER) would be applied.
But when you call the first method, and then second method from it, then second method isn't called through Spring proxy but rather directly, so its transactional configuration is ignored.
Spring transactions are proxy-based. That exception would be thrown if a bean A called another bean B, because the transactional aspect would intercept the call and throw the exception. But here, you're calling another method in the same object, and the transactional proxy is thus out of the picture.

Categories

Resources