Spring声明式事务的使用:使用AOP的思维,执行SQL的代码织入Spring约定的数据库事务的流程中。
一、Spring声明式数据库事务约定
在讲解Spring AOP时,只要我们遵循约定,就可以把自己开发的代码织入约定的流程中,减少大量的冗余代码,更加集中于业务的开发。
@Transactional:通过此注解标注告诉Spring在什么地方启用数据库事务功能。可以标注在类或者方法上,当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。
此注解还允许配置许多的属性,如事务的隔离级别,传播行为,异常类型(从而确定方法发生什么异常下回滚事务或者发生什么异常下不回滚事务)。
Spring IoC容器在加载时就会将@Transactional
中的配置信息解析出来,然后存到事务定义器(TransactionDefinition接口的实现类)里,并且记录哪些类或者方法需要启动事务功能,采取什么策略去执行事务。
这个过程中,我们所需要做的只是给需要事务的类或者方法标注@Transactional和配置其属性而已。
Spring数据库事务约定:事务的底层需要启用AOP功能,这是Spring事务的底层实现。
当它启动事务时,就会根据事务定义器内的配置去设置事务,首先根据传播行为去确定事务的策略。然后是隔离级别、超时时间、只读等内容的设置,Spring事务拦截器根据@Transactional配置的内容来完成的。
Spring通过对注解@Transactional属性配置去设置数据库事务,接着Spring就会开始调用开发者编写的业务代码,执行开发者的业务代码,可能发生异常,也可能不发生异常。在Spring数据库事务的流程中,它会根据是否发生异常采取不同的策略。
如果都没有发生异常,Spring数据库拦截器就会帮助我们提交事务。
如果发生异常,要判断事务定义器内的配置,如果事务定义器已经约定了该类型的异常不回滚事务就提交事务,如果没有任何配置或者不是配置不回滚事务的异常,则会回滚事务,并且将异常抛出。
无论发生异常与否,Spring都会释放事务资源,这样就可以保证数据库连接池正常可用了。
二、@Transactional的配置项
数据库事务属性都可以由@Transactional来配置
@Transactional源码分析:
package org.springframework.transaction.annotation;
/**** imports ****/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 通过bean name 指定事务管理器
@AliasFor("transactionManager")
String value() default "";
// 同value属性
@AliasFor("value")
String transactionManager() default "";
// 指定传播行为
Propagation propagation() default Propagation.REQUIRED;
// 指定隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 指定超时时间(单位秒)
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只读事务
boolean readOnly() default false;
// 方法在发生指定异常时回滚,默认是所有异常都回滚
Class<? extends Throwable>[] rollbackFor() default {};
// 方法在发生指定异常名称时回滚,默认是所有异常都回滚
String[] rollbackForClassName() default {};
// 方法在发生指定异常时不回滚,默认是所有异常都回滚
Class<? extends Throwable>[] noRollbackFor() default {};
// 方法在发生指定异常名称时不回滚,默认是所有异常都回滚
String[] noRollbackForClassName() default {};
}
value和transactionManager:配置一个Spring的事务管理器;
timeout:事务可以允许存在的时间戳,单位为秒;
readOnly:定义的是事务是否是只读事务;
rollbackFor、rollbackForClassName、noRollbackFor和noRollbackForClassName:指定异常,我们从流程中可以看到在带有事务的方法时,可能发生异常,通过这些属性的设置可以指定在什么异常的情况下依旧提交事务,在什么异常的情况下回滚事务,这些可以根据自己的需要进行指定。以上这些都比较好理解,
propagation:传播行为
isolation:隔离级别
关于注解@Transactional值得注意的是它可以放在接口上,也可以放在实现类上。推荐放在实现类上,因为放在接口上将使得你的类基于接口的代理时它才生效。
Spring可以使用JDK动态代理,也可以使用CGLIG动态代理。如果使用接口,那么你将不能切换为CGLIB动态代理,而只能允许你使用JDK动态代理,并且使用对应的接口去代理你的类,这样才能驱动这个注解,这将大大地限制你的使用,因此在实现类上使用@Transactional注解才是最佳的方式。
三、Spring事务管理器
在Spring中,事务管理器的顶层接口为PlatormTransactionManager,Spring还为此定义了一些列的接口和类:
Spring事务管理器
当我们引入其他框架时,还会有其他的事务管理器的类,比方说我们引入Hibernate,那么Spring orm包还会提供HibernateTransactionManager与之对应并给我们使用。
MyBatis框架提供了DataSourceTransactionManager。
PlatformTransactionManager源码分析:
package org.springframework.transaction;
public interface PlatformTransactionManager {
// 获取事务,它还会设置数据属性
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
Spring在事务管理时,就是将这些方法按照约定织入对应的流程中的,其中getTransaction方法的参数是一个事务定义(TransactionDefinition),它是依赖于我们配置的@Transactional的配置项生成的,于是通过它就能够设置事务的属性了,而提交和回滚事务也就可以通过commit和rollback方法来执行。
在Spring Boot中,当你依赖于mybatis-spring-boot-starter之后,它会自动创建一个DataSource-TransactionManager对象,作为事务管理器,如果依赖于spring-boot-starter-data-jpa,则它会自动创建JpaTransactionManager对象作为事务管理器,所以我们一般不需要自己创建事务管理器而直接使用它们即可。
四、隔离级别
其实就是数据库的隔离级别,此处不详细讲。
五、事务的传播行为
传播行为:方法之间调用事务采取的策略问题,即当一个方法调用另外一个方法时,可以让事务采取不同的策略工作。
在绝大部分的情况下,我们会认为数据库事务要么全部成功,要么全部失败。但现实中也许会有特殊的情况。
例如,执行一个批量程序,它会处理很多的交易,绝大部分交易是可以顺利完成的,但是也有极少数的交易因为特殊原因不能完成而发生异常,这时我们不应该因为极少数的交易不能完成而回滚批量任务调用的其他交易,使得那些本能完成的交易也变为不能完成了。
此时,我们真实的需求是:在一个批量任务执行的过程中,调用多个交易时,如果有一些交易发生异常,只是回滚那些出现异常的交易,而不是整个批量任务,这样就能够使得那些没有问题的交易可以顺利完成,而有问题的交易则不做任何事情。
传播行为:在Spring事务机制中对数据库存在7种传播行为,是通过枚举类Propagation定义的。
传播行为枚举:
package org.springframework.transaction.annotation;
/**** imports ****/
public enum Propagation {
/**
* 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,
* 否则新建一个事务运行子方法
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 支持事务,如果当前存在事务,就沿用当前事务,
* 如果不存在,则继续采用无事务的方式运行子方法
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 必须使用事务,如果当前没有事务,则会抛出异常,
* 如果存在当前事务,就沿用当前事务
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 无论当前事务是否存在,都会创建新事务运行方法,
* 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 不支持事务,当前存在事务时,将挂起事务,运行方法
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 在当前方法调用子方法时,如果子方法发生异常,
* 只回滚子方法执行过的SQL,而不回滚当前方法的事务
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
传播行为一共分为7种,但是常用的只有3种,其他的使用率比较低。
六、测试REQUIRED、REQUIRES_NEW和NESTED这3种最常用的传播行为
1.创建用户表
CREATE TABLE `tms_app_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '手机',
...
) ENGINE=InnoDB COMMENT='物流APP账号表';
2.用户POJO:参考之前的
3.Mapper
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface TransactionUserDao {
@Insert({"insert into tms_app_user(id,name,phone)values(",
"#{user.id},#{user.name},#{user.phone})"})
int insertUser(@Param("user") AppUserEntity appUserEntity);
}
4.用户服务接口
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
public interface TransactionUserService {
// 新增用户
public int insertUser(AppUserEntity user) ;
}
5.用户服务接口的实现类
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransactionUserServiceImpl implements TransactionUserService {
@Autowired
private TransactionUserDao transactionUserDao;
@Override
//propagation= Propagation.REQUIRES_NEW
@Transactional(propagation= Propagation.NESTED)
public int insertUser(AppUserEntity user) {
return transactionUserDao.insertUser(user);
}
}
6.业务接囗
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
import java.util.List;
public interface UserBatchService {
public int insertUsers(List<AppUserEntity> userList);
}
7.业务接囗实现类
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserBatchServiceImpl implements UserBatchService {
@Autowired
private TransactionUserService transactionUserService = null;
@Override
@Transactional( propagation= Propagation.REQUIRED)
public int insertUsers(List<AppUserEntity> userList) {
int count = 0;
for (AppUserEntity user : userList) {
// 调用子方法,将使用@Transactional定义的传播行为
count += transactionUserService.insertUser(user);
}
return count;
}
}
代码中的方法上标注了注解@Transactional,意味着方法将启用Spring数据库事务机制。
8.控制器
package com.springboot.chapter2.transaction;
import com.springboot.chapter2.common.AppUserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/transction")
public class UserController {
@Autowired
private UserBatchService userBatchService;
@RequestMapping("/insertUsers")
@ResponseBody
public Map<String, Object> insertUsers(Integer id1, String name1, Integer id2,
String name2) {
AppUserEntity user1 = new AppUserEntity();
user1.setId(id1);
user1.setName(name1);
AppUserEntity user2 = new AppUserEntity();
user1.setId(id2);
user1.setName(name2);
List<AppUserEntity> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
// 结果会回填主键,返回插入条数
int inserts = userBatchService.insertUsers(userList);
Map<String, Object> result = new HashMap<>();
result.put("success", inserts>0);
result.put("user", userList);
return result;
}
}
10.启动服务
package com.springboot.chapter2.transaction;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import javax.annotation.PostConstruct;
@MapperScan(
basePackages = "com.springboot.chapter2.transaction",
annotationClass = Repository.class)
@SpringBootApplication(scanBasePackages = "com.springboot.chapter2.transaction")
public class Transaction5Application {
// 注入事务管理器,它由Spring Boot自动生成
@Autowired
PlatformTransactionManager transactionManager = null;
public static void main(String[] args) throws Exception {
SpringApplication.run(Transaction5Application.class, args);
}
// 使用后初始化方法,观察自动生成的事务管理器
@PostConstruct
public void viewTransactionManager() {
// 启动前加入断点观测
System.out.println(transactionManager.getClass().getName());
}
}
依赖于mybatis-spring-boot-starter之后,Spring Boot会自动创建事务管理器、MyBatis的SqlSessionFactory和SqlSessionTemplate等内容。
首先这里使用了@MapperScan扫描对应的包,并限定了只有被注解@Repository标注的接口,这样就可以把MyBatis对应的接口文件扫描到Spring IoC容器中了。
这里通过注解@Autowired直接注入了事务管理器,它是通过Spring Boot的机制自动生成的;而在viewMyBatis方法中,加入了注解@PostConstruct,所以在这个类对象被初始化后,会调用这个方法,在这个方法中,因为先前已经将IoC容器注入进来,所以可以通过IoC容器获取对应的Bean以监控它们。
Spring Boot已经生成了事务管理器,这便是Spring Boot的魅力,允许我们以最小的配置代价运行Spring的项目。
那么按照之前的约定使用注解@Transactional标注类和方法后,
Spring的事务拦截器就会同时使用事务管理器的方法开启事务,然后将代码织入Spring数据库事务的流程中,如果发生异常,就会回滚事务,如果不发生异常,那么就会提交事务,
11.测试:在浏览器地址栏中输入请求
http://localhost:8080/transction/insertUsers?id1=200012&name1=aa&id2=200013&name2=bb
日志如下:
o.s.web.servlet.DispatcherServlet : GET "/transction/insertUsers?id1=200012&name1=aa&id2=200013&name2=bb", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.springboot.chapter2.transaction.UserController#insertUsers(Integer, String, Integer, String)
o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
o.h.stat.internal.StatisticsInitiator : Statistics initialized [enabled=false]
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(27675975<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.springboot.chapter2.transaction.UserBatchServiceImpl.insertUsers]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
o.h.e.t.internal.TransactionImpl : begin
o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@35b77b1e]
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(27675975<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@190754475 wrapping com.mysql.cj.jdbc.ConnectionImpl@3701e6e4] will be managed by Spring
c.s.c.t.TransactionUserDao.insertUser : ==> Preparing: insert into tms_app_user(id,name,phone)values( ?,?,?)
c.s.c.t.TransactionUserDao.insertUser : ==> Parameters: 200013(Integer), bb(String), null
c.s.c.t.TransactionUserDao.insertUser : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(27675975<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47] from current transaction
c.s.c.t.TransactionUserDao.insertUser : ==> Preparing: insert into tms_app_user(id,name,phone)values( ?,?,?)
c.s.c.t.TransactionUserDao.insertUser : ==> Parameters: null, null, null
c.s.c.t.TransactionUserDao.insertUser : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58430c47]
o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(27675975<open>)]
o.h.e.t.internal.TransactionImpl : committing
o.s.orm.jpa.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction
m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]
m.m.a.RequestResponseBodyMethodProcessor : Writing [{success=true, user=[AppUserEntity(id=200013, name=bb, phone=null, type=null, loginName=null, passwo (truncated)...]
o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
o.s.web.servlet.DispatcherServlet : Completed 200 OK
从日志中,我们可以看到Spring获取了数据库连接,并且修改了隔离级别,然后执行SQL,在最后会自动地关闭和提交数据库事务,因为我们对方法标注了@Transactional,所以Spring 会把对应方法的代码织入约定的事务流程中。
使用REQUIRES_NEW传播行为进行测试:
http://localhost:8080/transction/insertUsers?id1=200015&name1=aa&id2=200016&name2=bb
可以得到如下日志:
org.apache.tomcat.util.http.Parameters : Start processing with input [id1=200015&name1=aa&id2=200016&name2=bb]
o.s.web.servlet.DispatcherServlet : GET "/transction/insertUsers?id1=200015&name1=aa&id2=200016&name2=bb", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.springboot.chapter2.transaction.UserController#insertUsers(Integer, String, Integer, String)
o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
o.h.stat.internal.StatisticsInitiator : Statistics initialized [enabled=false]
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1872599585<open>)] for JPA transaction
# 创建当前方法事务(UserBatchServiceImpl的insertUsers方法),
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.springboot.chapter2.transaction.UserBatchServiceImpl.insertUsers]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
o.h.e.t.internal.TransactionImpl : begin
o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@53b659fe]
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1872599585<open>)] for JPA transaction
# 创建子方法事务(UserServiceImpl的insertUser方法)
o.s.orm.jpa.JpaTransactionManager : Suspending current transaction, creating new transaction with name [com.springboot.chapter2.transaction.TransactionUserServiceImpl.insertUser]
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(347049211<open>)] for JPA transaction
o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
o.h.e.t.internal.TransactionImpl : begin
o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@aef768a]
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@461b460f]
o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1075924701 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c4b1509] will be managed by Spring
c.s.c.t.TransactionUserDao.insertUser : ==> Preparing: insert into tms_app_user(id,name,phone)values( ?,?,?)
c.s.c.t.TransactionUserDao.insertUser : ==> Parameters: 200016(Integer), bb(String), null
c.s.c.t.TransactionUserDao.insertUser : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@461b460f]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@461b460f]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@461b460f]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@461b460f]
o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(347049211<open>)]
o.h.e.t.internal.TransactionImpl : committing
o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(347049211<open>)] after transaction
o.s.orm.jpa.JpaTransactionManager : Resuming suspended transaction after completion of inner transaction
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1872599585<open>)] for JPA transaction
# 创建子方法事务(UserServiceImpl的insertUser方法)
o.s.orm.jpa.JpaTransactionManager : Suspending current transaction, creating new transaction with name [com.springboot.chapter2.transaction.TransactionUserServiceImpl.insertUser]
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(988452796<open>)] for JPA transaction
o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
o.h.e.t.internal.TransactionImpl : begin
o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@756b60f8]
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ee3b09]
o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@871425451 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c4b1509] will be managed by Spring
c.s.c.t.TransactionUserDao.insertUser : ==> Preparing: insert into tms_app_user(id,name,phone)values( ?,?,?)
c.s.c.t.TransactionUserDao.insertUser : ==> Parameters: null, null, null
c.s.c.t.TransactionUserDao.insertUser : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ee3b09]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ee3b09]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ee3b09]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ee3b09]
o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(988452796<open>)]
o.h.e.t.internal.TransactionImpl : committing
o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(988452796<open>)] after transaction
o.s.orm.jpa.JpaTransactionManager : Resuming suspended transaction after completion of inner transaction
o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1872599585<open>)]
o.h.e.t.internal.TransactionImpl : committing
o.s.orm.jpa.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction
m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]
m.m.a.RequestResponseBodyMethodProcessor : Writing [{success=true, user=[AppUserEntity(id=200016, name=bb, phone=null, type=null, loginName=null, passwo (truncated)...]
o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
o.s.web.servlet.DispatcherServlet : Completed 200 OK
注释是我加进去的。从日志中可以看到,它启用了新的数据库事务去运行每一个insertUser方法,并且独立提交,这样就完全脱离了原有事务的管控,每一个事务都可以拥有自己独立的隔离级别和锁。
NESTED隔离级别也基本类似,可自行测试。它是一个如果子方法回滚而当前事务不回滚的方法。
在大部分的数据库中,一段SQL语句中可以设置一个标志位,然后后面的代码执行时如果有异常,只是回滚到这个标志位的数据状态,而不会让这个标志位之前的代码也回滚。
这个标志位,在数据库的概念中被称为保存点(save point)。从加粗日志部分可以看到,Spring为我们生成了nested事务,而从其日志信息中可以看到保存点的释放,可见Spring也是使用保存点技术来完成让子事务回滚而不致使当前事务回滚的工作。
注意,并不是所有的数据库都支持保存点技术,因此Spring内部有这样的规则:当数据库支持保存点技术时,就启用保存点技术;如果不能支持,就新建一个事务去运行你的代码,即等价于REQUIRES_NEW传播行为。
NESTED传播行为和REQUIRES_NEW还是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性,这是在应用中需要注意的地方。
七、@Transactional自调用失效问题
同用一个Service,用A方法调用B方法,B方法上加事务注解,注解会失效:
Spring数据库事务的约定,其实现原理是AOP,而AOP的原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样Spring就不能把你的代码织入到约定的流程中。
正确做法:用一个Service去调用另一个Service,这样就是代理对象的调用,Spring才会将代码织入事务流程。