トッカンソフトウェア

SpringFrameworkでトランザクション(commit、rollback)(Javaアプリケーション)

今回はトランザクション(commit、rollback)をやります。


アノテーション(@Transactional)を使ってトランザクション

Springの設定ファイルにトランザクションの設定をして、クラスまたはメソッドに@Transactionalを付けるとトランザクション処理を行うことができます。
クラスまたはメソッドが正常に動作したときはコミットされ、例外発生で中断したときロールバックされます。対象となる例外クラスなど色々設定できますが、
今回はシンプルに例外が発生したらロールバックするサンプルを作ります。

動作させます

pom.xml、データクラス(MUser.java)は前回 と同様なので省略します。

Spring用の設定ファイル(SpringTest.xml)
				
<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:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/tx 
	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
	
	<bean id="helloWorld" class="spring.test.SpringBean">
	</bean>

	<context:component-scan base-package="spring.test" />

	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxTotal" value="${jdbc.maxTotal}" />
	</bean>

	<context:property-placeholder
		location="file:C:\\workspace\\springApp\\jdbc.properties" />

	<bean class="org.springframework.jdbc.core.JdbcTemplate">
		<constructor-arg ref="dataSource" />
	</bean>

	<tx:annotation-driven />

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
</beans>


			
annotation-drivenで名前空間に紐づくアノテーションが使えるようになります。今回の名前空間は http://www.springframework.org/schema/tx なので、@Transactionalが使えるようになります。

DataSourceTransactionManagerクラスでトランザクション管理をすると宣言しています。

実行対象となるJavaクラス(HelloWorldTest.java)
				
package spring.test;

import org.springframework.context.support.FileSystemXmlApplicationContext;

public class HelloWorldTest {
	public static void main(String[] args) {
		FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("C:\\workspace\\springApp\\SpringTest.xml");
//		FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("classpath:SpringTest.xml");

		SpringBean bean = (SpringBean) context.getBean("helloWorld");
		try {
			bean.insertUpdate();
		} catch (Exception e) {
			System.out.println("例外発生:" + e.toString());
		}
		bean.show();

		context.close();
	}
}


			
今回の設定では例外が発生したらロールバックされるのですが、ロールバックされたことを確認(ロールバック後にデータ抽出して確認)するためにtry-catchで囲みました。

処理は、新規登録(id:0005) → 更新(ここで例外発生) → データ抽出 の順で行い、新規登録分がロールバックで消えていることを確認します。

設定ファイルよりSpringがデータをセットするBeanクラス(SpringBean.java)
				
package spring.test;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;

public class SpringBean {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Transactional(rollbackFor = Exception.class)
	public void insertUpdate() {
		MUser user = new MUser();

		System.out.println("追加します");
		user.setId("0005");
		user.setName("新規追加");
		jdbcTemplate.update("insert into m_user(id, name) values(? , ?)", user.getId(), user.getName());

		System.out.println("更新します");
		user.setName("更新");
		jdbcTemplate.update("update m_userXX set name = ? where id = ?", user.getName(), user.getId());
	}

	public void show() {
		List<MUser> list = jdbcTemplate.query("select * from m_user", new RowMapper<MUser>() {
			public MUser mapRow(ResultSet rs, int rowNum) throws SQLException {
				MUser user = new MUser();
				user.setId(rs.getString("id"));
				user.setName(rs.getString("name"));
				return user;
			}
		});
		for (MUser user : list) {
			System.out.println(user.getId() + "-" + user.getName());
		}

	}
}

			
対象ソースでは、クラスまたはメソッドに@Transactionalを付けるだけでトランザクション処理対象になります。
今回は、INSERTは正常に行い、UPDATEでわざとテーブル名を間違えて指定し、例外を発生させています。

それでは実行してみましょう。

追加したID:0005が抽出されないので正常に動作したことが確認できました。

注意

				
	呼び出し元の処理
	bean.insertUpdate();
	
	
	
	呼び出し先の処理
	public void insertUpdate() {
		insertUpdate2();
	}

	@Transactional(rollbackFor = Exception.class)
	private void insertUpdate2() {
		MUser user = new MUser();

		System.out.println("追加します");
		user.setId("0005");
		user.setName("新規追加");
		jdbcTemplate.update("insert into m_user(id, name) values(? , ?)", user.getId(), user.getName());

		System.out.println("更新します");
		user.setName("更新");
		jdbcTemplate.update("update m_userXX set name = ? where id = ?", user.getName(), user.getId());
	}

			


