トランザクションの基本特性は以下の4つです。
- 原子性(Atomicity):すべての操作が成功するか、いずれか1つでも失敗した場合は全操作を取り消すこと。
- 一貫性(Consistency):トランザクション前後でデータの整合性が保たれること。例: AとBがそれぞれ100円所持する場合の合計200円が、振込後も変化しない。
- 隔離性(Isolation):複数のトランザクションが同時に実行されても、互いに干渉しないこと。
- 永続性(Durability):コミットされた変更は永続的にデータベースに保存されること。
以下に、银行振込処理を題材としたトランザクション管理の環境構築と実装例を紹介します。
1. データベースの準備
CREATE TABLE t_account (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
money INT
);
2. DAO・Serviceの実装
以下はXMLベースの設定ファイル(_${spring-config.xml}_)の例です。
<context:component-scan base-package="com.spring.test"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/user_db"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
DAOインタフェースおよび実装クラスです。アクセス対象のアカウント名・金額はコード中で固定しています。
package com.spring.test.dao;
public interface AccountDao {
void increaseBalance(String username, int amount);
void decreaseBalance(String username, int amount);
}
package com.spring.test.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void increaseBalance(String username, int amount) {
String sql = "UPDATE t_account SET money = money + ? WHERE username = ?";
jdbcTemplate.update(sql, amount, username);
}
@Override
public void decreaseBalance(String username, int amount) {
String sql = "UPDATE t_account SET money = money - ? WHERE username = ?";
jdbcTemplate.update(sql, amount, username);
}
}
サービス層では、複数のDAOメソッドを組み合わせたビジネスロジックを定義します。
package com.spring.test.service;
import com.spring.test.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TransferService {
@Autowired
private AccountDao accountDao;
// 通常の振込処理(例外発生例含む)
public void performTransfer() {
accountDao.decreaseBalance("lucy", 100);
// 振込中に意図的な例外発生(例: 内部システムエラー)
int x = 10 / 0;
accountDao.increaseBalance("marry", 100);
}
}
3. トランザクション未管理時の問題点
上記のまま実行すると、`decreaseBalance`が実行された直後に例外が発生した場合、「lucyの口座から減額されたがmarryには加算されない」状態が発生します。これはデータの不整合を生じます。
4. Springによる宣言的トランザクション管理
4.1 トランザクションマネージャの設定
<!-- 数据源dataSourceは前述の通り -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
4.2 @Transactionalアノテーションの適用
@Service
@Transactional
public class TransferService {
// 上記def一本で OK
}
この状態で`performTransfer`を実行すると、内部で例外が発生した場合にトランザクションがロールバックされ、lucy・marryともに口座残高が変化しないことが保証されます。
5. @Transactional属性の詳細
| 属性名 | 説明 |
|---|---|
propagation |
トランザクションの伝搬挙動(デフォルト: REQUIRED) |
isolation |
隔離レベル(デフォルト: データベース依存) |
timeout |
トランザクションのタイムアウト秒数(デフォルト: -1:なし) |
readOnly |
読み取り専用モード指定(クエリのみの場合はtrue推奨) |
rollbackFor |
明示的にロールバック対象にする例外クラス |
noRollbackFor |
ロールバック対象から除外する例外クラス |
5.1 伝搬挙動(Propagation)
- REQUIRED:既にトランザクションが存在すれば参加し、なければ新規作成。
- REQUIRES_NEW:常に新しいトランザクションを作成し、既存は一時中断。
- SUPPORTS:既存トランザクションがあれば参加、なければトランザクションなしで実行。
- MANDATORY:既存トランザクション内でのみ実行。ない場合は例外。
- NEVER:トランザクションなしでの実行を必須。ある場合は例外。
- NOT_SUPPORTED:トランザクションを一時中断して実行。
- NESTED:既存トランザクション内のサブトランザクション(Savepoint利用)。
5.2 隔離レベル(Isolation)
| レベル | チルドリード | 非再現リード | ファントムリード |
|---|---|---|---|
READ_UNCOMMITTED |
あり | あり | あり |
READ_COMMITTED |
なし | あり | あり |
REPEATABLE_READ |
なし | なし | あり |
SERIALIZABLE |
なし | なし | なし |
5.3 使用例
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ,
timeout = 10,
readOnly = false,
rollbackFor = {SQLException.class, RuntimeException.class}
)
public void performTransfer() {
// ...
}