GlassFish/Payaraで@Transactional境界内で発生した例外が失われる現象が治ってた!

普段から僕は、Java EEやるときはGlassFish/Payaraを使っているのですが、1つだけ懸念事項がありました。それが表題の現象です。それがPayara 4.1.1.154(おそらくGlassFish 4.1.1)で治っていた、というお話です。

どんな現象?

Java EEでWeb作るときは、永続化層(JPA)・ビジネスロジック層(CDI/EJB)・プレゼンテーション層(JSF/JAX-RS)というような階層構造にすることが多いと思います。

永続化層

@Dependent
public class EmployeeDao {
 @PersistenceContext(unitName = "empPU")
 private EntityManager em;

 @Transactional(TxType.REQUIRED)
 public Employee findById(Integer id) {
//  Employee emp = em.find(Employee.class, id);
//  return emp;
  throw new RuntimeException("ERROR IN DAO!!!");
 }

}

ビジネスロジック

@Dependent
public class EmployeeService {
 @Inject
 private EmployeeDao dao;

 @Transactional(TxType.REQUIRED)
 public Employee findById(Integer id) {
  Employee emp = dao.findById(id);
  return emp;
 }

}

プレゼンテーション層

@Path("employees")
public class EmployeeResource {
 @Inject
 private EmployeeService servicee;

 @GET
 @Path("{id}")
 public Response findById(@PathParam("id") Integer id) {
  Employee emp = service.findById(id);
  return Response.ok().entity(emp).build();
 }

}

永続化層で、わざとRuntimeExceptionをスローしています。

これを実行すると

これで実行してGlassFish/Payaraのログを見ると、javax.transaction.RollbackExceptionがスローされていると書かれています。RuntimeExceptionのスタックトレースは、表示されません。元の例外が失われてしまうのです。

なぜ、こうなるのか

上妻さんのブログで詳しく書かれています。昨年のGlassFishアドベントカレンダーの記事です。

GlassFish4.1をなおしてみた - 見習いプログラミング日記

簡単に言うと、javax.transaction.RollbackExceptionクラスにThrowable causeを引数に取るコンストラクタがそもそも定義されていないことや、トランザクションロールバック確定であるにも関わらずコミットしようとするコードが存在していたことが問題でした。

この問題はとても悩みの種でした。研修中にビジネスロジック以下の層で例外が発生した時にデバッグするのがとても難しかったり、開発現場やシステムの実運用環境でコレが起こったら大変だよなあ・・・と思ったり。

Java EEの大きなメリットの1つは、トランザクション管理をAPサーバーに任せることができるという点だと僕は思っているので、これはどうしたもんかなあと、長い間思っていました。

Payara 4.1.1.154で治ってた!

先日、Payara 4.1.1.154がリリースされました。で、ぼんやりとリリースノートを読んでいたら、「Upstream Fixes」に"GLASSFISH-21172 - javax.transaction.RollbackException from @Transactional bean has no cause set"の文言があるじゃありませんか!

http://www.payara.co.uk/release_notes

そして、リンクを辿るとGlassFishのJIRAへ。

[GLASSFISH-21172] javax.transaction.RollbackException from @Transactional bean has no cause set - Java.net JIRA

コメント一覧からさらにリンクを辿ると、ソースコードのDIFFが読めます。

https://java.net/projects/glassfish/sources/svn/revision/64135

おお、治ってる!しかも、上妻さんがブログに書かれていた修正案とほぼ、いや、全く同じ!

僕はPayara 4.1.1.154で動作確認しましたが、修正日付を見ると、おそらくGlassFish 4.1.1で修正されていたものと思われます。

で、修正後の挙動は?

これも上妻さんが書かれていた通りなのですが、@Transactional境界内で発生した例外がそのままスローされます。上記のコードならRuntimeExceptionですね。

まとめ

  • GlassFish/Payaraで例外が失われる現象が治っていました!
  • 上妻さん凄すぎる!