web-dev-qa-db-ja.com

Spring @Transactional in a Aspect(AOP)

@Transactionalアノテーションを含むアスペクトを作成しました。私のアドバイスは期待どおりに呼び出されていますが、新しいエンティティAuditRecordがデータベースに保存されることはなく、@ Transactionalアノテーションが機能していないようです。

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

}

Beanのコンテキストは次のとおりです。

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

私のポイントカットは、インターフェイス(サービスインターフェイス)のみをインターセプトしています。サービスメソッドはトランザクションである場合とそうでない場合があります。サービスメソッドがトランザクションである場合、何らかの理由でアドバイスが失敗した場合は、そのトランザクションをロールバックしたいと思います。

私の質問:トランザクションアノテーションが無視されるのはなぜですか? SpringでAOPサービスを構築するのはこれが初めてですが、アーキテクチャや実装の改善も歓迎します。

ありがとう!

12
levacjeep

春に、 @Transactionalは、クラスのプロキシ(a Javaまたはcglibプロキシ))を作成し、注釈付きメソッドをインターセプトすることで機能します。これは、@Transactional同じクラスの別のメソッドからアノテーション付きメソッドを呼び出している場合は、機能しません。

createRecordメソッドを新しいクラスに移動するだけで(Spring Beanにすることも忘れないでください)、機能します。

とても良い質問です。トランザクションをロールバック/コミットする必要がある場合は、前述のようにスプリングトランザクションを直接構成できます ここ

このメソッドを実行するために、各クラス/メソッドに手動で@Transactionalを追加する必要はありません。

Springの構成は以下のとおりです(参照リンクからコピー)。カスタムアドバイザ/ポイントカットを作成する代わりに、advisors/pointcutsを使用してspring-applicationcontext.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. Springトランザクション: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html
3
Zeus