2014-12-30 3 views
7

@Transactional 어노테이션이 포함 된 Aspect를 만들었습니다. 내 충고는 예상대로 호출되지만 새로운 엔터티 AuditRecord는 데이터베이스에 저장되지 않습니다. 내 @Transactional 주석이 작동하지 않는 것처럼 보입니다. 다음과 같이@ Aspect (AOP)에서의 트랜잭션 @

@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; 
    } 

} 

빈 컨텍스트는 다음과 같습니다

<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> 

내 포인트 컷은 인터페이스 (서비스 인터페이스)를 차단한다. 서비스 메소드는 트랜잭션이 될 수도 있고 그렇지 않을 수도 있습니다. 서비스 메소드가 트랜잭션 인 경우, 어떤 이유로 Advice가 실패하면 해당 트랜잭션을 롤백하고 싶습니다.

내 질문 : 왜 트랜잭션 주석이 무시됩니까? AOP 서비스를 Spring으로 구축 한 것은 이번이 처음이며, 아키텍처 또는 구현 개선을 환영합니다.

감사합니다.

답변

9

스프링에서 @Transactional은 클래스 프록시 (Java 또는 cglib 프록시)를 만들고 주석 된 메소드를 가로 채서 작동합니다. 즉, 같은 클래스의 다른 메서드에서 주석 된 메서드를 호출하는 경우 @Transactional이 작동하지 않습니다.

createRecord 메소드를 새로운 클래스로 옮기면됩니다 (스프링 빈을 만드는 것도 잊지 마세요).

3

아주 좋은 질문입니다. 트랜잭션을 롤백/커밋해야한다면 here과 같이 스프링 트랜잭션을 직접 구성 할 수 있습니다.

이 방법을 사용하는 경우 수동으로 각 클래스/메소드에 @Transactional을 추가 할 필요가 없습니다.

스프링 구성은 다음과 같습니다 (참조 링크에서 복사). 커스텀 어드바이저/포인트 컷을 작성하는 대신, 스프링 - 애플리케이션 context.xml 파일에 어드바이저/포인트 컷과 함께 설정을 추가하면됩니다.

<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> 

심판 :

  1. 봄 트랜잭션 : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html