不使用Spring Boot的@Transactional,进行事务控制的方法
啊,今天是平安夜呢~
不管那种事,这次我会介绍在Spring Boot上如何不使用@Transactional(注释驱动的事务管理)来进行事务控制的方法。
有些人可能会想到用@Transactional,但实际上只能在我们自己创建的组件上指定@Transaction,所以无法对依赖于Spring的第三方开源库等的方法应用事务(这是很自然的)。
我该怎么办?
除了使用Spring注解指定事务方法的方式外,还有其他方法。
-
- 指定したメソッド名(パターン)に一致するメソッドを対象にする
-
- 指定したクラスの指定したメソッド名(パターン)に一致するメソッドを対象にする
- 全てのメソッドを対象にする
提供了各种方法和支持。
过去一直使用Spring的人可能会在使用XML进行Bean定义时看到过以下类似的事务控制声明吧?在这个例子中,我们将TxDemoApplication类的所有公共方法都设定为事务控制的对象。
<?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>
如果在Spring Boot上创建像上面的XML文件,并使用@ImportResource加载它,它将能够工作。
@SpringBootApplication
@ImportResource("classpath:/transactionContext.xml")
public class TxDemoApplication implements CommandLineRunner {
// ...
}
但是,我不想再回到XML文件了,使用Spring Boot中的Spring的人(只知道Java Config的人)也有些人不知道如何使用XML定义Bean的方法。
让我们使用Java Config来实现相同的功能吧!
如果使用Java Config来表示相同的内容,只需要创建以下的Java Config。
@Configuration
public class TransactionConfig {
@Bean
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
PlatformTransactionManager transactionManager) {
// トランザクション管理するメソッドとトランザクション制御に必要な属性値を指定する
MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
source.addTransactionalMethod(TxDemoApplication.class, "*", new RuleBasedTransactionAttribute());
// トランザクション制御を行うAOPのAdvisorを生成する
// トランザクション制御用のAdvice(TransactionInteceptor)の適用箇所を指定するポイントカットは、↑で指定したメソッドと連動する仕組みになっている
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(source);
advisor.setAdvice(new TransactionInterceptor(transactionManager, source));
return advisor;
}
}
注意事项:
如果要使用此机制,请确保在类路径中包含org.aspectj:aspectjweaver的JAR文件,因此请将org.springframework.boot:spring-boot-starter-aop添加为依赖库。
pom.xml
org.springframework.boot
spring-boot-starter-aop警告:
使用Spring Boot时,由于自动配置机制,通过@Transactional(基于注解的事务管理)进行事务控制的机制也会被启用。因此…如果被上述Java Config指定的方法上有@Transactional注解,事务控制的Advice(TransactionInteceptor)会被重复应用,因此要注意。
TransactionAttributeSource的类型
在上述例子中,使用了MethodMapTransactionAttributeSource,但是Spring为TransactionAttributeSource的实现类提供了几种选项。
NameMatchTransactionAttributeSource
指定したメソッド名(パターン)に一致するメソッドに対してトランザクション制御を適用するMethodMapTransactionAttributeSource
指定したクラスの指定したメソッド名(パターン)に対してトランザクション制御を適用するMatchAlwaysTransactionAttributeSource
全てのメソッドに対してトランザクション制御を適用するAnnotationTransactionAttributeSource
Spring、JTA、EJBのアノテーションが付与されたクラス・メソッドに対してトランザクション制御を適用する (Spring Bootの自動コンフィギュレーションを利用するとこのクラスが使われます)CompositeTransactionAttributeSource
複数のTransactionAttributeSource
を集約してトランザクション制御を適用する适用于目标类的过滤器
BeanFactoryTransactionAttributeSourceAdvisor提供了一个过滤AOP适用目标类的选项。例如,如果只想对被标记为@Service的Bean应用AOP,则可以定义以下的Bean。
@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); // フィルタ条件を実装し、`setClassFilter`を呼び出す
return advisor;
}
}
验证应用程式
我在下面附上了一个为了验证功能而创建的示例应用程序。
验证环境
-
- Spring Boot 1.4.3.RELEASE
-
- Spring Framework 4.3.5.RELEASE
- AspectJ 1.8.9
创建项目
请在“SPRING INITIALIZR”上选择“JDBC”,“H2”,“AOP”作为项目的依赖库进行下载。
更改日志级别
为了确认交易是否被应用,将Spring JDBC日志输出模式设置为调试模式。
logging.level.org.springframework.jdbc=debug
创建应用程序和定义Bnea
接下来,在Spring Boot应用程序中实现CommandRunner,并进行事务控制的Bean定义。
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 // このメソッドをトランザクション制御対象にするけど・・・@Transactionalは付与しない!!
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;
}
}
}
应用程序的执行
让我们来运行Spring Boot应用程序吧。
$ ./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
通过查看控制台日志,我们可以看到在SQL实现之前和之后进行了事务的开始、提交和结束。
总结
虽然使用频率可能不高,但是… 这是关于可以在未标注 @Transactional 的方法上进行事务控制的讨论。
基本上,我认为对于我们自己创建的组件,最好使用 @Transactional 进行事务控制,但根据应用程序要求,介绍的方法也可能有效的情况。
最后
2016年圣诞节快乐!