一、事件根本概念
什么是事件
- 数据库事件( transaction)是访问并可能操纵各种数据项的一个数据库操纵序列
- 这些操纵要么全部执行,要么全部不执行,是一个不可分割的工作单位
事件的特性
- A:原子性(Atomicity)
- 一个事件(transaction)中的所有操纵,要么全部完成,要么全部不完成,不会竣事在中心某个环节
- 事件在执行过程中发生错误,会被回滚(Rollback)到事件开始前的状态,就像这个事件从来没有执行过一样
- C:一致性(Consistency)
- 一个事件必须使数据库从一个一致性状态变换到另一个一致性状态
- 假如事件乐成地完成,那么系统中所有变化将正确地应用,系统处于有效状态
- 假如在事件中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态
- I:隔离性(Isolation)
- 指的是在并发环境中,当不同的事件同时操纵雷同的数据时,每个事件都有各自的完备数据空间
- 由并发事件所做的修改必须与任何其他并发事件所做的修改隔离
- 事件查看数据更新时,数据所处的状态要么是另一事件修改它之前的状态,要么是另一事件修改它之后的状态,事件不会查看到中心状态的数据
- D:长期性(Durability)
- 指的是只要事件乐成竣事,它对数据库所做的更新就必须保存下来
- 即使发生系统瓦解,重新启动数据库系统后,数据库还能规复到事件乐成竣事时的状态
二、基于注解的声明式事件
1、@Transactional注解标识的位置
- @Transactional标识在方法上,则只会影响该方法
- @Transactional标识的类上,则会影响类中所有的方法
2、事件属性:只读
- 对一个查询操纵来说,假如我们把它设置成只读,就能够明白告诉数据库,这个操纵不涉及写操纵
- 这样数据库就能够针对查询操纵来进行优化
- 默认false,可以查询,可以增删改,设置为true,只能查询
使用方式:
- @Transactional(readOnly = true)
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
复制代码 对增删改操纵设置只读会抛出下面非常:Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
3、事件属性:超时
- 事件在执行过程中,有可能由于遇到某些问题,导致程序卡住,从而长时间占用数据库资源
- 而长时间占用资源,大概率是由于程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)
- 此时这个很可能出问题的程序应该被回滚,撤销它已做的操纵,事件竣事,把资源让出来,让其他正常程序可以执行
- 概括来说就是一句话:超时回滚,释放资源
- 默认值为-1,不超时。设置时间以秒单位盘算
使用方式:
- //超时时间单位秒
- @Transactional(timeout = 3)
- public void buyBook(Integer bookId, Integer userId) {
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
复制代码 执行过程中抛出非常:org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
4、事件属性:回滚策略
- 声明式事件默认只针对运行时非常回滚,编译时非常不回滚
- 可以通过@Transactional中相干属性设置回滚策略
- rollbackFor属性:须要设置一个Class类型的对象
- noRollbackFor属性:须要设置一个Class类型的对象
- rollbackForClassName属性:须要设置一个字符串类型的全类名
- rollbackFor属性:须要设置一个字符串类型的全类名
使用方式:
- @Transactional(noRollbackFor = ArithmeticException.class)
- //@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1/0);
- }
复制代码 出现了数学运算非常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操纵正常执行
5、事件属性:隔离级别
- 数据库系统必须具有隔离并发运行各个事件的能力,使它们不会相互影响,避免各种并发问题
- 一个事件与其他事件隔离的程度称为隔离级别
- SQL标准中规定了多种事件隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
隔离级别一共有四种:
- 读未提交:READ_UNCOMMITTED(read_uncommitted)
- 答应Transaction01读取Transaction02未提交的修改
- 读已提交:READ_COMMITTED(read_committed)
- 要求Transaction01只能读取Transaction02已提交的修改
- 可重复读:REPEATABLE_READ(repeatable_read)
- 确保Transaction01可以多次从一个字段中读取到雷同的值
- 即使Transaction01执行期间别的事件对这个字段进行更新,Transaction01读到也没有变化
- 串行化:SERIALIZABLE(serializable)
- 确保Transaction01可以多次从一个表中读取到雷同的行,在Transaction01执行期间,克制别的事件对这个表进行添加、更新、删除操纵
- 可以避免任何并发问题,但性能十分低下
各个隔离级别办理并发问题的能力见下表:
隔离级别脏读不可重复读幻读READ UNCOMMITTED-读未提交有有有READ COMMITTED-读已提交无有有REPEATABLE READ-可重复读无无有SERIALIZABLE-串行读无无无 各种数据库产物对事件隔离级别的支持程度:
隔离级别OracleMySQLREAD UNCOMMITTED-读未提交×√READ COMMITTED-读已提交√(默认)√REPEATABLE READ-可重复读×√(默认)SERIALIZABLE-串行读√√ 使用方式:
- @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
- @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
- @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
- @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
- @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
复制代码 6、事件属性:流传行为
什么是事件的流传行为?
- 在service1类和service2类中分别有a()方法和b()方法,两个方法都是事件
- 当a()方法执行过程中调用了b()方法,事件是如何传递的?
- 归并到一个事件里?还是开启一个新的事件?这就是事件流传行为
七种流传行为
- REQUIRED(required):支持当前事件,假如不存在就新建一个(默认)【没有就新建,有就加入】
- SUPPORTS(supports):支持当前事件,假如当前没有事件,就以非事件方式执行【有就加入,没有就不管了】
- MANDATORY(mandatory):必须运行在一个事件中,假如当前没有事件正在发生,将抛出一个非常【有就加入,没有就抛非常】
- REQUIRES_NEW(requires_new):开启一个新的事件,假如一个事件已经存在,则将这个存在的事件挂起【不管有没有,直接开启一个新事件,开启的新事件和之前的事件不存在嵌套关系,之前事件被挂起】实用内部事件和外部事件不存在业务关联情况,如日志
- NOT_SUPPORTED(not_supported):以非事件方式运行,假如有事件存在,挂起当前事件【不支持事件,存在就挂起】
- NEVER(never):以非事件方式运行,假如有事件存在,抛出非常【不支持事件,存在就抛非常】
- NESTED(nested):假如当前正有一个事件在进行中,则该方法应当运行在一个嵌套式事件中。被嵌套的事件可以独立于外层事件进行提交或回滚。假如外层事件不存在,行为就像REQUIRED一样【有事件的话,就在这个事件里再嵌套一个完全独立的事件,嵌套的事件可以独立的提交和回滚。没有事件就和REQUIRED一样】
测试
- @Transactional(propagation = Propagation.REQUIRED)
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
复制代码
- @Service
- public class CheckoutServiceImpl implements CheckoutService {
- @Autowired
- private BookService bookService;
- @Override
- @Transactional
- //一次购买多本图书
- public void checkout(Integer[] bookIds, Integer userId) {
- for (Integer bookId : bookIds) {
- bookService.buyBook(bookId, userId);
- }
- }
- }
复制代码 观察结果:
- 买书方法@Transactional(propagation = Propagation.REQUIRED)
- 表现假如当火线程上有已经开启的事件可用,那么就在这个事件中运行
- 经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事件注解,因此在此事件中执行
- 所购买的两本图书的价格为80和50,而用户的余额为100
- 在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
- 买书方法@Transactional(propagation = Propagation.REQUIRES_NEW)
- 表现不管当火线程上是否有已经开启的事件,都要开启新事件
- 加在buybook()上,每次调用这个方法,都开起一个新事件,每次调用都不相干
- 每次购买图书都是在buyBook()的事件中执行,因此第一本图书购买乐成,事件竣事,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |