笔记簿
ᴄᴏᴅɪɴɢ ɪs ᴀʀᴛ
首页
关于
搜索
登录
注册
近期文章
Spring @Transactional原理及使用 - 主要介绍Spring声明式事务中使用注解@Transactional的原理及注意事项
##### 本文主要讨论Spring声明式事务中使用注解@Transactional的方式、原理及注意事项,主要包括以下内容: ```text Spring @Transactional的配置使用; Spring @Transactional的传播行为和隔离级别; Spring @Transactional的工作原理; Spring @Transactional的注意事项; Spring @Transactional自我调用中的问题。 ``` ##### 1.Spring 中 @Transactional的配置 - 在Spring配置文件中引入命名空间 ```xml
``` - xml配置文件中,添加事务管理器bean配置 ```xml
``` - 在@Configuration中使用@EnableTransactionManager注解 ```java @Configuration @EnableTransactionManagement(proxyTargetClass = true) @MapperScan(basePackages = "cn.promptness.kpi.dao") public class DataSourceConfiguration { } ``` - 在使用事务的方法或者类上添加@Transactional(value="transactionManager")注解 ##### 2.Spring @Transactional的传播行为和隔离级别 - 事务注解方式: @Transactional - 标注在类前:标示类中所有方法都进行事务处理 - 标注在接口、实现类的方法前:标示方法进行事务处理 - 事务传播行为介绍: | 事务传播行为 | 说明 | | ------------ | ------------ | | @Transactional(propagation=Propagation.REQUIRED) | 如果有事务, 那么加入事务, 没有的话新建一个(默认情况) | | @Transactional(propagation=Propagation.NOT_SUPPORTED) | 容器不为这个方法开启事务 | | @Transactional(propagation=Propagation.REQUIRES_NEW) | 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 | | @Transactional(propagation=Propagation.MANDATORY) | 必须在一个已有的事务中执行,否则抛出异常 | | @Transactional(propagation=Propagation.NEVER) | 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) | | @Transactional(propagation=Propagation.SUPPORTS) | 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务 | - 事务超时设置 ```java @Transactional(timeout=30) //默认是30秒 ``` - 事务隔离级别 |事务隔离级别|说明| |------------|------------| |@Transactional(isolation = Isolation.READ_UNCOMMITTED)|读取未提交数据(会出现脏读, 不可重复读),基本不使用| |@Transactional(isolation = Isolation.READ_COMMITTED)(SQLSERVER默认)|读取已提交数据(会出现不可重复读和幻读)| |@Transactional(isolation = Isolation.REPEATABLE_READ)|可重复读(会出现幻读)| |@Transactional(isolation = Isolation.SERIALIZABLE)|串行化| 脏读 : 一个事务读取到另一事务未提交的更新数据 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据。相反,”可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据 幻读 : 一个事务读到另一个事务已提交的insert数据 - @Transactional的属性: |属性|类型|描述| |----------|------------|---------| |value|String|可选的限定描述符,指定使用的事务管理器| |propagation|enum:Propagation|可选的事务传播行为设置| |isolation|enum:Isolation|可选的事务隔离级别设置| |readOnly|boolean|读写或只读事务,默认读写| |timeout|int(in seconds granularity)|事务超时时间设置| |rollbackFor|Class对象数组,必须继承自Throwable|导致事务回滚的异常类数组| |rollbackForClassName|类名数组,必须继承自Throwable|导致事务回滚的异常类名字数组| |noRollbackFor|Class对象数组,必须继承自Throwable|不会导致事务回滚的异常类数组| |noRollbackForClassName|类名数组,必须继承自Throwable|不会导致事务回滚的异常类名字数组| #### 3.Spring @Transactional的工作原理 - 自动提交 默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。 事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。这点,Spring会在org/springframework/jdbc/datasource/DataSourceTransactionManager.java中将底层连接的自动提交特性设置为false。 ```java // switch to manual commit if necessary。 this is very expensive in some jdbc drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already)。if (con。getautocommit()) { txobject.setmustrestoreautocommit(true); if (logger.isdebugenabled()) { logger.debug("switching jdbc connection [" + con + "] to manual commit"); } //首先将自动提交属性改为false con.setautocommit(false); } ``` - spring事务回滚规则 - Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。 - 默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚)。而抛出checked异常则不会导致事务回滚。 - Spring也支持明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务。 - 还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。 #### 4.Spring @Transactional的注意事项 - 由于Spring事务管理是基于接口代理或动态字节码技术,通过AOP实施事务增强的。 - 对于基于接口动态代理的AOP事务增强来说,由于接口的方法是public的,这就要求实现类的实现方法必须是public的(不能是protected,private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其它方法不可能被动态代理,相应的也就不能实施AOP增强,也即不能进行Spring事务增强。 - 基于CGLib字节码动态代理的方案是通过扩展被增强类,动态创建子类的方式进行AOP增强植入的。由于使用final,static,private修饰符的方法都不能被子类覆盖,相应的,这些方法将不能被实施的AOP增强。 - 所以,必须特别注意这些修饰符的使用,@Transactional 注解只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但是这个被注解的方法将不会展示已配置的事务设置。 - 用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚。默认遇到运行期异常(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的异常时回滚;而遇到需要捕获的异常(throw new Exception(“注释”);)不会回滚,即遇到受检查的异常(就是非运行时抛出的异常,编译器会检查到的异常叫受检查异常或说受检查异常)时,需我们指定方式来让事务回滚 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception。class,其它异常}) 。如果让unchecked异常不回滚: @Transactional(notRollbackFor=RunTimeException.class) 如下: ```java @Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚 public void methodName(){ throw new Exception("注释"); } @Transactional(noRollbackFor=Exception.class) //指定不回滚,遇到运行期异常(throw new RuntimeException("注释");)会回滚 public ItimDaoImpl getItemDaoImpl(){ throw new RuntimeException("注释"); } ``` - 仅仅 @Transactional注解的出现不足于开启事务行为,它仅仅是一种元数据,能够被可以识别 @Transactional注解和上述的配置适当的具有事务行为的beans所使用。其实,根本上是 元素的出现 开启了事务行为。 - Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类的方法上使用 @Transactional 注解。 @Transactional 注解标识的方法,处理过程尽量的简单。尤其是带锁的事务方法,能不放在事务里面的最好不要放在事务里面。可以将常规的数据库查询操作放在事务前面进行,而事务内进行增、删、改、加锁查询等操作。 @Transactional 注解的默认事务管理器bean是“transactionManager”,如果声明为其他名称的事务管理器,需要在方法上添加@Transational(“managerName”)。
@Transactional 注解标注的方法中不要出现网络调用、比较耗时的处理程序,因为,事务中数据库连接是不会释放的,如果每个事务的处理时间都非常长,那么宝贵的数据库连接资源将很快被耗尽。
#### 5.Spring @Transactional自我调用中的问题 Spring事务使用AOP代理后的方法调用执行流程,如图所示:  从图中可以看出,调用事务时首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强。即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。 这样在自我调用时,则会出现无法开启事务的问题,比如: ```java public interface TargetService { public void a(); public void b(); } @Service public class TargetServiceImpl implements TargetService { public void a() { this.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { //执行数据库操作 } } ``` 此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,因此b方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)”将不会实施,即结果是b和a方法的事务是方法的事务定义是一样的。
解决方法一:
通过BeanPostProcessor 在目标对象中注入代理对象: 一、定义BeanPostProcessor 需要使用的标识接口 ```java public interface BeanSelfAware { public abstract void setSelf(Object obj); } ``` 二、定义自己的BeanPostProcessor(InjectBeanSelfProcessor) ```java @Component public class InjectBeanSelfProcessor implements BeanPostProcessor,ApplicationContextAware { private ApplicationContext context; // ① 注入ApplicationContext public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (!(bean instanceof BeanSelfAware)) { // ② 如果Bean没有实现BeanSelfAware标识接口 跳过 return bean; } if (AopUtils.isAopProxy(bean)) { // ③ 如果当前对象是AOP代理对象,直接注入 ((BeanSelfAware) bean).setSelf(bean); } else { // ④ 如果当前对象不是AOP代理,则通过context.getBean(beanName)获取代理对象并注入 // 此种方式不适合解决prototype Bean的代理对象注入 ((BeanSelfAware) bean).setSelf(context.getBean(beanName)); } return bean; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } } ``` 三、目标类实现 ```java public interface TargetService { public void a(); public void b(); } @Service public class TargetServiceImpl implements TargetService, BeanSelfAware { private TargetService self; public void setSelf(Object proxyBean) { //通过InjectBeanSelfProcessor注入自己(目标对象)的AOP代理对象 this.self = (TargetService) proxyBean; } public void a() { self.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { //执行数据库操作 } } ``` postProcessAfterInitialization根据目标对象是否实现BeanSelfAware标识接口,通过setSelf(bean)将代理对象(bean)注入到目标对象中,从而可以完成目标对象内部的自我调用。
解决方法二:
```java public interface TargetService { public void a(); public void b(); } @Service public class TargetServiceImpl implements TargetService { public void a() { TargetService proxyTarget = (TargetService)AopContext.currentProxy(); proxyTarget.b(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void b() { //执行数据库操作 } } ``` 这种方式完全将您的代码耦合到SpringAOP中,它使类本身意识到它正在AOP上下文中使用,
如果使用这种方式,还需要配置(exposeProxy = true)
«首页
1
2
3
4
5
末页»
近期文章
Spring @Transactional原理及使用
技术评审,你拿什么来吐槽?
springboot安全问题之CSRF
html页面通过js生成pdf文档
Docker简介与安装
Redis 分布式锁
RabbitMQ入门
高CPU占用排查并生产故障
Shell 读取控制台输入
Shell 工具cut
文章分类
Java
RabbitMQ
Redis
Spring
SpringMVC
SpringBoot
Mysql
Go
Mybatis
Apollo
Consul
Linux
Docker
Nginx
Other
天气