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 :).
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:
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.
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 |
pluralTransactionAttributeSource Aggregate and apply transaction control |
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;
}
}
Below is a sample application created for operation verification.
Please download the project by selecting "JDBC", "H2", and "AOP" as the dependent libraries on "SPRING INITIALIZR".
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
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;
}
}
}
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:
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.
Happy Christmas 2016 !! :wave:
Recommended Posts