ããã¯ããªã«ãããããŠæžãããã®ïŒ
Spring Frameworkã§@Transactional
ã¢ãããŒã·ã§ã³ã䜿ã£ãæã®ãããŒã«ããã¯ã«é¢ããèšå®ã確èªããŠãããããªããš
æããŸããŠã
@Transactionalã¢ãããŒã·ã§ã³ã䜿ã£ããã©ã³ã¶ã¯ã·ã§ã³ç®¡ç
Spring Frameworkã®ããã¥ã¡ã³ããšããŠã¯ããã¡ãã«èšè¿°ããããŸãã
ã¡ãœããããŸãã¯ã¯ã©ã¹ã«@Transactional
ã¢ãããŒã·ã§ã³ãä»äžããããšã§ããã®ã¡ãœããããŸãã¯ã¯ã©ã¹ïŒããã³ãµãã¯ã©ã¹ïŒã®
ã¡ãœããããã©ã³ã¶ã¯ã·ã§ã³ã«åå ãããããšãã§ããŸãã
Used at the class level as above, the annotation indicates a default for all methods of the declaring class (as well as its subclasses).
@Transactional
ã¢ãããŒã·ã§ã³ã«èšå®å¯èœãªé
ç®ã¯ããã¡ãã
ãã©ã³ã¶ã¯ã·ã§ã³åé¢ã¬ãã«ãRead-OnlyãããŒã«ããã¯ã«é¢ããèšå®ãå¯èœã§ãã
JavadocãèŠãŠãããã§ãããã
Transactional (Spring Framework 5.3.6 API)
ã¡ãªã¿ã«ãåŸæ¥ã®XMLã§èšå®ããæ¹æ³ã«ã€ããŠã¯ãã¡ãã«èšè¿°ããããŸãã
Example of Declarative Transaction Implementation
ããŒã«ããã¯ã«ã€ããŠ
ããŠãã¡ãã£ãšããŒã«ããã¯ã«é¢ãã説æãèŠãŠã¿ãŸããããããã©ã«ãã§ã¯RuntimeException
ãã¹ããŒããããšããŒã«ããã¯ãã
ãã§ãã¯äŸå€ã§ã¯ãããªããªãããšèšè¿°ããããŸãã
Any RuntimeException triggers rollback, and any checked Exception does not.
ã¡ãªã¿ã«ã宣èšçãã©ã³ã¶ã¯ã·ã§ã³ã®èª¬æã®ãšããã«ã¯ãError
ã察象ã«ãªãããšãæžãããŠããŸãã
That is, when the thrown exception is an instance or subclass of RuntimeException. ( Error instances also, by default, result in a rollback).
Rolling Back a Declarative Transaction
@Transactional
ã®JavadocãèŠãŠã¿ãŸãããã
If no custom rollback rules apply, the transaction will roll back on RuntimeException and Error but not on checked exceptions.
Transactional (Spring Framework 5.3.6 API)
ããã©ã«ãã§ã¯RuntimeException
ãšError
ã察象ãšãªããã§è¯ãããã§ãã
ãã®ãããããœãŒã¹ã³ãŒããèŠã€ã€ãèšå®ãå€ãããããŠã¿ãããšããã®ãä»åã®ãé¡ã§ãã
ã¡ãªã¿ã«ãä»åã¯æ±ããŸããããããŒã«ããã¯ããªããäŸå€ãå®çŸ©ããããšãå¯èœã§ããã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã
$ 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-72-generic", arch: "amd64", family: "unix"
ããŒã¿ããŒã¹ã«ã¯MySQL 8.0.24ã䜿ãã172.17.0.2ã§åäœããŠãããã®ãšããŸãã
æºå
Spring Initializrã§ãããžã§ã¯ããäœæããŸããjdbc
ãšmysql
ãäŸåé¢ä¿ã«å«ããŸããã
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=2.4.5 \ -d javaVersion=11 \ -d name=transactional-rollback-rule \ -d groupId=org.littlewings \ -d artifactId=transactional-rollback-rule \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.spring.jdbc \ -d dependencies=jdbc,mysql \ -d baseDir=transactional-rollback-rule | tar zxvf - $ cd transactional-rollback-rule
Spring Bootã®ããŒãžã§ã³ã¯2.4.5ãSpring Frameworkã®ããŒãžã§ã³ã¯5.3.6ã§ãã
ã§ãããã£ããããžã§ã¯ãã®äŸåé¢ä¿çã¯ããã¡ãã
<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 { }
ãã¹ãã³ãŒãã®é圢ãäœè£œã
src/test/java/org/littlewings/spring/jdbc/TransactionalRollbackTest.java
package org.littlewings.spring.jdbc; 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 static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @SpringBootTest class TransactionalRollbackTest { @Autowired JdbcTemplate jdbcTemplate; @BeforeEach public void createTable() { jdbcTemplate.execute("drop table if exists sample"); jdbcTemplate.execute("create table sample(word varchar(10));"); } // ããã«ãBeanã®ã€ã³ãžã§ã¯ã·ã§ã³ãšãã¹ããæžãïŒ }
ãã¹ãã®å®è¡ããšã«ãããŒãã«ãdropãcreateããããã«ããŠããŸãã
@BeforeEach public void createTable() { jdbcTemplate.execute("drop table if exists sample"); jdbcTemplate.execute("create table sample(word varchar(10));"); }
ã¢ããªã±ãŒã·ã§ã³ã®èšå®ã¯ããã¡ãã ãã§ãã
src/test/resources/application.properties
spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice spring.datasource.username=kazuhira spring.datasource.password=password
ãŸãã¯åçŽã«äœ¿ã£ãŠã¿ã
æåã¯ã@Transactional
ãåçŽã«äœ¿ã£ãŠã¿ãŸãããã
@Transactinal
ãä»äžããã¡ãœãããæã€ããããªã¯ã©ã¹ãçšæã
src/main/java/org/littlewings/spring/jdbc/SimpleTransactionalService.java
package org.littlewings.spring.jdbc; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class SimpleTransactionalService { JdbcTemplate jdbcTemplate; public SimpleTransactionalService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Transactional(readOnly = true) public List<String> findAll() { return jdbcTemplate.queryForList("select word from sample order by word", String.class); } @Transactional public void insertSuccess(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); } @Transactional public void insertWithRuntimeException(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new RuntimeException("Oops!!"); } @Transactional public void insertWithException(String value) throws Exception { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new Exception("Commit?"); } }
@Transactional
ãã€ãããinsertãæåããã¡ãœãããRuntimeException
ãã¹ããŒããã¡ãœãããException
ãã¹ããŒãã
ã¡ãœããããããŠããŒã¿ãååŸããã¡ãœãããå®çŸ©ããŠããŸãã
ãã¹ãã³ãŒãã
@Autowired SimpleTransactionalService simpleTransactionalService; @Test public void simply() { simpleTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> simpleTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> simpleTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Commit?"); assertThat(simpleTransactionalService.findAll()).containsExactly("foo", "hoge"); }
çµæãèŠããšãRuntimeException
ãã¹ããŒããã¡ãœããã¯ããŒã«ããã¯ãããŠããŸãããException
ãã¹ããŒããã¡ãœããã¯
ããŒã«ããã¯ãããŠããªãïŒã³ããããããŠããïŒããšã確èªã§ããŸãã
å®è£ ã確èªãã
ããŠããã®ãããã¯ã©ãã§å®çŸ©ãããŠããã®ã§ãããïŒ
@Transactional
ã¢ãããŒã·ã§ã³ã®ããŒã«ããã¯å¯Ÿè±¡ã®äŸå€ã®ããã©ã«ãå€ã¯ãç¹ã«ãªã«ãæå®ããªãã®ã§ãã
Spring Frameworkã®ãœãŒã¹ã³ãŒããè¿œã£ãŠã¿ãŸãã
@Transactional
âŠã€ãŸããTransactionInterceptor
ãé©çšãããã¡ãœãããå®è¡ãããäŸå€ãã¹ããŒãããæã«ããŒã«ããã¯
ãããã©ããã確èªããŠããã®ã¯ãã¡ãã®ããã§ãã
ããã¡ãã£ãšè¿œã£ãŠãããšãRuleBasedTransactionAttribute
ã¯ã©ã¹ã«ãã©ãçããŸãã@Transactional
ã«ããŒã«ããã¯å¯Ÿè±¡ã®
äŸå€ãæ瀺çã«èšå®ããŠããå Žåã¯ãããã§ç¢ºèªããããã§ãã
ãªã«ãèšå®ããŠããªãå Žåã¯ã芪ã¯ã©ã¹ãåŒã³åºããŸãã
芪ã¯ã©ã¹ã§ããDefaultTransactionAttribute
ã¯ã©ã¹ã®rollbackOn
ã¡ãœãããèŠããšãRuntimeException
ãŸãã¯Error
ã§ããã°
true
ãè¿ãããã«ãªã£ãŠããŸãã
ãšããããã§ãããã©ã«ãã®æåã¯ãèšå®ã§ã¯ãªããŠããã©ã«ãå®è£ ã§å®çŸãããŠãããšããããšã«ãªããŸãã
ãŸããRuleBasedTransactionAttribute
ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãäœæããŠããã®ã¯ããã¡ãã§ããã
@Transactional
ã¢ãããŒã·ã§ã³ã®å
容ã䜿ã£ãŠãã€ã³ã¹ã¿ã³ã¹ãèšå®ããŠããããã§ãã
èšå®ããŠã¿ã
å®è£ ãèŠããšããã§ãã¡ãã£ãšèšå®ãå€ããŠã¿ãŸãããã
以äžã®ããã«ã@Transactional
ã®rollbackFor
ã«Exception
ãæå®ããŠã¿ãŸãã
src/main/java/org/littlewings/spring/jdbc/WithRollbackForTransactionalService.java
package org.littlewings.spring.jdbc; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class WithRollbackForTransactionalService { JdbcTemplate jdbcTemplate; public WithRollbackForTransactionalService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Transactional(readOnly = true) public List<String> findAll() { return jdbcTemplate.queryForList("select word from sample order by word", String.class); } @Transactional(rollbackFor = Exception.class) public void insertSuccess(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); } @Transactional(rollbackFor = Exception.class) public void insertWithRuntimeException(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new RuntimeException("Oops!!"); } @Transactional(rollbackFor = Exception.class) public void insertWithException(String value) throws Exception { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new Exception("Rollback!"); } }
ãã¹ãã³ãŒãã§ç¢ºèªã
@Autowired WithRollbackForTransactionalService withRollbackForTransactionalService; @Test public void withRollbackFor() { withRollbackForTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> withRollbackForTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> withRollbackForTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Rollback!"); assertThat(withRollbackForTransactionalService.findAll()).containsExactly("foo"); }
ä»åºŠã¯ãã¹ããŒããã®ãException
ã§ãããŒã«ããã¯ãããããã«ãªããŸããã
ã¢ãããŒã·ã§ã³ãåæãã
ãšããã§ãå
ã»ã©ã®ãããªèšå®ã«ãããšãåã
ã®ã¡ãœãããã¯ã©ã¹ã«@Transactional
ã®èšå®ãããããæžããŠãããã
èšå®ãã¹ãªã©ãæ°ã«ãªããšããã§ãã
XMLã§èšå®ããŠããæã¯ãããæå³äžæ¬ã§èšå®ã§ããŠããæããããŸãã
Rolling Back a Declarative Transaction
@Transactional
ã§ãã©ã³ã¶ã¯ã·ã§ã³ç®¡çããå Žåã¯ãã©ãããã°ããã®ã§ãããïŒ
ã¢ãããŒã·ã§ã³ãåæããã°è¯ãããã§ãã
ãã¡ãã®ããã¥ã¡ã³ãã«åŸãã@Transactional
ãã«ã¹ã¿ãã€ãºããæ°ããã¢ãããŒã·ã§ã³ãå®çŸ©ããŸãã
Using Meta-annotations and Composed Annotations
æ°ããã¢ãããŒã·ã§ã³ã§ã¯ã@AliasFor
ã¢ãããŒã·ã§ã³ã䜿ã£ãŠ@Transactional
ã®èšå®ãžåæ ãããŸãã
AliasFor (Spring Framework 5.3.6 API)
ãããªæãã®ãã®ãäœæã
src/main/java/org/littlewings/spring/jdbc/MyTransactional.java
package org.littlewings.spring.jdbc; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; import org.springframework.transaction.annotation.Transactional; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Transactional public @interface MyTransactional { @AliasFor(annotation = Transactional.class, attribute = "readOnly") boolean readOnly() default false; @AliasFor(annotation = Transactional.class, attribute = "rollbackFor") Class<? extends Throwable>[] rollbackFor() default Exception.class; }
@Transactional
ãã¡ã¿ã¢ãããŒã·ã§ã³ãšããŠäœ¿ããæ°ããã¢ãããŒã·ã§ã³ãå®çŸ©ããŸãã
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Transactional public @interface MyTransactional {
rollbackFor
ã®èšå®ã¯ãããã©ã«ãã§Exception
ãããŒã«ããã¯å¯Ÿè±¡ã«ããŠã¿ãŸããã
@AliasFor(annotation = Transactional.class, attribute = "rollbackFor") Class<? extends Throwable>[] rollbackFor() default Exception.class;
ããšã¯ãreadOnly
ãèšå®ã§ããããã«ããŠããŸãã
@AliasFor(annotation = Transactional.class, attribute = "readOnly") boolean readOnly() default false;
äœæããã¢ãããŒã·ã§ã³ã䜿ã£ãã³ãŒãã
src/main/java/org/littlewings/spring/jdbc/WithComposedAnnotationTransactionalService.java
package org.littlewings.spring.jdbc; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class WithComposedAnnotationTransactionalService { JdbcTemplate jdbcTemplate; public WithComposedAnnotationTransactionalService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @MyTransactional(readOnly = true) public List<String> findAll() { return jdbcTemplate.queryForList("select word from sample order by word", String.class); } @MyTransactional public void insertSuccess(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); } @MyTransactional public void insertWithRuntimeException(String value) { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new RuntimeException("Oops!!"); } @MyTransactional public void insertWithException(String value) throws Exception { jdbcTemplate.update("insert into sample(word) values(?)", value); throw new Exception("Rollback!"); } }
ãã¡ãã«ã¯ãrollbackFor
ã®èšå®ã¯ãããŸããã
確èªçšã®ãã¹ãã³ãŒãã
@Autowired WithComposedAnnotationTransactionalService withComposedAnnotationTransactionalService; @Test public void withComposedAnnotation() { withComposedAnnotationTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> withComposedAnnotationTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> withComposedAnnotationTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Rollback!"); assertThat(withComposedAnnotationTransactionalService.findAll()).containsExactly("foo"); }
å
ã»ã©ã®ã@Transactional
ïŒrollbackFor
ã§Exception
ãæå®ããæãšåãçµæãåŸãããŸããã
ãšããããã§ãèšå®ãå
±éåãããã£ããããããæãã«ãããããããâŠãšãããã@Transactional
ã¢ãããŒã·ã§ã³ãã
ãã©ã³ã¶ã¯ã·ã§ã³ç®¡çã®èšå®ãèªã¿åããã®ãããã®ãœãŒã¹ã³ãŒããèŠãŠãããšãããããæ¹æ³ãªããããªæããïŒ
ãŸãšã
ä»åã¯ãSpring Frameworkã®@Transactional
ã®ããŒã«ããã¯ã«é¢ããèšå®ãèŠããããã®å®è£
éšåãèŠãããèšå®ãå€ããŠ
確èªãããããŠã¿ãŸããã
ã©ããªãã§ãããïŒ@Transactional
ããã®ãŸãŸäœ¿ãã®ãªãããã®ã§ãããrollbackFor
ããããäžåŸã«ã¹ã¿ãã€ãºãããå Žåã¯
åæã¢ãããŒã·ã§ã³ã䜿ã£ãæ¹ãè¯ããããªãããªãããã§ããªããããªã
Spring Frameworkã䜿ããªãããã©ã³ã¶ã¯ã·ã§ã³ç®¡çã¯@Transactional
ã§ããããã¿ãããªãšããããããã§ããããã
å€ã«èªåã®ãã®ãæã¡èŸŒããšæ··ä¹±ããããªæããã
ããŒã«ããã¹ãã§ç¢ºèªã§ãããããã®ã§ããããã
XMLã§åã蟌ãã§ããæã¯ãã¡ãœããåã®ã«ãŒã«ãªã©ã§äžåŸé©çšãšãã£ãæãã§ãããããã¡ãã£ãšéã£ãæ©ã¿ïŒ
ã¡ãªã¿ã«ãXMLã§åã蟌ãã§ããå 容ããJavaConfigã§è¡ãå Žåã¯ãã®ãããã䜿ãããšã«ãªãããã§ãã
BeanFactoryTransactionAttributeSourceAdvisor (Spring Framework 5.3.6 API)
MethodMapTransactionAttributeSource (Spring Framework 5.3.6 API)
RuleBasedTransactionAttribute (Spring Framework 5.3.6 API)
æåŸã«ãäœæãããã¹ãã³ãŒãå šäœãèŒããŠãããŸãããã
src/test/java/org/littlewings/spring/jdbc/TransactionalRollbackTest.java
package org.littlewings.spring.jdbc; 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 static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @SpringBootTest class TransactionalRollbackTest { @Autowired JdbcTemplate jdbcTemplate; @BeforeEach public void createTable() { jdbcTemplate.execute("drop table if exists sample"); jdbcTemplate.execute("create table sample(word varchar(10));"); } @Autowired SimpleTransactionalService simpleTransactionalService; @Test public void simply() { simpleTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> simpleTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> simpleTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Commit?"); assertThat(simpleTransactionalService.findAll()).containsExactly("foo", "hoge"); } @Autowired WithRollbackForTransactionalService withRollbackForTransactionalService; @Test public void withRollbackFor() { withRollbackForTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> withRollbackForTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> withRollbackForTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Rollback!"); assertThat(withRollbackForTransactionalService.findAll()).containsExactly("foo"); } @Autowired WithComposedAnnotationTransactionalService withComposedAnnotationTransactionalService; @Test public void withComposedAnnotation() { withComposedAnnotationTransactionalService.insertSuccess("foo"); assertThatThrownBy(() -> withComposedAnnotationTransactionalService.insertWithRuntimeException("bar")).isInstanceOf(RuntimeException.class).hasMessage("Oops!!"); assertThatThrownBy(() -> withComposedAnnotationTransactionalService.insertWithException("hoge")).isInstanceOf(Exception.class).hasMessage("Rollback!"); assertThat(withComposedAnnotationTransactionalService.findAll()).containsExactly("foo"); } }