ããã¯ããªã«ãããããŠæžãããã®ïŒ
Spring Frameworkã䜿ã£ãŠãããšããã©ã³ã¶ã¯ã·ã§ã³ç®¡çã@Transactional
ã¢ãããŒã·ã§ã³ã䜿ã£ãŠå®£èšçã«æžããŠããããšã
å€ããšæããŸãã
@Transactional
ã䜿ã£ãå ŽåãäŸå€ïŒããã©ã«ãã§ã¯RuntimeException
ã®ãµãã¯ã©ã¹ïŒãã¹ããŒãããæã«ããŒã«ããã¯ããã
ããšã«ãªã£ãŠããŸããããã§ã@Transactional
ã¢ãããŒã·ã§ã³ãä»äžãããã¡ãœããããã¹ããããã€éäžã§äŸå€ã
ææããå Žåã«ã©ãããæåã«ãªãã®ãã確èªããŠã¿ãããªãšæããŸããŠã
ãã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã«ã€ããŠã¯ãPROPAGATION_REQUIRED
ãäž»ãªå¯Ÿè±¡ã«ããŠããŸãã
Springã®å®£èšçãã©ã³ã¶ã¯ã·ã§ã³ãšãã©ã³ã¶ã¯ã·ã§ã³ã®äŒæ
Springã®å®£èšçãã©ã³ã¶ã¯ã·ã§ã³ã«é¢ããããã¥ã¡ã³ããšããŠã¯ããã¡ãã§ããã
Declarative Transaction Management
ãŸãã@Transactional
ã¢ãããŒã·ã§ã³ã«ã€ããŠã¯ããã¡ãã«èšèŒãããŠããŸãã
ããã§ããã©ã³ã¶ã¯ã·ã§ã³ã®äŒæã«é¢ããããã¥ã¡ã³ããèªãã§ã¿ãŸãã
æåã«çåã«æžããããšã®çãããå®ã¯ããã«æžããŠããŸãã
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> <java.version>11</java.version> </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 { // æ§æ誀ãã§å®è¡ã«å€±æããSQL 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
ã®ç©ã¿éããšãªãããå Žåã¯ãäžéå端ãªãšããã§
äŸå€ãæãŸããã«ãã©ã³ã¶ã¯ã·ã§ã³ã®å¢çå€ã§ææãããããã©ã³ã¶ã¯ã·ã§ã³ã®å¢çãè·šããªããã¡ã«ææããããšããšãã
æãã«ããæ¹ãè¯ãããã§ããã
ãœãŒã¹ã³ãŒããã確èªãã
ãã®ãããã®åäœãããœãŒã¹ã³ãŒãäžã§ã確èªããŠã¿ãŸãããã
ãã©ã³ã¶ã¯ã·ã§ã³å¢çãšãªãã¡ãœãããäŸå€ã§æããæç¹ã§ãããŒã«ããã¯ã®ããŒã¯ãè¡ãããŸãã
ResourceHolderSupport
ã§ã®rollbackOnly
ãtrue
ã«èšå®ãããã®ãããã®ããŒã¯ã§ããã
ãã®ç¶æ
ã«ãªããšãå€åŽã®ãã©ã³ã¶ã¯ã·ã§ã³å¢çãã³ãããããããšããã¿ã€ãã³ã°ã§ãããŒã«ããã¯ãè¡ãããã«ããŒã¯ãããŠ
ããããšãæ€åºãããŸãã
ãããŠãUnexpectedRollbackException
ãã¹ããŒãããŸãããšã
ãã¡ããããœãŒã¹ã³ãŒãäžã§ã®ç¢ºèªã§ããã
ããšã¯ãããå°ãããªãšãŒã·ã§ã³ã確èªããŠã¿ãŸãããã
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 { // æ§æ誀ãã§å®è¡ã«å€±æããSQL 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 { // æ§æ誀ãã§å®è¡ã«å€±æããSQL 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ããã¹ãããŠããæã«äžéå端ãªå Žæã§äŸå€ãææããããããš
åä»ãªããšã«ãªãããã§ããã
äŒæã¬ãã«ããããããäŸå€ãæãŸãããããã©ã³ã¶ã¯ã·ã§ã³å¢çãæããŠããŸããããã®ããããã¡ãããšèããŠ
æžããªããšãããªãã§ããã