1、事务的概念
事务就是对于数据库数据的一组不可分割的操作,这些操作要么全部执行,要么全部不执行。
2、事务的特性
事务具有四大特性:ACID
2.1、原子性(Automicity)
一个事务的所有操作要么全部执行,要么全部不执行,不会结束在某个中间环节。事务如果在执行过程发生错误,就会被回滚(Rollback)到事务开始前的状态,就好像这个事务从来没有执行过。
2.2、一致性(Consistency)
一个事务执行前后,数据库的状态都要保持一致。
比如一个事务对数据a执行加一的操作,在事务成功执行后数据库中的这个数据a的值必须是加了一。或者两个人互相转账,转账前后的总金额都是不变的。
2.3、隔离性(Isolation)
主要指的是在并发环境下,一个事务的操作不受其他并发事务的影响,在一个独立隔离的环境中进行操作。事务查看数据更新时,要么看到的是其他并发事务对数据更新后的状态,要么是其他并发事务对数据更新前的状态,不会看到其他事务具体操作过程中数据的状态。
2.3、持久性(Durability)
指的是事务成功结束后,它对于数据库的更新会保存到磁盘当中实现一个持久化的存储。即使数据库崩溃或者重启,都能恢复到事务成功结束时的状态。
3、编程式事务
事务功能的相关操作都能通过自己编写代码实现
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
缺点:
- 细节没有被屏蔽,所有操作细节都需要开发人员自己去实现,比较繁琐。
- 代码的复用性比较差,如果对其中的一些功能有效抽离出来,每次实现都需要开发人员自己编写代码,效率低。
4、声明式事务概念
框架将对于事务的一些相关操作抽取出来进行封装,封装好后,只需要在配置文件中进行简单的配置就能完成操作。
优点:
- 提高了开发效率。
- 减少了冗余的代码。
- 框架会综合考虑实际开发中的各种问题,进行健壮性和性能等各个方面的优化。
所以,我们可以总结下面两个概念:
编程式:自己写代码实现功能
声明式:通过配置让框架实现功能
5、声明式事务实现
两种方式:基于注解实现和基于XML实现,第一种使用比较多,所以这里着重介绍第一种实现方式。
6、基于注解实现声明式事务
6.1、添加事务配置
在Spring的配置文件中添加相关配置,开启事务的注解驱动。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
6.2、添加事务注解
因为service层是处理业务逻辑的,因此事务的处理一般在service层进行。
在业务层添加@Transactional注解。
7、@Transactional注解
7.1、标注位置
标注到方法上,则只会影响这个方法。
标注到类上,则会影响这个类中的所有方法。
7.2、事务属性
1、只读
对一个查询操作来说,如果把它设置为只读,就能明确的告诉数据库,这个操作不涉及写操作,进行写操作时会抛出异常。
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
2、超时
事务在执行过程中可能遇到某些问题卡住,从而长时间的占用资源,因此需要设置一个超时时间,让事务回滚,释放资源。
//超时时间单位秒
@Transactional(timeout = 3)
3、回滚策略
声明式事务默认是针对运行时异常回滚,编译时异常不回滚。
相关属性:
-
rollbackFor属性:需要设置一个Class类型的对象
-
rollbackForClassName属性:需要设置一个字符串类型的全类名
-
noRollbackFor属性:需要设置一个Class类型的对象
-
rollbackFor属性:需要设置一个字符串类型的全类名
@Transactional(noRollbackFor = ArithmeticException.class)
4、隔离级别
隔离级别一共有四种:
-
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
-
读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。
-
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
-
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
5、传播行为
什么是事务的传播行为?
在一个类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
可以通过@Transactional中的propagation属性设置事务传播行为。
一共有七种传播行为:
-
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
-
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
-
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
-
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
-
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
-
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
-
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】