[JAVA] How to control transactions in Spring Boot without using @Transactional

Oh, today is Christmas Eve ~: santa :: snowflake: Don't worry about that ... This time, I will introduce how to control transactions on Spring Boot without using @Transactional (annotation-driven transaction management).

You might think that you should use @Transactional, but since you can specify @Transaction only for the components you create, the OSS library made by the 3rd party that does not depend on Spring. You can't make transactions with methods such as (obviously ...: sweat_smile :).

What should I do?

In addition to the method of specifying the transaction target method using annotations in Spring,

Methods etc. are supported.

If you have been using Spring for a long time, you may have seen the following transaction control declaration in the Bean definition using XML. In this example, all public methods of the TxDemoApplication class are subject to transaction control.

src/resources/transactionContext.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: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/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
">

	<tx:advice id="transactionAdvisor">
		<tx:attributes>
			<tx:method name="*" />
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<aop:pointcut id="txDemoApplicationPointcut" expression="execution(* com.example.TxDemoApplication.*(..))"/>
		<aop:advisor advice-ref="transactionAdvisor" pointcut-ref="txDemoApplicationPointcut"/>
	</aop:config>

</beans>

Even on Spring Boot, if you create an XML file like ↑ and load it using @ImportResource as shown below, it will work.

@SpringBootApplication
@ImportResource("classpath:/transactionContext.xml")
public class TxDemoApplication implements CommandLineRunner {
    // ...
}

But ... I don't want to go back to the XML file anymore ...: sweat_smile :, If you are using Spring from Spring Boot (those who only know Java Config), I wonder how to define a bean using XML ~ Therefore, there are some people: wink:

Do the same with Java Config! !!

If you want to express the same thing using Java Config ... Just create the following Java Config.

@Configuration
public class TransactionConfig {
	@Bean
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			PlatformTransactionManager transactionManager) {

		//Specify transaction management methods and attribute values required for transaction control
		MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
		source.addTransactionalMethod(TxDemoApplication.class, "*", new RuleBasedTransactionAttribute());

		//Generate an AOP Advisor that controls transactions
		//Advice for transaction control(TransactionInteceptor)The pointcut that specifies the application location of is linked with the method specified in ↑.
		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(source);
		advisor.setAdvice(new TransactionInterceptor(transactionManager, source));
		return advisor;
	}

}

Note:

If you want to use this mechanism, you need JAR of ʻorg.aspectj: aspectjweaver in the classpath, so add ʻorg.springframework.boot: spring-boot-starter-aop as a dependent library. ..

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Warning:

With Spring Boot, the automatic configuration mechanism also enables a mechanism to control transactions using @Transactional (annotation-driven transaction management). Therefore ... If @Transactional is added to the method specified in Java Config in ↑, Advice (TransactionInteceptor) for transaction control will be applied twice, so be careful.

TransactionAttributeSource type

In the example above, MethodMapTransactionAttributeSource is used, but Spring provides some implementation classes for TransactionAttributeSource.

name of the class Description
NameMatchTransactionAttributeSource Apply transaction control to methods that match the specified method name (pattern)
MethodMapTransactionAttributeSource Apply transaction control to the specified method name (pattern) of the specified class
MatchAlwaysTransactionAttributeSource Apply transaction control to all methods
AnnotationTransactionAttributeSource Apply transaction control to Spring, JTA, and EJB annotated class methods (this class is used with Spring Boot autoconfiguration)
CompositeTransactionAttributeSource pluralTransactionAttributeSourceAggregate and apply transaction control

Filters for applicable classes

BeanFactoryTransactionAttributeSourceAdvisor gives you the option to filter the classes to which AOP applies. For example ... If you want to apply only beans with @Service, you can define the following beans.

@Configuration
public class TransactionConfig {
	@Bean
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			PlatformTransactionManager transactionManager) {

		NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
		source.addTransactionalMethod("*", new RuleBasedTransactionAttribute());

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(source);
		advisor.setAdvice(new TransactionInterceptor(transactionManager, source));
		advisor.setClassFilter(clazz -> AnnotationUtils.findAnnotation(clazz, Service.class) != null); //Implement filter conditions and`setClassFilter`Call
		return advisor;
	}

}

Verification app

Below is a sample application created for operation verification.

Verification environment

Creating a project

Please download the project by selecting "JDBC", "H2", and "AOP" as the dependent libraries on "SPRING INITIALIZR".

Change log level

Set the Spring JDBC log output mode to debug to see if the transaction has been applied.

src/resources/application.properties


logging.level.org.springframework.jdbc=debug

App creation and Bnea definition

Next, implement CommandRunner in the Spring Boot application and define the bean for transaction control.

package com.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.MethodMapTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

@SpringBootApplication
public class TxDemoApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(TxDemoApplication.class, args);
	}

	private final JdbcOperations jdbcOperations;

	public TxDemoApplication(JdbcOperations jdbcOperations) {
		this.jdbcOperations = jdbcOperations;
	}

	@Override //This method is subject to transaction control, but ...@Transactional is not granted! !!
	public void run(String... args) throws Exception {
		Integer value = jdbcOperations.queryForObject("SELECT 1", Integer.class);
		System.out.println(value);
	}

	@Configuration
	static class TransactionConfig {
		@Bean
		public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
				PlatformTransactionManager transactionManager) {

			MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
			source.addTransactionalMethod(TxDemoApplication.class, "*", new RuleBasedTransactionAttribute());

			BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
			advisor.setTransactionAttributeSource(source);
			advisor.setAdvice(new TransactionInterceptor(transactionManager, source));
			return advisor;
		}

	}

}

