ããã¯ããªã«ãããããŠæžãããã®ïŒ
Spring Frameworkã䜿ã£ãŠãããšããã©ã³ã¶ã¯ã·ã§ã³ç®¡çã@Transactional
ã¢ãããŒã·ã§ã³ã䜿ã£ãŠå®£èšçã«æžããŠããããšã
å€ããšæããŸãã
@Transactional
ã䜿ã£ãå ŽåãäŸå€ïŒããã©ã«ãã§ã¯RuntimeException
ã®ãµãã¯ã©ã¹ïŒãã¹ããŒãããæã«ããŒã«ããã¯ããã
ããšã«ãªã£ãŠããŸããããã§ã@Transactional
ã¢ãããŒã·ã§ã³ãä»äžãããã¡ãœããããã¹ããããã€éäžã§äŸå€ã
ææããå Žåã«ã©ãããæåã«ãªãã®ãã確èªããŠã¿ãããªãšæããŸããŠã
ãã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã«ã€ããŠã¯ãPROPAGATION_REQUIRED
ãäž»ãªå¯Ÿè±¡ã«ããŠããŸãã
Springã®å®£èšçãã©ã³ã¶ã¯ã·ã§ã³ã«é¢ããããã¥ã¡ã³ããšããŠã¯ããã¡ãã§ããã
Declarative Transaction Management
ãŸãã@Transactional
ã¢ãããŒã·ã§ã³ã«ã€ããŠã¯ããã¡ãã«èšèŒãããŠããŸãã
Using @Transactional
ããã§ããã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã«é¢ããããã¥ã¡ã³ããèªãã§ã¿ãŸãã
Transaction Propagation
æåã«çåã«æžããããšã®çãããå®ã¯ããã«æžããŠããŸãã
Understanding PROPAGATION_REQUIRED
PROPAGATION_REQUIRED
ã䜿ã£ããã©ã³ã¶ã¯ã·ã§ã³ããã¹ãããå
åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ã®ããŒã«ããã¯ã確å®ããå Žåã®
ããšãæžããŠãããŸãã
When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon which the setting is applied. Each such logical transaction scope can determine rollback-only status individually, with an outer transaction scope being logically independent from the inner transaction scope. In the case of standard PROPAGATION_REQUIRED behavior, all these scopes are mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transactionâs chance to actually commit.
However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So, if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.
ã€ãŸããå
åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ã®ããŒã«ããã¯ã確å®ããŠãããšå€åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ã¯UnexpectedRollbackException
ã
ã¹ããŒããããšããããšã«ãªãããã§ãã
å
åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ãç¬ç«ããããå Žåã¯ãPROPAGATION_REQUIRES_NEW
ã䜿ãããã§ããã
Understanding PROPAGATION_REQUIRES_NEW
ä»åã¯ããããŸã§å«ããŠç¢ºèªããŠã¿ããããªããšæããŸãã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã§ãã
$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)
$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-73-generic", arch: "amd64", family: "unix"
ããŒã¿ããŒã¹ã¯MySQL 8.0.25ã䜿ãã172.17.0.2ã§åäœããŠãããã®ãšããŸãã
Spring Bootãããžã§ã¯ããäœæãã
æåã«ãSpring Bootãããžã§ã¯ããäœæããŸããSpring Bootã®ããŒãžã§ã³ã¯2.5.0ã§ãäŸåé¢ä¿ã«ã¯jdbc
ãšmysql
ãå ããŸãã
$ curl -s https://start.spring.io/starter.tgz \
-d bootVersion=2.5.0 \
-d javaVersion=11 \
-d name=transactional-nested-required \
-d groupId=org.littlewings \
-d artifactId=transactional-nested-required \
-d version=0.0.1-SNAPSHOT \
-d packageName=org.littlewings.spring.jdbc \
-d dependencies=jdbc,mysql \
-d baseDir=transactional-nested-required | tar zxvf -
$ cd transactional-nested-required
$ find src -name '*.java' | xargs rm
çæãããJavaãœãŒã¹ã³ãŒãã¯åé€ã
MavenäŸåé¢ä¿ãªã©ã¯ããããªæãã§ãã
<properties>
<javaversion>11</javaversion>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
確èªã¯ãã¹ãã³ãŒãã§è¡ãããšæããŸããã@SpringBootApplication
ã¢ãããŒã·ã§ã³ãä»äžããã¯ã©ã¹ã¯äœæããŠ
ãããŸãã
src/main/java/org/littlewings/spring/jdbc/App.java
package org.littlewings.spring.jdbc;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
}
application.properties
ã¯ããããªèšå®ã«ããŠãããŸããã
src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8
spring.datasource.username=kazuhira
spring.datasource.password=password
ãé¡
@Transactional
ã¢ãããŒã·ã§ã³ãä»äžããã¡ãœãããæã€ã¯ã©ã¹ãäœæãããµã€ãã«åŠçãå®äºãããããäŸå€ãã¹ããŒããŠ
ãã£ãããããããªãã£ããâŠãšããã€ãããªãšãŒã·ã§ã³ãã€ããŠç¢ºèªããŠã¿ãŸãããã
ãã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã¯PROPAGATION_REQUIRES
ããå§ããPROPAGATION_REQUIRES_NEW
ãç¹ã亀ããŠ
ããããã«ããŸãã
ãã¹ãã³ãŒãã®é圢
ãŸãã¯ãã¹ãã³ãŒãã®é圢ãäœæããŸãããããããªæãã«ããŸããã
src/test/java/org/littlewings/spring/jdbc/TransactionalTest.java
package org.littlewings.spring.jdbc;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.UnexpectedRollbackException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SpringBootTest
public class TransactionalTest {
@Autowired
JdbcTemplate jdbcTemplate;
@BeforeEach
public void createTable() {
jdbcTemplate.execute("drop table if exists sample");
jdbcTemplate.execute("create table sample(word varchar(25));");
}
}
ãã¹ãããšã«ãããŒãã«ãDROP ïŒ CREATEããŸãã
Serviceã¯ã©ã¹
ä»åã¯ãServiceã¯ã©ã¹ã2ã€çšæããŸããããããã@Transactional
ã¢ãããŒã·ã§ã³ãä»äžããã¡ãœãããæã¡ã
å€åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ãå
åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ãè¡šçŸããŸãã
å€åŽã«è©²åœãããã®ã¯ããã¡ããJdbcTemplate
ãšãå
åŽã®Serviceã¯ã©ã¹ã䜿çšããŸãã
src/main/java/org/littlewings/spring/jdbc/MyService.java
package org.littlewings.spring.jdbc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
Logger logger = LoggerFactory.getLogger(MyService.class);
JdbcTemplate jdbcTemplate;
NestedService nestedService;
public MyService(JdbcTemplate jdbcTemplate, NestedService nestedService) {
this.jdbcTemplate = jdbcTemplate;
this.nestedService = nestedService;
}
}
MyService
ã¯ã©ã¹ããåŒã³åºãããã®ã¯ããã¡ããåãããJdbcTemplate
ã䜿çšããŸãã
src/main/java/org/littlewings/spring/jdbc/NestedService.java
package org.littlewings.spring.jdbc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class NestedService {
Logger logger = LoggerFactory.getLogger(NestedService.class);
JdbcTemplate jdbcTemplate;
public NestedService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
ãããã®ã¯ã©ã¹ãšãã¹ãã³ãŒãã䜿ã£ãŠã確èªããŠããŸãããã
ãã¹ãã³ãŒãåŽã§ã¯ãäžèšã®MyService
ã¯ã©ã¹ã䜿çšããŸãã
@SpringBootTest
public class TransactionalTest {
@Autowired
JdbcTemplate jdbcTemplate;
@BeforeEach
public void createTable() {
jdbcTemplate.execute("drop table if exists sample");
jdbcTemplate.execute("create table sample(word varchar(25));");
}
@Autowired
MyService myService;
ããããå
ã¯ãMyService
ãNestedService
ããã¹ãã³ãŒãã®é ã§ããããæžããŠç¢ºèªããŠãããŸãã
PROPAGATION_REQUIRES
ã§ã¯ã確èªããŠãããŸãããã
ã³ããããã
ãŸãã¯ãµã€ãã«ãã©ã³ã¶ã¯ã·ã§ã³ãå®äºããïŒïŒã³ãããããïŒãã¿ãŒã³ãæå®ãããwordãç»é²ããinsert
æãå®è¡ããŠã
ãã®wordãäºéã«ããŠæ¬¡ã®Serviceã¯ã©ã¹ãåŒã³åºããŸãã
@Service
public class MyService {
@Transactional
public int insertRequired(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequired(word + " " + word);
}
}
ã¯ã©ã¹ã®å®çŸ©ã¯ãæç²ããŠæžããŠãããŸãã
åŒã³åºãå
ã
@Service
public class NestedService {
@Transactional
public int insertRequired(String word) {
return jdbcTemplate.update("insert into sample(word) values(?)", word);
}
}
ã©ã¡ããæŽæ°ä»¶æ°ãè¿ãã®ã§ãäž¡æ¹ã®æŽæ°ãããŸããã£ãå Žåã¯æ»ãå€ã2ã«ãªããŸããã
ãã¹ãã³ãŒããããŒã¿ãç»é²ãããŠããã®ã確èªã§ããŸãã
@Test
public void transactionalNormally() {
assertThat(myService.insertRequired("Hello!!")).isEqualTo(2);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!", "Hello!! Hello!!"));
}
次ã¯ãããŒã«ããã¯ãããã¿ãŒã³ã
å€åŽã®Serviceã¯ã©ã¹ã¯ãµã€ãã§ãã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedThrown(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredAndThrown(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã§ã¯äŸå€ãã¹ããŒããŸãã
@Service
public class NestedService {
@Transactional
public int insertRequiredAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
ãã¹ãã³ãŒãåŽãããŒã«ããã¯ã§ããã
@Test
public void transactionalNestedFailed() {
assertThatThrownBy(() -> myService.insertRequiredAndNestedThrown("Hello!!"))
.isInstanceOf(RuntimeException.class)
.hasMessage("Oops!!");
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEmpty();
}
å
åŽã®Serviceã¯ã©ã¹ãäŸå€ãã¹ããŒããå€åŽã®Serviceã¯ã©ã¹å
ã§ææãã
ç¶ããŠã¯ãå
åŽã®Serviceã¯ã©ã¹ã®ã¡ãœãããäŸå€ãã¹ããŒããŠãå€åŽã®Serviceã¯ã©ã¹ã§ãã®äŸå€ãè£è¶³ãããã¿ãŒã³ã
ã€ãŸããå€åŽã®Serviceã¯ã©ã¹ã¯ãããªæãã§ãã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedThrownAndCatch(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
try {
nestedService.insertRequiredAndThrown(word + " " + word);
} catch (RuntimeException e) {
logger.error("insert failed", e);
}
return result;
}
}
å
åŽã®Serviceã¯ã©ã¹ã¯ãå
ã»ã©ãšåãã§ãã
@Service
public class NestedService {
@Transactional
public int insertRequiredAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
ãã¹ãã³ãŒãã¯å€åŽã®Serviceã¯ã©ã¹ããäŸå€ãã¹ããŒãããªãâŠãšæããããUnexpectedRollbackException
ãã¹ããŒããã
ããšã«ãªããŸãã
ãããªããšãèšããã€ã€ã
Transaction rolled back because it has been marked as rollback-only
@Test
public void transactionalNestedFailedAndCatch() {
assertThatThrownBy(() -> myService.insertRequiredAndNestedThrownAndCatch("Hello!!"))
.isInstanceOf(UnexpectedRollbackException.class)
.hasMessage("Transaction rolled back because it has been marked as rollback-only");
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEmpty();
}
ããã¥ã¡ã³ãéãã§ããã
å
åŽã®Serviceã¯ã©ã¹ã§äŸå€ã¯çºçãããã®ã®ãã¡ãœãããäŸå€ã§æããªã
ãªã«ãèšã£ãŠããããšãããšãå
åŽã®Serviceã¯ã©ã¹ã§äŸå€ã¯çºçãããã®ã®ããã©ã³ã¶ã¯ã·ã§ã³ã®å¢çãšãªãã¡ãœããã¯
äŸå€ã§è±åºããªãããšã
å€åŽã®Serviceã¯ã©ã¹ã¯ããããªæãã§ãã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedIgnoreFailed(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredAndIgnoreFailed(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã¯èª€ã£ãSQLæãå®è¡ãããŠäŸå€ãçºçããŸãããã¡ãœããèªäœã¯äŸå€ã§ã¯æããŸããã
@Service
public class NestedService {
@Transactional
public int insertRequiredAndIgnoreFailed(String word) {
try {
jdbcTemplate.update("insert into sample(word) v(?)", word);
} catch (RuntimeException e) {
logger.error("sql error", e);
}
return 0;
}
}
ãã¹ãã³ãŒãã§ã¯ãå€åŽã®Serviceã¯ã©ã¹ã®åŠççµæã ããåæ ïŒïŒã³ãããïŒãããŠããããšã確èªã§ããŸãã
@Test
public void transactionalNestedIgnoreFailed() {
assertThat(myService.insertRequiredAndNestedIgnoreFailed("Hello!!")).isEqualTo(1);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!"));
}
äºæ³ã§ãã話ã§ã¯ãããŸããããã©ã³ã¶ã¯ã·ã§ã³ã®å¢çãäŸå€ã§æããŠããŸãããããã
ã€ãŸãããã©ã³ã¶ã¯ã·ã§ã³ã®äŒæãPROPAGATION_REQUIRED
ã®ç©ã¿éããšãªãããå Žåã¯ãäžéå端ãªãšããã§
äŸå€ãæãŸããã«ãã©ã³ã¶ã¯ã·ã§ã³ã®å¢çå€ã§ææãããããã©ã³ã¶ã¯ã·ã§ã³ã®å¢çãè·šããªããã¡ã«ææããããšããšãã
æãã«ããæ¹ãè¯ãããã§ããã
ãã®ãããã®åäœãããœãŒã¹ã³ãŒãäžã§ã確èªããŠã¿ãŸãããã
ãã©ã³ã¶ã¯ã·ã§ã³å¢çãšãªãã¡ãœãããäŸå€ã§æããæç¹ã§ãããŒã«ããã¯ã®ããŒã¯ãè¡ãããŸãã
https://github.com/spring-projects/spring-framework/blob/v5.3.7/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java#L844
ResourceHolderSupport
ã§ã®rollbackOnly
ãtrue
ã«èšå®ãããã®ãããã®ããŒã¯ã§ããã
https://github.com/spring-projects/spring-framework/blob/v5.3.7/spring-tx/src/main/java/org/springframework/transaction/support/ResourceHolderSupport.java#L67-L69
ãã®ç¶æ
ã«ãªããšãå€åŽã®ãã©ã³ã¶ã¯ã·ã§ã³å¢çãã³ãããããããšããã¿ã€ãã³ã°ã§ãããŒã«ããã¯ãè¡ãããã«ããŒã¯ãããŠ
ããããšãæ€åºãããŸãã
https://github.com/spring-projects/spring-framework/blob/v5.3.7/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java#L703-L709
ãããŠãUnexpectedRollbackException
ãã¹ããŒãããŸãããšã
https://github.com/spring-projects/spring-framework/blob/v5.3.7/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java#L869-L872
ãã¡ããããœãŒã¹ã³ãŒãäžã§ã®ç¢ºèªã§ããã
ããšã¯ãããå°ãããªãšãŒã·ã§ã³ã確èªããŠã¿ãŸãããã
PROPAGATION_REQUIRESãšPROPAGATION_REQUIRES_NEWã®çµã¿åãã
ä»åºŠã¯ãå€åŽãPROPAGATION_REQUIRES
ãå
åŽãPROPAGATION_REQUIRES_NEW
ã«ããŠã¿ãŸãã
å€åŽãšå
åŽã§ããã©ã³ã¶ã¯ã·ã§ã³ãå¥ã«ãªããŸããã
ã³ããããã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedNew(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNew(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ããã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã¬ãã«ããREQUIRES_NEW
ã§ãã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNew(String word) {
return jdbcTemplate.update("insert into sample(word) values(?)", word);
}
}
ãšã¯ãããã³ãããããã®ã§çµæã¯ãŸããµã€ãã§ãã
@Test
public void transactionalNormallyNestedNew() {
assertThat(myService.insertRequiredAndNestedNew("Hello!!")).isEqualTo(2);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!", "Hello!! Hello!!"));
}
ç¶ããŠãããŒã«ããã¯ããã¡ããå€ãã£ãããšã¯ãããŸããã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedNewThrown(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNewAndThrown(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
äž¡æ¹ã®ã¡ãœãããäŸå€ã§æããã®ã§ãããŒã«ããã¯ããŸãã
@Test
public void transactionalNestedNewFailed() {
assertThatThrownBy(() -> myService.insertRequiredAndNestedNewThrown("Hello!!"))
.isInstanceOf(RuntimeException.class)
.hasMessage("Oops!!");
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEmpty();
}
å
åŽã®Serviceã¯ã©ã¹ãäŸå€ãã¹ããŒããå€åŽã®Serviceã¯ã©ã¹å
ã§ææãã
ãã¡ãã¯ãPROPAGATION_REQUIRED
ã®æãšå€åããããŸãã
å€åŽã®Serviceã¯ã©ã¹ãå
åŽã®Serviceã¯ã©ã¹ãã¹ããŒããäŸå€ãææããŸãã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedNewThrownAndCatch(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
try {
nestedService.insertRequiredNewAndThrown(word + " " + word);
} catch (RuntimeException e) {
logger.error("insert failed", e);
}
return result;
}
}
å
åŽã®Serviceã¯ã©ã¹ã¯ãå
ã»ã©ãšåãã§ãã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
ãã¹ãã³ãŒããPROPAGATION_REQUIRED
ããã¹ãããŠããæãšã¯ç°ãªããUnexpectedRollbackException
ã¯ã¹ããŒããã
å€åŽã®ãã©ã³ã¶ã¯ã·ã§ã³ã¯ã³ããããããŸãã
@Test
public void transactionalNestedNewFailedAndCatch() {
assertThat(myService.insertRequiredAndNestedNewThrownAndCatch("Hello!!")).isEqualTo(1);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!"));
}
å¥ã
ã®ãã©ã³ã¶ã¯ã·ã§ã³ã«ãªã£ãŠããã®ã§ããããªããŸãããã
å
åŽã®Serviceã¯ã©ã¹ã§äŸå€ã¯çºçãããã®ã®ãã¡ãœãããäŸå€ã§æããªã
ãã¡ããè©ŠããŠã¿ãŸãã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional
public int insertRequiredAndNestedNewIgnoreFailed(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNewAndIgnoreFailed(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndIgnoreFailed(String word) {
try {
jdbcTemplate.update("insert into sample(word) v(?)", word);
} catch (RuntimeException e) {
logger.error("sql error", e);
}
return 0;
}
}
ãã¹ãã³ãŒãããã¡ãã«ã€ããŠã¯ãããããå¥ã®ãã©ã³ã¶ã¯ã·ã§ã³ãã³ããããããã ãããšãªããŸãã
@Test
public void transactionalNestedNewIgnoreFailed() {
assertThat(myService.insertRequiredAndNestedNewIgnoreFailed("Hello!!")).isEqualTo(1);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!"));
}
ããããããããªããŸãããããšã
äž¡æ¹ãšãPROPAGATION_REQUIRES_NEWã«ãã
ä»åã®ç¢ºèªæ¹æ³ã§ãããããæå³ã¯ãªãæ°ãããŸããã網çŸ
çãªæå³ã§ã¯äžå¿âŠãšããããšã§ã
ãœãŒã¹ã³ãŒããšçµæã ãèŒããŸãã
PROPAGATION_REQUIRES
ãPROPAGATION_REQUIRES_NEW
ã®çµã¿åãããšåãçµæã«ãªããŸããããããã
ç¬ç«ããããšãæ瀺ããŠããã ããªã®ã§ã
ã³ããããã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndNestedNew(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNew(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNew(String word) {
return jdbcTemplate.update("insert into sample(word) values(?)", word);
}
}
ãã¹ãã³ãŒãã
@Test
public void transactionalNormallyNewNestedNew() {
assertThat(myService.insertRequiredNewAndNestedNew("Hello!!")).isEqualTo(2);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!", "Hello!! Hello!!"));
}
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndNestedNewThrown(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNewAndThrown(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
ãã¹ãã³ãŒãã
@Test
public void transactionalNewNestedNewFailed() {
assertThatThrownBy(() -> myService.insertRequiredNewAndNestedNewThrown("Hello!!"))
.isInstanceOf(RuntimeException.class)
.hasMessage("Oops!!");
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEmpty();
}
å
åŽã®Serviceã¯ã©ã¹ãäŸå€ãã¹ããŒããå€åŽã®Serviceã¯ã©ã¹å
ã§ææãã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndNestedNewThrownAndCatch(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
try {
nestedService.insertRequiredNewAndThrown(word + " " + word);
} catch (RuntimeException e) {
logger.error("insert failed", e);
}
return result;
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndThrown(String word) {
jdbcTemplate.update("insert into sample(word) values(?)", word);
throw new RuntimeException("Oops!!");
}
}
ãã¹ãã³ãŒãã
@Test
public void transactionalNewNestedNewFailedAndCatch() {
assertThat(myService.insertRequiredNewAndNestedNewThrownAndCatch("Hello!!")).isEqualTo(1);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!"));
}
å
åŽã®Serviceã¯ã©ã¹ã§äŸå€ã¯çºçãããã®ã®ãã¡ãœãããäŸå€ã§æããªã
å€åŽã®Serviceã¯ã©ã¹ã
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndNestedNewIgnoreFailed(String word) {
int result = jdbcTemplate.update("insert into sample(word) values(?)", word);
return result + nestedService.insertRequiredNewAndIgnoreFailed(word + " " + word);
}
}
å
åŽã®Serviceã¯ã©ã¹ã
@Service
public class NestedService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertRequiredNewAndIgnoreFailed(String word) {
try {
jdbcTemplate.update("insert into sample(word) v(?)", word);
} catch (RuntimeException e) {
logger.error("sql error", e);
}
return 0;
}
}
ãã¹ãã³ãŒãã
@Test
public void transactionalNewNestedNewIgnoreFailed() {
assertThat(myService.insertRequiredNewAndNestedNewIgnoreFailed("Hello!!")).isEqualTo(1);
assertThat(jdbcTemplate.queryForList("select word from sample order by word", String.class))
.isEqualTo(List.of("Hello!!"));
}
ãŸãšã
Spring Frameworkã䜿ã£ãæã®ãREQUIREDãªäŒæã¬ãã«ã®ãã©ã³ã¶ã¯ã·ã§ã³ããã¹ãããæã«ãäŸå€ãã©ãæ±ããã§
ã©ã®ãããªæåããã®ãã確èªããŠã¿ãŸããã
ããã¥ã¡ã³ãã«çãã¯æžããŠããã®ã§ãããREQUIREDããã¹ãããŠããæã«äžéå端ãªå Žæã§äŸå€ãææããããããš
åä»ãªããšã«ãªãããã§ããã
äŒæã¬ãã«ããããããäŸå€ãæãŸãããããã©ã³ã¶ã¯ã·ã§ã³å¢çãæããŠããŸããããã®ããããã¡ãããšèããŠ
æžããªããšãããªãã§ããã