ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Spring之事件
[打印本页]
作者:
勿忘初心做自己
时间:
2024-9-2 11:54
标题:
Spring之事件
一、事件根本概念
什么是事件
数据库事件( 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企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4