事务四大特性
1. 原子性(Atomicity):事务不可再分,要么都执行,要么都不执行。
2. 一致性(Consistency):事务执行前后,数据的完整性保持一致,即修改前后数据总量是一样的大概。
3. 隔离性(Isolation):一个事务执行过程中,不会受到其他事务干扰。
4. 持久性(Durability):事务一旦结束,对数据库的影响是永久的。数据持久化到数据库中。
SQL标准定义的四种隔离级别
1. READ UNCOMMITTED(未提交读):
1. 在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为**脏读(Dirty Read)**。
2. 性能上并不占优势
2. READ COMMITTED(提交读):
1. 大多数数据库系统的默认隔离级别都是READ COMMITTED **(但MySQL不是)**。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。
2. 提交读有时也叫做**不可重复读(nonrepeatable read)**。
3. REPEATABLE READ(可重复读):
1. REPEATABLE READ解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。 但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。
2. 幻读:当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前事务再次读取该范围的记录时,会产生幻行(Phantom Row)。
3. InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决幻读问题。
4. MySQL的默认事务隔离级别。
4. SERIALIZABLE(可串行化):
SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。
5. ANSI SQL隔离级别
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
Spring事务的五个隔离级别
1. ISOLATION_DEFAULT(默认):使用数据库默认的事务隔离级别。
2. ISOLATION_READ_UNCOMMITTED:事务最低的隔离级别,允许一个事务可以读取另一个事务未提交的数据。 会产生脏读、不可重复读和幻读。
3. ISOLATION_READ_COMMITTED: 保证一个事务只能读取另一个事务修改并提交后的数据,不能读取未提交的数据。防止脏读。
4. ISOLATION_REPEATABLE_READ:保证一个事务不能更新另一个事务修改但尚未提交的数据。可以避免脏读和不可重复读。
5. ISOLATION_SERIALIZABLE:序列化执行所有事务。都避免了,但是效率极低。
Spring事务7个传播机制
1. 在同一个事务中:
1. PROPAGION_REQUIRED(默认):支持当前事务,不存在则创建一个新的事务。
2. PROPAGION_SUPPORTS:支持当前事务,不存在就以非事务方式运行。
3. PROPAGION_MANDATORY:支持当前事务,如果不存在,抛出异常。
2. 不同事务中:
1. PROPAGIN_REQUIRES_NEW:当前存在事务则挂起,创建一个新事务。
2. PROPAGION_SUPPORTS:当前存在事务则挂起,以非事务方式运行。
3. PROPAGION_NEVER:当前存在事务抛出异常,非事务方式运行。
4. PROPAGION_NESTED:当前存在事务则嵌套事务执行。
Spring事务传播机制demo
传播机制规律
1. 事务失效原因:
1. 对象没有被spring管理
2. 事务方法不是public修饰符修饰
3. 异常被捕获,举例: com.example.transaction.User2ServiceImpl.addRequiredAndException方法;
但是需要注意的是当外围方法跟调用方法不在同一类时,外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。举例:com.example.transaction.TransactionApplicationTests.transaction_required_required_exception_try
4. 发生自身调用,类中的一个没有启用事务的方法A() 调用了本类中的另一个启用事务的方法B();解决方案:获取当前类的代理对象,然后在A()中用代理对象调用B()。从Bean容器中获取当前实例,本质上还是Spring动态代理对象 UserServiceImpl self = applicationContext.getBean(UserServiceImpl.class);
2. 事务传播的规律
1. 外围方法未开启事务的情况下,
1. Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
2. Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
3. Propagation.NESTED和Propagation.REQUIRE作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
4. MANDATORY修饰的内部方法会抛出异常
2. 外围方法开启事务的情况下,
1. Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
2. Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。
3. Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。
4. NEVER修饰的内部方法会抛出异常
创建表结构
CREATE TABLE `user1` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;``
``CREATE TABLE `user2` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
用例代码
@Service
public class User1ServiceImpl {
@Autowired
private User1Mapper user1Mapper;
/**
*
* @param user user
*/
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void addRequired(User1 user){
user1Mapper.insert(user);
}
/**
*
* @param user
*/
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(User1 user){
user1Mapper.insert(user);
}
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NESTED)
public void addNested(User1 user){
user1Mapper.insert(user);
}
@Service
public class User2ServiceImpl {
@Autowired
private User2Mapper user2Mapper;
/**
*
* @param user user
*/
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void addRequired(User2 user){
user2Mapper.insert(user);
}
/**
*
* @param user user
*/
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void addRequiredException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void addRequiredAndException(){
User2 user2=new User2();
user2.setName("李四1233qq");
addRequired(user2);
User2 user2_1=new User2();
user2_1.setName("李四45644qq");
try{
addRequiredException(user2_1);
}catch (Exception e){
System.out.println("同一类内事务异常被捕获,导致不会滚");
}
}
/**
*
* @param user
*/
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(User2 user){
user2Mapper.insert(user);
}
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NESTED)
public void addNested(User2 user){
user2Mapper.insert(user);
}
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NESTED)
public void addNestedException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
public void interiorQuote(){
User2 user2=new User2();
user2.setName("李四qqqq");
addRequired(user2);
User2 user2_1=new User2();
user2_1.setName("李四aaaaaa");
addRequiredException(user2_1);
}
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void interiorQuote2(){
User2 user2=new User2();
user2.setName("李四cccccc");
addRequired(user2);
User2 user2_1=new User2();
user2_1.setName("李四dddddd");
addRequiredException(user2_1);
}
}
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NEVER)
public void addSupports(){
User1 user1=new User1();
user1.setName("李四1233qqaaa");
user1Mapper.insert(user1);
}
}
@SpringBootTest
class TransactionApplicationTests {
@Autowired
private User1ServiceImpl user1Service;
@Autowired
private User2ServiceImpl user2Service;
@Test
void contextLoads() {
}
/*-------------通过以下两个方法证明在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。-----*/
/**
* 外围方法无事务,调用方法事务传播机制REQUIRED,外围方法异常;不影响调用方法事务提交
*/
@Test
public void notransaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
/**
* 外围方法无事务,调用方法事务传播机制REQUIRED,调用方法user2Service.addRequiredException(),发生异常,user1Service.addRequired正常提交, user2Service.addRequiredExceptio回滚
*/
@Test
public void notransaction_required_required_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiredException(user2);
}
/*-------------通过以下外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。-----*/
/**
* 外围方法启用事务指定传播机制REQUIRED,调用方法事务传播机制REQUIRED,外围方法异常,调用方法也回滚
*/
@Test
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三3");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四3");
user2Service.addRequired(user2);
throw new RuntimeException();
}
/**
* 外围方法启用事务指定传播机制REQUIRED,调用方法事务传播机制REQUIRED,,调用方法user2Service.addRequiredException(),发生异常,user1Service.addRequired、 user2Service.addRequiredExceptio回滚
*/
@Test
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void transaction_required_required_exception(){
User1 user1=new User1();
user1.setName("张三3");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四3");
user2Service.addRequiredException(user2);
}
/**
* 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。
*/
@Test
@Transactional
public void transaction_required_required_exception_try(){
User1 user1=new User1();
user1.setName("张三3");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四3");
try {
user2Service.addRequiredException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
/**
* 如果在同一个类中,catch不回滚;spring声明式事务 同一类内方法调用事务失效
*/
@Test
public void addRequiredAndException(){
user2Service.addRequiredAndException();
}
@Test
public void interiorQuote(){
user2Service.interiorQuote();
}
@Test
public void interiorQuote2(){
user2Service.interiorQuote2();
}
/*--------通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。-----*/
/**
* 外围方法没有事务,调用方法事务传播机制REQUIRES_NEW,两个事务独立运行,外围发生异常不会回滚
*/
@Test
public void notransaction_exception_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("张三111");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("李四111");
user2Service.addRequiresNew(user2);
throw new RuntimeException();
}
/**
* 外围方法没有事务,,调用方法事务传播机制REQUIRES_NEW,两个事务独立运行,其中一个方法异常回滚,其他事务不受影响
*/
@Test
public void notransaction_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNewException(user2);
}
/*--在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰.---*/
/**
* 外围方法事务机制REQUIRED,调用方法事务机制为REQUIRED和REQUIRES_NEW,外围方法异常,同为REQUIRED的事务会归位同一事务,一起回滚;REQUIRES_NEW为独立事务不受影响
*/
@Test
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void transaction_exception_required_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("张三aaa");
user1Service.addRequired(user1);
User1 user2=new User1();
user2.setName("李四aaa");
user1Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五aaa");
user2Service.addRequiresNew(user3);
throw new RuntimeException();
}
/**
* 外围方法事务机制REQUIRED,调用方法事务机制为REQUIRED和REQUIRES_NEW,调用方法发生异常回滚,REQUIRED发生回滚,REQUIRES_NEW不受影响
*/
@Test
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("张三bbb");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四bbb");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五bbb");
user2Service.addRequiresNewException(user3);
}
/**
* 外围方法事务机制REQUIRED,调用方法事务机制为REQUIRED和REQUIRES_NEW,调用方法发生即使被捕获,异常方法和REQUIRED发生回滚,REQUIRES_NEW不受影响
*/
@Test
@Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
User1 user1=new User1();
user1.setName("张三ccc");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四ccc");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五ccc");
try {
user2Service.addRequiresNewException(user3);
} catch (Exception e) {
System.out.println("回滚");
}
}
/*-- 通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRE作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。 --*/
@Test
public void notransaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三ddd");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四ddd");
user2Service.addNested(user2);
throw new RuntimeException();
}
@Test
public void notransaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("张三eee");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四eee");
user2Service.addNestedException(user2);
}
@Test
@Transactional
public void addSupports(){
user1Service.addSupports();
}
/*-- 外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务--*/
@Test
@Transactional
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("张三fff");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四fff");
user2Service.addNested(user2);
throw new RuntimeException();
}
@Test
@Transactional
public void transaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("张三ggg");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四ggg");
user2Service.addNestedException(user2);
}
@Test
@Transactional
public void transaction_nested_nested_exception_try(){
User1 user1=new User1();
user1.setName("张三hhh");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四hhh");
try {
user2Service.addNestedException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
}