Run the app

Now let's run the Spring Boot application.

$ ./mvnw spring-boot:run
...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2016-12-24 16:20:30.713  INFO 58327 --- [           main] com.example.TxDemoApplication            : Starting TxDemoApplication on Kazuki-no-MacBook-Pro.local with PID 58327 (/Users/shimizukazuki/Downloads/tx-demo/target/classes started by shimizukazuki in /Users/shimizukazuki/Downloads/tx-demo)
2016-12-24 16:20:30.715  INFO 58327 --- [           main] com.example.TxDemoApplication            : No active profile set, falling back to default profiles: default
2016-12-24 16:20:30.748  INFO 58327 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1e2e2b3: startup date [Sat Dec 24 16:20:30 JST 2016]; root of context hierarchy
2016-12-24 16:20:31.454  INFO 58327 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-12-24 16:20:31.469 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.example.TxDemoApplication$$EnhancerBySpringCGLIB$$9f83f17d.run]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2016-12-24 16:20:31.609 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]]] for JDBC transaction
2016-12-24 16:20:31.611 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]]] to manual commit
2016-12-24 16:20:31.618 DEBUG 58327 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL query [SELECT 1]
1
2016-12-24 16:20:31.638 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2016-12-24 16:20:31.638 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]]]
2016-12-24 16:20:31.639 DEBUG 58327 --- [           main] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]]] after transaction
2016-12-24 16:20:31.639 DEBUG 58327 --- [           main] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
2016-12-24 16:20:31.642  INFO 58327 --- [           main] com.example.TxDemoApplication            : Started TxDemoApplication in 1.106 seconds (JVM running for 3.466)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.612 s
[INFO] Finished at: 2016-12-24T16:20:31+09:00
[INFO] Final Memory: 24M/315M
[INFO] ------------------------------------------------------------------------
2016-12-24 16:20:31.742  INFO 58327 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1e2e2b3: startup date [Sat Dec 24 16:20:30 JST 2016]; root of context hierarchy
2016-12-24 16:20:31.744  INFO 58327 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

If you look at the console log, you can see that transactions are started, committed, and terminated before and after the SQL implementation:: clap:: clap:: clap:

Summary

It may not be used often, but ... You can control transactions for methods that do not have @Transactional. Basically, I think that it is better to add @Transactional to control transactions for components that you create yourself, but depending on the requirements of the application, the method introduced this time may be effective. Hmm.

at the end

Happy Christmas 2016 !! :wave:

Recommended Posts

How to control transactions in Spring Boot without using @Transactional
How to add a classpath in Spring Boot
How to bind to property file in Spring Boot
How to create a Spring Boot project in IntelliJ
How to use CommandLineRunner in Spring Batch of Spring Boot
Test field-injected class in Spring boot test without using Spring container
How to use Lombok in Spring
How to change application.properties settings at boot time in Spring boot
Deploy Spring Boot applications to Heroku without using the Heroku CLI
How to set Spring Boot + PostgreSQL
How to call and use API in Java (Spring Boot)
How to use ModelMapper (Spring boot)
How to make a hinadan for a Spring Boot project using SPRING INITIALIZR
Uploading and downloading files using Ajax in Spring Boot (without JQuery)
How to include Spring Tool in Eclipse 4.6.3?
How to not start Flyway when running unit tests in Spring Boot
[Swift] How to set an image in the background without using UIImageView.
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to use built-in h2db with spring boot
How to make Spring Boot Docker Image smaller
How to use Spring Boot session attributes (@SessionAttributes)
Apply Twitter Bootstrap 4 to Spring Boot 2 using Webjars
How to define multiple orm.xml in Spring4, JPA2.1
[Spring Boot] How to refer to the property file
Spring Boot --How to set session timeout time
When you want to notify an error somewhere when using graphql-spring-boot in Spring Boot
How to set environment variables in the properties file of Spring boot application
How to set Dependency Injection (DI) for Spring Boot
How to send value in HTML without screen transition
How to write a unit test for Spring Boot 2
[Spring Boot] How to create a project (for beginners)
[Introduction to Spring Boot] Submit a form using thymeleaf
How to convert A to a and a to A using AND and OR in Java
How to start tomcat local server without using eclipse
Introduce swagger-ui to REST API implemented in Spring Boot
How to boot by environment with Spring Boot of Maven
How to use In-Memory Job repository in Spring Batch
How to implement authentication process by specifying user name and password in Spring Boot
Set context-param in Spring Boot
Try Spring Boot from 0 to 100.
How to pass an object to Mapper in MyBatis without arguments
Spring Boot 2 multi-project in Gradle
How to use the same Mapper class in multiple data sources with Spring Boot + MyBatis
Prevent operations! How to securely update Rails manually using transactions
How to develop separately into Xib files without using Storyboard
Introduction to Spring Boot ① ~ DI ~
How to deploy jQuery in your Rails app using Webpacker
How to output array values without using a for statement
How to install JDK 8 on Windows without using the installer
Use thymeleaf3 with parent without specifying spring-boot-starter-parent in Spring Boot
Major changes in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
How to join a table without using DBFlute and sql
Introduction to Spring Boot Part 1
Try using Spring Boot Security
I tried to get started with Swagger using Spring Boot
8 things to insert into DB using Spring Boot and JPA
Let's find out how to receive in Request Body with REST API of Spring Boot
Use @ControllerAdvice, @ExceptionHandler, HandlerExceptionResolver in Spring Boot to catch exceptions
How to authorize using graphql-ruby
[Sprint Boot] How to use 3 types of SqlParameterSource defined in org.springframework.jdbc.core.namedparam