30.9. トランザクション

この章で紹介した例はすべて、トランザクション内で動作するように定義されたものです。トランザクションはプレロードされたデータの存続期間を決めるため、トランザクションの粒度は最適化ローディングにおいて主な要素です。トランザクションが完了、コミット、またはロールバックした場合は、プレロードキャッシュ内のデータが失われます。これは結果的に性能の面で深刻な影響を引き起こす可能性があります。
(結果セットを小さく保つため) 先頭の 4 人のギャングを選択する on-find 最適化クエリーを使用した例を用いて、トランザクションなしで実行した場合の性能上の影響をデモンストレーションし、これをラッパートランザクションなしで実行します。コード例は次のとおりです。
public String createGangsterHtmlTable_no_tx() throws FinderException
{
    StringBuffer table = new StringBuffer();
    table.append("<table>");

    Collection gangsters = gangsterHome.findFour();
    for(Iterator iter = gangsters.iterator(); iter.hasNext(); ) {
        Gangster gangster = (Gangster)iter.next();
        table.append("<tr>");
        table.append("<td>").append(gangster.getName());
        table.append("</td>");
        table.append("<td>").append(gangster.getNickName());
        table.append("</td>");
        table.append("<td>").append(gangster.getBadness());
        table.append("</td>");
        table.append("</tr>");
    }
    
    table.append("</table>");
    return table.toString();
}
この finder の結果、次のクエリーが実行されます。
SELECT t0_g.id, t0_g.name, t0_g.nick_name, t0_g.badness
  FROM gangster t0_g
  WHERE t0_g.id < 4
  ORDER BY t0_g.id ASC
通常はこれが実行される唯一のクエリーになりますが、このコードはトランザクション内で実行されていないために、finder が戻るとすぐに、プレロードされたデータはすべて破棄されます。そのため、CMP フィールドにアクセスすると、JBoss は次の 4 つのクエリー (各ループに対して 1 つ) を実行します 。
SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=0) OR (id=1) OR (id=2) OR (id=3)
SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=1) OR (id=2) OR (id=3)
SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=2) OR (id=3)
SELECT name, nick_name, badness
  FROM gangster
  WHERE (id=3)
実際にはこれよりも悪い事態になります。JBoss は、各クエリーを 3 回、アクセスされた各 CMP フィールドに対して 1 回ずつ実行します。その理由は、CMP フィールドアクセッサーの呼び出しと次の呼び出しの間で、プレロードされた値が破棄されるからです。
次の図は、クエリーの実行を示したものです。
トランザクションなし on-find 最適化クエリー実行

図30.13 トランザクションなし on-find 最適化クエリー実行

データベースからロードされるデータ量のせいで、先読みなしにした場合よりもパフォーマンスが悪くなります。ロードされる行の数は、次の式で求められます。
例の中のトランザクションはエンティティに対する 1 回の呼び出しによって境界が引かれるため、これはすべて起こります。これは「トランザクション内でどのようにコードを実行するか」という重要な問題を提起します。その答えは、コードを実行する場所によって変わります。EJB (セッション、エンティティ、またはメッセージ駆動型) で実行する場合は、assembly-descriptorRequired または RequiresNewtrans-attribute をメソッドに付けなければなりません。コードを EJB で実行していない場合は、ユーザートランザクションが必要です。次のコードでは、ユーザートランザクションによって、宣言されたメソッドに対する呼び出しをラップしています。
public String createGangsterHtmlTable_with_tx()
    throws FinderException
{
    UserTransaction tx = null;
    try {
        InitialContext ctx = new InitialContext();
        tx = (UserTransaction) ctx.lookup("UserTransaction");
        tx.begin();

        String table = createGangsterHtmlTable_no_tx();
	
        if (tx.getStatus() == Status.STATUS_ACTIVE) {
	        tx.commit();
        }
	    return table;
    } catch (Exception e) {
        try {
            if (tx != null) tx.rollback();
        } catch (SystemException unused) {
            // eat the exception we are exceptioning out anyway
        }
        if (e instanceof FinderException) {
	        throw (FinderException) e;
        }
        if (e instanceof RuntimeException) {
	        throw (RuntimeException) e;
        }

        throw new EJBException(e);
    }
}