設定ファイルだけでトランザクション

次にアノテーションを使わずに設定ファイルでトランザクション処理を行います。SpringのAOP(アスペクト指向プログラミング)という機能を使用します。
AOPについては後で書きます。

Maven用設定ファイル(pom.xml)
				
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>springTest</groupId>
	<artifactId>springTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<release>17</release>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>5.3.22</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.3.22</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.3.22</version>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<version>42.5.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.9.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.3.22</version>
		</dependency>
	</dependencies>
</project>

			
AOPを使用するためのライブラリを追加しています。

Spring用の設定ファイル(SpringTest.xml)
				
<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: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-4.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/tx 
	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
	http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean id="helloWorld" class="spring.test.SpringBean">
	</bean>

	<context:component-scan base-package="spring.test" />

	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxTotal" value="${jdbc.maxTotal}" />
	</bean>

	<context:property-placeholder
		location="file:C:\\workspace\\springApp\\jdbc.properties" />

	<bean class="org.springframework.jdbc.core.JdbcTemplate">
		<constructor-arg ref="dataSource" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<tx:advice id="txAdv" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="insert*" />
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<aop:advisor advice-ref="txAdv" pointcut="execution(* spring.test.SpringBean.*(..))" />
	</aop:config>
</beans>


			
トランザクション処理の指定などを行っています。

設定ファイルよりSpringがデータをセットするBeanクラス(SpringBean.java)
				
package spring.test;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;

public class SpringBean {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	// @Transactional(rollbackFor = Exception.class)
	public void insertUpdate() {
		MUser user = new MUser();

		System.out.println("追加します");
		user.setId("0005");
		user.setName("新規追加");
		jdbcTemplate.update("insert into m_user(id, name) values(? , ?)", user.getId(), user.getName());

		System.out.println("更新します");
		user.setName("更新");
		jdbcTemplate.update("update m_userXX set name = ? where id = ?", user.getName(), user.getId());
	}

	public void show() {
		List<MUser> list = jdbcTemplate.query("select * from m_user", new RowMapper<MUser>() {
			public MUser mapRow(ResultSet rs, int rowNum) throws SQLException {
				MUser user = new MUser();
				user.setId(rs.getString("id"));
				user.setName(rs.getString("name"));
				return user;
			}
		});
		for (MUser user : list) {
			System.out.println(user.getId() + "-" + user.getName());
		}
	}
}

			
@Transactionalをコメントにして、無効化しています。

今回もINSERTは正常に行い、UPDATEでわざとテーブル名を間違えて指定し、例外を発生させています。

追加したID:0005が抽出されないので正常に動作したことが確認できました。

個別のトランザクション処理

最後にクラス、メソッド単位ではなく、指定箇所のみで動作するトランザクション処理をやります。
SpringBean.java以外は、「アノテーション(@Transactional)を使ってトランザクション」と同一でいけます。

SpringBean.javaは以下のようにします。
				
package spring.test;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

public class SpringBean {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private PlatformTransactionManager txMgr;

	// @Transactional
	public void insertUpdate() {
		DefaultTransactionDefinition dtDef = new DefaultTransactionDefinition();

		TransactionStatus tSts = txMgr.getTransaction(dtDef);
		try {
			MUser user = new MUser();

			System.out.println("追加します");
			user.setId("0005");
			user.setName("新規追加");
			jdbcTemplate.update("insert into m_user(id, name) values(? , ?)", user.getId(), user.getName());

			System.out.println("更新します");
			user.setName("更新");
			jdbcTemplate.update("update m_userXX set name = ? where id = ?", user.getName(), user.getId());
			txMgr.commit(tSts);
		} catch (RuntimeException e) {
			txMgr.rollback(tSts);
			throw e;
		}
	}

	public void show() {
		List<MUser> list = jdbcTemplate.query("select * from m_user", new RowMapper<MUser>() {
			public MUser mapRow(ResultSet rs, int rowNum) throws SQLException {
				MUser user = new MUser();
				user.setId(rs.getString("id"));
				user.setName(rs.getString("name"));
				return user;
			}
		});
		for (MUser user : list) {
			System.out.println(user.getId() + "-" + user.getName());
		}
	}
}

			
DefaultTransactionDefinition は色々オプションが指定できますが、省略しました。

今回もINSERTは正常に行い、UPDATEでわざとテーブル名を間違えて指定し、例外を発生させています。

追加したID:0005が抽出されないので正常に動作したことが確認できました。

ページのトップへ戻る