事务是逻辑上的一组操作,要么都执行,要么都不执行。
Spring 对事务的支持取决于数据库 ,如果是使用Mysql的myisam引擎,那么就是从根本上就不支持事务。Spring事务的本质就是数据库对事务的支持,使用JDBC的事务管理机制,利用java.sql.Connection对象完成对事务的提交。
未使用Spring框架前,Java中事务实现示例代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
public class DemoApplication {
public static void main(String[] args) {
// 1.获取连接
Connection conn = DriverManager.getConnection();
try {
// 2.将自动提交设置为false
conn.setAutoCommit(false);
/*-----------------*/
// 3.执行一到多个CRUD操作
/*-----------------*/
// 4.1手动提交
conn.commit();
} catch (Exception e) {
// 4.2一旦其中一个操作出错都将回滚,所有操作都不成功
conn.rollback();
} finally {
// 5.关闭连接
conn.colse();
}
}
}
Spring框架则提供统一的事务抽象,无论是JTA、JDBC、Hibernate/JPA、Mybatis/Mybatis-Plus,Spring都使用统一的编程模型,使得应用程序可以很容易地在不同的事务框架之间进行切换。
Spring事务管理的核心接口是PlatformTransactionManager
。接口PlatformTransactionManager
定义事务操作的行为,PlatformTransactionManager
依赖TransactionDefinition
和TransactionStatus
接口。
// 设置事务的传播行为以及隔离级别
@Transactional(propagation = Propagation.NESTED, isolation = Isolation.READ_COMMITTED)
public int upsert() {
....
}
注解属性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
字段名 | 类型 | 含义 |
---|---|---|
value | String | value 主要用来指定不同的事务管理器,满足在同一个系统中,存在不同的事务管理器。如果在 Spring 中,配置了多个数据源声明了多个事务管理器,可以通过该参数来进行指定事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
事务传播机制 【propagation】
public interface TransactionDefinition {
/**
* 默认选择,如果当前方法没有事务就新建一个事务;如果已存在一个事务,就加入到这个事务中
*/
int PROPAGATION_REQUIRED = 0;
/**
* 如果当前方法存在事务,则加入到这个事务中,如果当前不存在事务,以非事务方式执行
*/
int PROPAGATION_SUPPORTS = 1;
/**
* 被修饰的方法必须在事务中,否则抛出异常
*/
int PROPAGATION_MANDATORY = 2;
/**
* 必须运行在自己的事务中,如果当前方法已经在事务中,挂起当前事务,新建一个
*/
int PROPAGATION_REQUIRES_NEW = 3;
/**
* 被修饰的方法不应该运行在事务中,如果存在当前事务,则该事务在方法运行期间被挂起
*/
int PROPAGATION_NOT_SUPPORTED = 4;
/**
* 被修饰的方法必须不在事务中,否则抛出异常
*/
int PROPAGATION_NEVER = 5;
/**
* 当前方法支持嵌套事务,嵌套事务与当前事务进行单独提交和回滚,如果不存在当前事务,则行为和Required一样
*/
int PROPAGATION_NESTED = 6;
}
事务隔离级别【isolation】
同样是在TransctionDefinition
类中定义的。
public interface TransactionDefinition {
/**
* 使用数据库本身的隔离级别,MySQL(RR)、Oracle(RC)
*/
int ISOLATION_DEFAULT = -1;
/**
* 读未提交
*/
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
/**
* 读已提交
*/
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
/**
* 可重复度
*/
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
/**
* 串行化
*/
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
}
只读模式【readOnly】
readOnly=true的意义是什么?为什么我一个数据查询操作还要启用事务支持呢?
目的仅是优化数据库执行,mysql默认对每一个连接都启用autocommit模式,该模式下每发送一条sql执行完成后就自动提交事务,并开启一个事务。当readOnly=true时,所有的sql查询就都在一个事务里完成了。
@Transactional 事务注解原理
@Transactional
的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
@Transcational事务失效
- 数据库引擎不支持事务
- 没有被Spring管理
- 方法不是public
- 自调用
- 数据源没有配置事务管理器
- 传播机制设置成不支持事务
(@Transactional(propagation = Propagation.NOT_SUPPORTED))
- 异常不抛出,内部捕获
- 配置的回滚异常类型和抛出的异常类型不匹配
2. 没有被Spring管理
// 把@Service注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
3. 方法不是 public 的
源码解析:https://blog.csdn.net/CPLASF_/article/details/115365665
4. Spring AOP 自调用问题
原因
Spring中会产生两个类,原类A和代理类AA,各自有方法M1、M2和PM1、PM2,假设(P)M2上有注解,方法增强,(P)M1调用(P)M2。当外部直接调用代理类AA的PM2时候,是方法增强的(preHandler-M2-postHandler),但如果外部调用AA的PM1,实际是调用M1方法,M1在AA中并没有增强,只是直接调用A的M1方法,M1再调用M2方法(原类A方法的M2并没有被增强),所以没有走到增强逻辑,所以事务失效。
示例
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
MyService 类中的method1()调用method2()就会导致method2()的事务失效。
@Service
public class MyService {
private void method1() {
method2();
//......
}
@Transactional
public void method2() {
//......
}
}
解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
8. 配置的回滚异常类型和抛出的异常类型不匹配
// 这样事务也是不生效的,因为默认回滚的是:RuntimeException
// 如果你想触发其他异常的回滚,需要在注解上配置一下 @Transactional(rollbackFor = Exception.class)
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}