来源链接: 有关Spring事务,看这一篇就足够了.
来源链接: Spring事务不生效的场景分析.
来源链接: pring 事务失效的场景.
MySQL常用存储引擎之Innodb
链接: MySQL常用存储引擎之Innodb.
事务的特性 ACID
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring支持编程式事务管理和声明式的事务管理
编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
一般情况下比编程式事务好用。
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理作为横切关注点,通过aop方法模块化。
Spring中通过Spring AOP框架支持声明式事务管理。
spring事务传播特性 7种
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。
spring支持7种事务传播行为
propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这
个事务中,这是最常见的选择。
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与
propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
事务的隔离级别
事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。
在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:
脏读(Dirty read)
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。
不可重复读重点在修改。
幻读(Phantom reads)
幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。
幻读重点在新增或删除。
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
不可重复读和幻读不要搞混:不可重复读针对一行数据的修改,幻读针对向一张表中插入一条数据或删除一条数据
MySQL事务的隔离级别
① Read Uncommitted :一个事务可以读取另一个事务未提交的数据,安全级别最低,问题都会产生(读未提交)
② Read Committed :一个事务可以读取另一个事务已提交的数据,可以解决第一类丢失更新和脏读(读已提交)
③ Repeatable Read :事务开启后,其他事务不能对数据再进行修改(行锁),直到事务结束(可重复读)
④ Serializable :解决所有问题。 需要对数据表加锁,加锁会降低数据处理的性能,效率最低。(序列化)
事务超时
为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。
假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。
事务回滚规则
在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。
不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
Spring事务的失效场景
1.数据库或数据库引擎不支持事务
2.本类中没有事务的方法调用含有事务的方法
在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。
原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的
不同类调用 A类无事务方法 调用 B类有事务的方法,则事务生效
Spring事务底层使用的是AOP 思想,当Spring容器启动的时候会去解析和加载相关的类,为带有@Transactional方法的类生成代理类,在代理类中实现目标方法的增强,进入目标方法之前开启事务, 退出目标方法时提交/回滚事务。
加了这个注解,Spring会为其生成一个代理类,由代理类实现事务管理,通过环绕通知来增强。但是这个方法的调用者并不是代理类而是this,既然调用者变成了this,因此就无法开启事务管理
// 错误示例:
public interface UserInfoService {
void business();
void testTwo();
}
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
/**
* 带事务的业务代码
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void business() {
UserInfo userInfo = new UserInfo();
userInfo.setName("小周");
userInfo.setAge(26);
int insert = userInfoDao.insert(userInfo);
System.out.println(insert);// 1为执行成功
int i = 10/0; // 模拟异常
}
/**
* 本类中没有事务的方法调用含有事务的方法
*/
@Override
public void testTwo() {
business();
}
}
@SpringBootTest
class SpringbootDemoApplicationTests {
@Autowired
UserInfoService UserInfoService;
@Test
void contextLoads(){
UserInfoService.testTwo();
}
}
日志打印出现 Closing non transactional SqlSession
这种情况说明没有开启Spring的事务管理,因此才会关闭一个非事务的SqlSession
3.异常被捕获未抛出
// 错误示例:
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
/**
* 带事务的业务代码
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void business() {
UserInfo userInfo = new UserInfo();
userInfo.setName("小周");
userInfo.setAge(26);
int insert = userInfoDao.insert(userInfo);
System.out.println(insert);// 1为执行成功
try {
int i = 10/0; // 模拟异常
} catch (Exception e) {
e.printStackTrace(); // 异常捕获却不处理
}
}
}
@SpringBootTest
class SpringbootDemoApplicationTests {
@Autowired
UserInfoService UserInfoService;
@Test
void contextLoads(){
UserInfoService.business();
}
}
日志打印出现 Releasing transactional SqlSession
事务提交了
阿里开发手册建议
4.非RuntimeException异常
Spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,
事务是否执行取决于是否抛出RuntimeException。
如果抛出RuntimeException并在你的业务方法中没有catch到的话,事务会回滚。
因为Spring事务只能捕捉到Error和RuntimeException异常,而对于Exception异常是捕获不到的。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException()
// 错误示例:
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
/**
* 带事务的业务代码
*/
@Transactional
@Override
public void business() throws IOException {
UserInfo userInfo = new UserInfo();
userInfo.setName("小周");
userInfo.setAge(26);
int insert = userInfoDao.insert(userInfo);
System.out.println(insert);// 1为执行成功
FileInputStream fileInputStream = new FileInputStream(new File("xxxxx"));
fileInputStream.close();
}
}
@SpringBootTest
class SpringbootDemoApplicationTests {
@Autowired
UserInfoService UserInfoService;
@Test
void contextLoads() throws IOException {
UserInfoService.business();
}
}
RuntimeException:运行期异常,这种类型的异常不要求用户强制处理异常,
但是若发生了异常,则交付JVM进行默认处理。
用户可以选择不处理,也可以选择处理,如果出现了这种异常,那么一定是写程序的人的问题。
常见的RuntimeException几种情况:
错误的类型转换,ClassCastException,这个十分常见,原因也非常之多,不多赘述
数组越界,ArrayIndexOutOfBoundException,细心检查数组下标来杜绝此类异常发生
空指针异常,NullPointerException,检查变量是否为null
算数异常,ArithmeticException ,检查数学运算中是否存在分母为零等错误
并发修改异常,ConcurrentModificationException,一般出现在集合遍历时,如果线程在使用快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常,解决办法一般使用迭代器本身的方法或者选择使用其他方案遍历集合即可。
非RuntimeException:编译期异常,这种异常是用户必须要去处理的一类异常,不处理程序无法编译通过
这种异常大多属于IO流中的异常。
// 正确示例:
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
/**
* 带事务的业务代码
*/
@Transactional(rollbackFor = Exception.class)// 加上 rollbackFor = Exception.class
@Override
public void business() throws IOException {
UserInfo userInfo = new UserInfo();
userInfo.setName("小周");
userInfo.setAge(26);
int insert = userInfoDao.insert(userInfo);
System.out.println(insert);// 1为执行成功
FileInputStream fileInputStream = new FileInputStream(new File("xxxxx"));
fileInputStream.close();
}
}
5.rollbackFor属性使用错误
Spring默认抛出了未检查unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务;
其他异常不会触发回滚事务。
rollbackFor 可以指定能够触发事务回滚的异常类型。
如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
// 错误示例:
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoDao userInfoDao;
/**
* 带事务的业务代码
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void business() throws IOException {
UserInfo userInfo = new UserInfo();
userInfo.setName("小周");
userInfo.setAge(26);
int insert = userInfoDao.insert(userInfo);
System.out.println(insert);// 1为执行成功
FileInputStream fileInputStream = new FileInputStream(new File("xxxxx"));
fileInputStream.close();
}
}
Spring声明式事务配置参考
加上 rollbackFor = Exception.class
@Transactional(rollbackFor = Exception.class)
事务的传播性:@Transactional(propagation=Propagation.REQUIRED)
只读:@Transactional(readOnly=true)
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
事务的超时性:@Transactional(timeout=30)
事务回滚指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
事务回滚指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133940.html