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.