声明式事务
事务
事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务的特性
原子性
原子性指的是事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性
一致性是指事务执行前后数据的完整性要保持一致。
隔离性
隔离性是指多个用户并发访问数据库的时候,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据要相互隔离。
持久性
持久性是指一个事务一旦被提交,它对数据库中数据的改变是永久的,即使数据库发生故障也不应该对其有任何影响。
Spring事务管理的接口
高层抽象接口
PlatformTransactionManager 事务管理器
TransactionDefinition 事务定义信息
TransactionStatus 事务具体运行状态
PlatformTransactionManager
Spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现
TransactionDefinition
TransactionDefinition定义事务的隔离级别
如果不考虑隔离性,会引发如下的安全问题:
脏读:一个事务读取到另一个事务未提交的数据,如果这些数据被回滚,则读取到的数据是无效的
不可重复读:同一个事务中,多次读取同一个数据返回的结果有可能不同
幻读:一个事务读取了几行记录后,另一个事务插入以下记录,幻读就发生了。在后来的查询中,该事务就会发现有些原来没有的记录
事务的四种隔离级别
TransactionDefinition定义事务的传播行为
事务的传播行为用于解决各个事务之间相互嵌套的问题
TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下。
- String getName():获取事务对象名称。
- int getIsolationLevel():获取事务的隔离级别。
- int getPropagationBehavior():获取事务的传播行为。
- int getTimeout():获取事务的超时时间。
- boolean isReadOnly():获取事务是否只读。
TransactionStatus 获取事务的信息
TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作
名称 | 说明 |
---|---|
void flush() | 刷新事务 |
boolean hasSavepoint() | 获取是否存在保存点 |
boolean isCompleted() | 获取事务是否完成 |
boolean isNewTransaction() | 获取是否是新事务 |
boolean isRollbackOnly() | 获取是否回滚 |
void setRollbackOnly() | 设置事务回滚 |
Spring支持两种事务管理的方式
编程式事务
TransactionFilter{
try{
//获取连接
//设置自动提交
chain.doFilter();
//提交
}catch(Exception e){
//回滚
}finally{
//关闭连接释放资源
}
}
声明式事务
声明式事务管理代码入侵性最小,Spring是通过AOP实现的
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。Spring提供了事务切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描添加了Spring注解的组件-->
<context:component-scan base-package="cn.jason"/>
<!-- 使用context名称空间将外部配置文件加载进来-->
<context:property-placeholder location="druid.properties"/>
<!-- 配置数据源,并且加入到IOC容器的管理中-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="10"/>
</bean>
<!-- 让IOC容器管理jdbcTemplate对象的创建与维护-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!-- 控制住数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启基于注解的事务控制管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
为方法添加上@Transactional注解,标识这是一个事务
@Transactional
public void checkout(String username,String isbn){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
事务细节
Isolation isolation() 事务的隔离级别
Class<? extends Throwable>[] noRollbackFor() 不进行回滚的异常
String[] noRollbackForClassName() 不进行回滚的异常(全类名)
Class<? extends Throwable>[] rollbackFor() 进行回滚的异常
String[] rollbackForClassName() 进行回滚的异常(全类名)
异常的分类
运行时异常(非检测异常):可以不处理,默认都回滚
编译时异常(检测异常):要么try-catch 要么声明throws,默认不回滚
事务的回滚,默认发生运行时异常都回滚,发生编译时异常不会回滚
noRollbackFor:可以让默认回滚的异常让它不回滚
例如:
@Service
public class BookService {
@Autowired
BookDao bookDao;
//出现数学异常不回滚
@Transactional(noRollbackFor = {ArithmeticException.class})
public void checkout(String username,String isbn){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
int i = 1 / 0 ;
bookDao.updateBalance(username,price);
}
}
rollbackFor:默认不回滚的异常(编译时异常),指定让它回滚
Propagation propagation() 事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
如果有多个事务进行嵌套运行,子事务是否要和大事务共同用一个事务
Aservice{
tx_a(){
tx_b(){}
tx_c(){}
}
}
boolean readOnly() 设置事务为只读事务,如果该事务中的方法都是读取而不修改的话,可以对事务进行优化
int timeout() 事务超出指定执行时间后自动终止并回滚
事务的传播行为
另外有事务的业务逻辑,容器中保存的是它的代理对象
@Test
public void test() throws SQLException, FileNotFoundException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml") ;
BookService bookService = ioc.getBean(BookService.class);
//cn.jason.service.BookService@32502377
System.out.println(bookService);
}
案例1
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
//在updatePrice方法埋下异常
int i = 10 / 0 ;
}
@Transactional()
public int getPrice(String isbn){
return bookDao.getPrice(isbn) ;
}
}
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
// 传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(是否使用同一条连接)
//request
bookService.checkout("Tom","ISBN-001");
//request
bookService.updatePrice("ISBN-002",998);
}
}
public class TxTest {
@Test
public void test() throws SQLException, FileNotFoundException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml") ;
MulService mulService = ioc.getBean(MulService.class) ;
mulService.mulTx();
}
}
bookService的checkout和updatePrice两个事务的传播级别是REQUEST,当在MulService的mulTx事务中运行时,它们会被作为一个事务进行处理。此时,updatePrice方法出现异常,将导致整个事务的回滚。
案例2
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
//在updatePrice方法埋下异常
int i = 10 / 0 ;
}
@Transactional()
public int getPrice(String isbn){
return bookDao.getPrice(isbn) ;
}
}
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
// bookService.checkout作为一个新事务自己跑咯
//REQUIRES_NEW
bookService.checkout("Tom","ISBN-001");
//REQUIRES 和mulTx共用一个事务
//bookService.updatePrice执行报错,导致mulTx整个回滚
bookService.updatePrice("ISBN-002",998);
}
}
REQUIRES_NEW传播属性是启动新的事务,并在自己的事务内运行。如果有事务在运行,则将之前的事务挂起(暂停)
结果就是
bookService.checkout开启一个新事务,并且暂停mulTx事务的运行,最终成功提交,再次开启mulTx事务。
mulTx与bookService.updatePrice作为一个事务,运行到bookService.updatePrice发生异常进行回滚,而bookService.checkout已经成功提交
案例3
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
}
@Transactional()
public int getPrice(String isbn){
return bookDao.getPrice(isbn) ;
}
}
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
// 传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(是否使用同一条连接)
//REQUIRED
bookService.checkout("Tom","ISBN-001");
//REQUIRED
bookService.updatePrice("ISBN-002",998);
int i = 1 / 0 ;
}
}
传播属性REQUIRED,bookService.checkout、bookService.updatePrice和mulTx作为同一个事务
当mulTx事务在int i = 1 / 0 处出现异常时,bookService.checkout、bookService.updatePrice和mulTx都进行回滚。
案例4
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
}
@Transactional()
public int getPrice(String isbn){
return bookDao.getPrice(isbn) ;
}
}
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
// 传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(是否使用同一条连接)
//REQUIRES_NEW
bookService.checkout("Tom","ISBN-001");
//REQUIRES_NEW
bookService.updatePrice("ISBN-002",998);
//埋下异常
int i = 1 / 0 ;
}
}
bookService.checkout和bookService.updatePrice的传播属性都是REQUIRES_NEW,都开启了一个新事务
当mulTx事务在int i = 1 / 0 处出现异常时,前两个事务已经提交,此时只回滚mulTx事务。
任何地方崩掉,已经执行的REQUIRES_NEW都会成功(已经提交)
如果是REQUIRED,事务的属性都是继承于大事务,例如timeout等属性
REQUESTED:将之前事务用的connection传递给这个方法使用
REQUIRES_NEW:这个方法直接使用新的connection
本类之间的事务调用就只是一个事务
@Service
public class BookService {
@Autowired
BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn) {
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
}
@Transactional()
public int getPrice(String isbn){
return bookDao.getPrice(isbn) ;
}
@Transactional
public void multx(){
checkout("Tom","ISBN-001");
updatePrice("ISBN-002",998);
int i = 1/ 0 ;
}
/*
这两个是相等的
@Transactional
public void multx(){
checkout("Tom","ISBN-001");
updatePrice("ISBN-002",998);
int i = 1/ 0 ;
}
@Transactional
public void multx(){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price);
bookDao.updatePrice(isbn,price);
int i = 1/ 0 ;
}
*/
}
ioc.getBean(BookService.class) 获取的是BookService的代理对象,由这个代理对象进行事务控制。而这个multx事务中的checkout和updatePrice方法是BookService对象的方法而非BookService的代理对象,所以不存在事务控制的能力,即使是标注了@Transactional注解。
使用XML配置事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.jason"/>
<context:property-placeholder location="druid.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="10"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 真正的执行事务控制-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 让它控制住数据源里面的链接的关闭与提交-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置切面方法-->
<aop:config>
<!-- 选出我们要进行切入的方法-->
<aop:pointcut id="myPointcut" expression="execution(* cn.jason.service..*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="myPointcut"/>
</aop:config>
<!-- 配置事务建议-->
<tx:advice transaction-manager="transactionManager" id="interceptor">
<!-- 配置事务属性-->
<tx:attributes>
<!-- 指明让哪些由切入点表达式选出的方法成为事务方法,并且配置它们的属性-->
<tx:method name="checkout" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
</beans>