トランザクション

Doma はローカルトランザクションをサポートしています。このドキュメントでは、ローカルトランザクションの設定方法と使用方法について説明します。

グローバルトランザクションを使用する場合は、JTA (Java Transaction API) をサポートするフレームワークまたはアプリケーションサーバーを使用してください。

設定の定義 も参照してください。

設定

ローカル トランザクションを使用するには、次の条件が必要です。

  • Config 実装の getDataSource メソッドから LocalTransactionDataSource を返します。

  • コンストラクターで LocalTransactionDataSource を使用して LocalTransactionManager を作成します

  • この LocalTransactionManager を使用してデータベースアクセスを制御します

LocalTransactionManager を生成・取得する方法はいくつかありますが、最も簡単な方法は Config 実装クラスのコンストラクタ内で生成し、Config 実装クラスをシングルトンにすることです。

以下に例を示します。

public class DbConfig implements Config {

    private static final DbConfig CONFIG = new DbConfig();

    private final Dialect dialect;

    private final LocalTransactionDataSource dataSource;

    private final TransactionManager transactionManager;

    private DbConfig() {
        dialect = new H2Dialect();
        dataSource = new LocalTransactionDataSource(
                "jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null);
        transactionManager = new LocalTransactionManager(
                dataSource.getLocalTransaction(getJdbcLogger()));
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public static DbConfig singleton() {
        return CONFIG;
    }
}

使用法

次の例ではこのDAOインターフェースを使用します。

@Dao
public interface EmployeeDao {
    @Sql("select /*%expand*/* from employee where id = /*id*/0")
    @Select
    Employee selectById(Integer id);

    @Update
    int update(Employee employee);

    @Delete
    int delete(Employee employee);
}

トランザクションの開始と管理

次の TransactionManager のメソッドのいずれかを使用してトランザクションを開始できます。

  • required - 利用可能な既存のトランザクションを使用するか、存在しない場合は新しいトランザクションを作成します

  • requiresNew - 常に新しいトランザクションを作成し、既存のトランザクションを中断します

  • notSupported - トランザクションなしで実行され、既存のトランザクションを一時停止します

ラムダ式を使用して、トランザクションで実行するコードを定義します。

void doSomething() {
    TransactionManager tm = DbConfig.singleton().getTransactionManager();
    EmployeeDao dao = new EmployeeDaoImpl(DbConfig.singleton());
    
    tm.required(() -> {
        Employee employee = dao.selectById(1);
        employee.setName("hoge");
        employee.setJobType(JobType.PRESIDENT);
        dao.update(employee);
    });
}

トランザクションはラムダ式が正常に完了した場合、自動的にコミットされます。ラムダ式が例外をスローした場合、トランザクションは自動的にロールバックされます。

明示的なロールバック

例外をスローする以外に、setRollbackOnly メソッドを使用してトランザクションを明示的にロールバックすることもできます。

void doSomething() {
    TransactionManager tm = DbConfig.singleton().getTransactionManager();
    EmployeeDao dao = new EmployeeDaoImpl(DbConfig.singleton());
    
    tm.required(() -> {
        Employee employee = dao.selectById(1);
        employee.setName("hoge");
        employee.setJobType(JobType.PRESIDENT);
        dao.update(employee);
        // Mark as rollback
        tm.setRollbackOnly();
    });
}

セーブポイントの使用

セーブポイントを使用すると、トランザクションの特定の部分をロールバックしながら他の変更を維持できます。

void doSomething() {
    TransactionManager tm = DbConfig.singleton().getTransactionManager();
    EmployeeDao dao = new EmployeeDaoImpl(DbConfig.singleton());
    
    tm.required(() -> {
        // Search and update
        Employee employee = dao.selectById(1);
        employee.setName("hoge");
        dao.update(employee);
    
        // Create a savepoint
        tm.setSavepoint("beforeDelete");
    
        // Delete
        dao.delete(employee);
    
        // Rollback to the savepoint (cancel the deletion above)
        tm.rollback("beforeDelete");
    });
}