Spring 事务控制

声明式事务

事务

事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。

事务的特性

原子性

原子性指的是事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

一致性

一致性是指事务执行前后数据的完整性要保持一致。

隔离性

隔离性是指多个用户并发访问数据库的时候,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据要相互隔离。

持久性

持久性是指一个事务一旦被提交,它对数据库中数据的改变是永久的,即使数据库发生故障也不应该对其有任何影响。

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值