概要
AOP(アスペクト指向プログラミング)を用いた、宣言的トランザクションの使い方についてまとめた。
AOPを利用してトランザクション管理を行うことで、煩雑なトランザクションまわりのコードを除外できる。
また、サービスクラス内のメソッド毎に@Transactionalを付与して個別に設定する必要もなくなる。
この記事では、AOPを用いた基本的なトランザクション管理の方法について紹介する。
前提
以下の記事の続きとなる。
概要 トランザクションとは何か、トランザクション管理を行う目的やどのように管理していくのかについてまとめた。 トランザクションとは トランザクションとは、「DBに対する一連の処理」を管理する仕組み。 トランザクシ[…]
事前準備
トランザクション管理を行うために、サービスクラスの用意とトランザクションマネージャの有効化及びAOPの設定を行う。
サービスクラスの用意
サービスクラスはDAOクラスと同様に、インターフェースと実装を分けて作成する。
インターフェース
以下の記事で紹介しているインターフェースを使用する。
概要 @Transactionalを用いた宣言的トランザクションの使い方についてまとめた。 アノテーションを利用してトランザクション管理を行うことで、煩雑なトランザクションまわりのコードを除外できる。 この記事では、@Transa[…]
実装
インターフェースのメソッドを実装する。
StudentServiceImpl.java
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentDao studentDao;
/** 全生徒情報を取得 */
@Override
public List<Student> findAllStudens() {
return studentDao.findAllStudens();
}
/** 生徒情報とテスト情報の作成 */
@Override
public int insertStudentInfo(Student student, List test) {
// 生徒情報作成
int studentId = studentDao.insertStudentAndGetId(student);
// テスト情報作成
test.forEach(t -> t.setStudentId(studentId));
studentDao.batchInsertTestPoints(test);
return studentId;
}
}
Transactionalアノテーションを使用したケースと異なり、サービスメソッドにてトランザクションまわりの設定を記述する必要はない。
そのため、開発者はビジネスロジックの作成に集中できる。(ただしAOPのトランザクション設定にて定義したメソッドのルールを考慮する必要がある)
トランザクションの有効化
AOPを使用してトランザクション管理を使用するため、必要な設定を追加する。
必要モジュールの追加
AOPの機能を利用するため、必要な資材をビルドファイルに追加する。
pom.xml
<!-- AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.20.RELEASE</version>
</dependency>
<!-- AspectJランタイム -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9</version>
</dependency>
<!-- AspectJウィーバー(オプション) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9</version>
</dependency>
AOPの依存資材については適宜必要なバージョンを指定すること。
DIコンテナに登録
Transactionalアノテーションを使用するときと同様に、トランザクションマネージャを使用するためDIコンテナに登録する。
また、あわせてAOPによるトランザクション設定もBean定義ファイルに追加する。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- メッセージ管理 -->
<!-- ReloadableResourceBundleMessageSourceを使用する場合 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames" value="classpath:META-INF/messages" />
<property name="cacheSeconds" value="3600" /><!-- 1時間ごとにリロード -->
</bean>
<!-- コンポーネントスキャン -->
<context:component-scan base-package="com.example.prototype.biz" />
<!-- H2 DBのデータソース -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" />
</bean>
<!-- H2 DB初期化 -->
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="classpath:sql/schema.sql" />
<jdbc:script location="classpath:sql/data.sql" />
</jdbc:initialize-database>
<!-- JDBC テンプレート -->
<!-- JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- NamedParameterJdbcTemplate -->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- トランザクションマネージャ -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- トランザクション定義設定 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" read-only="true" />
<tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="10" read-only="false" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- トランザクション処理を行う設定 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*Service.*(..))" />
</aop:config>
</beans>
<bean id=”transactionManager” class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”>
トランザクション管理を行うために、トランザクションマネージャをDIコンテナに登録する。
<tx:advice id=”txAdvice” transaction-manager=”transactionManager”>
「tx:advice」タグ内に、トランザクションのアドバイス(指示)を定義する。
トランザクション対象とするメソッド名と、具体的なトランザクションの設定を指定することができる。
<tx:method name=”insert*” propagation=”REQUIRED” isolation=”READ_COMMITTED” timeout=”10″ read-only=”false” rollback-for=”Exception” />
「find」で始まるメソッドに対しては読み取り専用トランザクションを、
「insert」で始まるメソッドに対しては、指定した伝搬や独立性レベルやタイムアウトなどのトランザクションを適用している。
<aop:config>
「aop:config」タグ内でアスペクト指向の設定を行う。
「aop:advisor」タグにて、アドバイザー(指示)とポイントカット(メソッドの実行ポイント)の関連付けを行う。
「advice-ref」属性にて、参照するアドバイスのIDを指定する。
今回だとトランザクションの指示をまとめた「txAdvice」が対象となる。
「pointcut」属性にて、アドバイスが適用されるポイントカットを指定する。
具体的な設定方法は以下のようになる。
今回指定しているポイントカット指定の意味は、以下のようになる。
②パッケージ: 任意
③型、クラス: 名前がServiceで終わる任意のクラス
④メソッド: 任意
⑤引数: 任意
以上により、StudentServiceクラス内のメソッド「findAllStudens」または「insertStudentInfo」を呼び出す際に、指定したトランザクション制御を行うことができる。