TransactonRollbackExceptionが納得行かない・・・

http://d.hatena.ne.jp/minokuba/20110501/1304265347の10.5.7.1 Requiredにある以下の記述が納得行かないんです。

PROPAGATION_REQUIREDの場合、内側のトランザクションがrollback-markを付与した場合はそれは外側のトランザクションに伝搬する。もし内側のトランザクションが指示かにrollback-markを付与したが、外側のトランザクションで明示的にロールバックしなかった場合は外側のトランザクションスコープに対するインターセプターでロールバックする際に、UnexpectedRollbackExceptionを送出する。よって外側のトランザクションを呼び出す人は、UnexpectedRollbackExceptionを必要に応じて適切に処理する必要がある*1

( ゚Д゚)ハァ? 俺の誤訳かと思ったけどたしかにそういう挙動する。

実際に実験すると以下のとおりっぽい。パターン。

  1. 内側のトランザクションメソッドで例外を投げた(ロールバックフラグ立てた)が、外側のメソッドで例外を握りつぶし、そのまま処理を終了
  2. 内側のトランザクションメソッドで例外を投げ、外側のメソッドでもそのままスロー
  3. 内側のトランザクションメソッドで例外を投げ、外側のメソッドは例外を握りつぶしたが、TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();で明示的にフラグを立てる

結果

  1. UnexpectedRollbackException
  2. スローされた例外がそのまま投げられる
  3. 例外発生しない

明示的に例外スローしなくてもUnexpectedRollbackExceptionが投げられるのが納得行かないわけです。例えばトランザクションロールバックしたとしても、業務的な応答を戻り値で返却するケースは多いと思うのですが、正しくリターンしたつもりでもUnexpcetedRollbackExceptionが投げられてしまうので戻り値返せないのは切ない。個人的には例外はBeanが送出した例外だけをそのまま送出すべきで、本来TransactonInterceptorで解決できることはTransactonInterceptorで解決し、例外が発生しないようにハンドリングすべきだと思うのです。というわけで暫定回避策インタセプタ。でもこんなことやるのはなにかがおかしい。。

サービスクラス。コントローラから呼び出される。


package hello.spring.transaction;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Service
@Transactional(propagation=Propagation.REQUIRED)
public class TransactionalService {

@Resource(name="dataSource")
private DataSource dataSource;

@Autowired
private NestTransactionalService nestTransaction = null;

@Resource(name="transactionManager")
private PlatformTransactionManager transactionManager;

public String execute() throws Exception{
Connection connection = dataSource.getConnection();
executeSomeSQL(connection);
try{
nestTransaction.service();
}catch (Exception e) {
e.printStackTrace();
}
return createReturnValue();
}
}

TransactionalServiceから呼び出されるサービスクラス


@Service
public class NestTransactionalService {

@Resource(name="dataSource")
private DataSource dataSource;

@Transactional(propagation=Propagation.REQUIRED)
public void service() throws Exception{
Connection connection = dataSource.getConnection();
executeSomeSQL(connection);
//わ・ざ・と♪
throw new RuntimeException();
}
}


//ロールバックマークが立っている場合に、対象のトランザクションスコープにおける
//トランザクションステータスをロールバックに確定する
@Aspect
public class SetRollbackOnlyInterceptor{

//サービスメソッドの両方に介入する
@Around("execution(public * hello.spring.transaction.*Service.*(..))")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable{

Throwable t = null;
Object retVal = null;
try{
retVal = joinPoint.proceed();
}catch (Throwable e) {
t = e;
}

try{
TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
if(transactionStatus.isRollbackOnly() && !transactionStatus.isCompleted()){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}catch (Throwable t2) {
t2.printStackTrace();
}

if(t != null){
throw t;
}else{
return retVal;
}
}
}

たしかにこうすると期待通りの動作するし、UnexpectedRollbackExceptionも発生しない。ただなんか納得行かない。そもそも、transactionStatus.isRollbackOnly()がtrueなのに、なんでsetRollbackOnlyを呼ばなければならないのか…。もっとマシな(まっとうな)やり方ご存じの方は教えてください。

*1:後半原文と日本語の意味変えてます。