Spring基础-11-事务细节

langyue 2020-06-09

1         事务的隔离级别

1.1         数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。

脏读

      [1]Transaction01将某条记录的AGE值从20修改为30。

      [2]Transaction02读取了Transaction01更新后的值:30。

      [3]Transaction01回滚,AGE值恢复到了20。

      [4]Transaction02读取到的30就是一个无效的值。

不可重复读

      [1]Transaction01读取了AGE值为20。

      [2]Transaction02将AGE值修改为30。

      [3]Transaction01再次读取AGE值为30,和第一次读取不一致。

幻读

      [1]Transaction01读取了STUDENT表中的一部分数据。

      [2]Transaction02向STUDENT表中插入了新的行。

      [3]Transaction01读取了STUDENT表时,多出了一些行。

1.2         隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

读已提交:READ COMMITTED

         要求Transaction01只能读取Transaction02已提交的修改。

可重复读:REPEATABLE READ

         确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

串行化:SERIALIZABLE

         确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

⑤各个隔离级别解决并发问题的能力见下表

脏读

不可重复读

幻读

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

⑥各种数据库产品对事务隔离级别的支持程度

Oracle

MySQL

READ UNCOMMITTED

×

READ COMMITTED

REPEATABLE READ

×

√(默认)

SERIALIZABLE

修改MySQL隔离级别
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

如:SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

查询MySQL的隔离级别
SELECT @@global.tx_isolation; //查询全局隔离级别
SELECT @@session.tx_isolation;//查询当前会话隔离级别 
SELECT @@tx_isolation;//同上


事务操作
开启事务  start transaction;
提交事务  commit;
回滚事务  rollback;

各种读写情况演示

脏读:

Spring基础-11-事务细节

 可重复度:

Spring基础-11-事务细节

 不可重复度:

Spring基础-11-事务细节

 并发修改:底层会自动进行排队等待

Spring基础-11-事务细节

 事务的传播行为

 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

 Spring基础-11-事务细节

事务传播属性可以在@Transactional注解的propagation属性中定义。

说明

①REQUIRED传播行为

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

Spring基础-11-事务细节

②REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

Spring基础-11-事务细节

 案例代码:

BookDao.java:

package com.atguigu.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
    
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    /**
     * 1、减余额
     * 
     * 减去某个用户的余额
     */
    public void updateBalance(String userName,int price){
        String sql = "UPDATE account SET balance=balance-? WHERE username=?";
        jdbcTemplate.update(sql, price,userName);
    }
    
    /**
     * 2、按照图书的ISBN获取某本图书的价格
     * @return
     */
    public int getPrice(String isbn){
        String sql = "SELECT price FROM book WHERE isbn=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }
    
    /**
     * 3、减库存;减去某本书的库存;为了简单期间每次减一
     */
    public void updateStock(String isbn){
        String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
        jdbcTemplate.update(sql, isbn);
    }
    
    /**
     * 4、改图书价格
     * @param isbn
     * @param price
     */
    public void updatePrice(String isbn,int price){
        String sql = "update book set price=? where isbn=?";
        jdbcTemplate.update(sql, price,isbn);
    }
}

BookService.java:

package com.atguigu.service;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.atguigu.dao.BookDao;

@Service
public class BookService {
    
    @Autowired
    BookDao bookDao;
    
    
    
//    @Autowired
//    BookService bookService;
    
    /**
     * 事务细节:
     * isolation-Isolation:事务的隔离级别;
     * 
     * 
     * 
     * noRollbackFor-Class[]:哪些异常事务可以不回滚
     * noRollbackForClassName-String[](String全类名):
     * 
     * rollbackFor-Class[]:哪些异常事务需要回滚;
     * rollbackForClassName-String[]:
     * 
     * 异常分类:
     *         运行时异常(非检查异常):可以不用处理;默认都回滚;
     *         编译时异常(检查异常):要么try-catch,要么在方法上声明throws
     *                 默认不回滚;
     * 
     * 事务的回滚:默认发生运行时异常都 回滚,发生编译时异常不会回滚;
     * noRollbackFor:哪些异常事务可以不回滚;(可以让原来默认回滚的异常给他不回滚)
     *     noRollbackFor={ArithmeticException.class,NullPointerException.class}
     * noRollbackForClassName
     * 
     * rollbackFor:原本不回滚(原本编译时异常是不回滚的)的异常指定让其回滚;
     * 
     * readOnly-boolean:设置事务为只读事务:
     *         可以进行事务优化;
     *         readOnly=true:加快查询速度;不用管事务那一堆操作了。
     * 
     * timeout-int(秒为单位):超时:事务超出指定执行时长后自动终止并回滚
     * @throws FileNotFoundException 
     * 
     * 
     * propagation-Propagation:事务的传播行为;
     *     传播行为(事务的传播+事务的行为);
     *         如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;
     * 传播行为:
     * AService{
     *         tx_a(){
     *             //a的一些方法
     *             tx_b(){
     *             }
     *             tx_c(){
     *             }
     *         }
     * }
     * 
     * 
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void checkout(String username,String isbn){
        //1、减库存
        bookDao.updateStock(isbn);
        
        int price = bookDao.getPrice(isbn);
//        try {
//            Thread.sleep(3000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        //2、减余额
        bookDao.updateBalance(username, price);
        
        //int i = 10/0;
        //new FileInputStream("D://hahahahha.aa");
    }
    
    
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void updatePrice(String isbn,int price){
        bookDao.updatePrice(isbn, price);
    }
    
    
    
    /**
     * 根据业务的特性;进行调整
     * isolation=Isolation.READ_UNCOMMITTED:读出脏数据
     * 
     * 
     *         READ_COMMITTED;实际上业务逻辑中用的最多的也是这个;
     *         REPEATABLEP_READ;
     * @param isbn
     * @return
     */
    @Transactional(readOnly=true)
    public int getPrice(String isbn){
        return bookDao.getPrice(isbn);
    }
    
    
    @Transactional
    public void mulTx(){
        
        //ioc.getBean("BookSerice");
        checkout("Tom", "ISBN-001");
        
        updatePrice("ISBN-002", 998);
        
        int i=10/0;
    }
}

MulService.java:

package com.atguigu.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MulService {
    
    @Autowired
    private BookService bookService;
    
    @Transactional
    public void mulTx(){
        //都是可以设置的;
        //传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(使用同一条连接);
        //REQUIRED  
        bookService.checkout("Tom", "ISBN-001");
        
        //REQUIRED   REQUIRES_NEW
        bookService.updatePrice("ISBN-002", 998);
        
        //int i = 10/0;
    }

}

tx.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: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-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <context:component-scan base-package="com.atguigu"></context:component-scan>


    <!-- 0、引入外部配置文件 -->
    <context:property-placeholder location="classpath:dbconfig.properties" />
    <!-- 1、配置数据源 -->
    <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
    </bean>
    <!-- 2、配置JdbcTemplate操作数据库   value="#{pooledDataSource}"  ref="pooledDataSource"-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" value="#{pooledDataSource}"></property>
    </bean>
    
    <!-- 3、配置声明式事务
        1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
        2)、开启基于注解的事务式事务;依赖tx名称空间
        3)、给事务方法加注解
     -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="pooledDataSource"></property>
     </bean>
     
     <tx:annotation-driven transaction-manager="transactionManager"/>


</beans>

TxTest.java:

package com.atguigu.test;
import java.io.FileNotFoundException;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.atguigu.service.BookService;
import com.atguigu.service.MulService;

public class TxTest {
    
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");

    /**
     * 有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象
     * @throws FileNotFoundException
     * 
     * 
     * multx(){
     *         //REQUIRED
     *         A(){
     *             //REQUIRES_NEW
     *             B(){}
     *             //REQUIRED
     *             c(){}
     *         }
     *         
     *         //REQUIRES_NEW
     *         D(){
     *             DDDD()//  REQUIRES_NEW不崩,REQUIRED崩
     *             //REQUIRED
     *             E(){
     *                 //REQUIRES_NEW
     *                 F(){
     *                     //10/0(E崩,G崩,D崩,A,C崩)
     *                 }
     *             }
     *             //REQUIRES_NEW
     *             G(){}
     *         }
     * 
     * 
     *         10/0(B成功,D整个分支下全部成功)
     *         任何处崩,已经执行的REQUIRES_NEW都会成功;
     * 
     *         如果是REQUIRED;事务的属性都是继承于大事务的;
     *         而propagation=Propagation.REQUIRES_NEW可以调整
     *         默认:REQUIRED;
     * 
     *         REQUIRED:将之前事务用的connection传递给这个方法使用;
     *         REQUIRES_NEW:这个方法直接使用新的connection;
     * }
     * 
     */
    @Test
    public void test() throws FileNotFoundException {
        BookService bookService = ioc.getBean(BookService.class);
        
        //MulService bean = ioc.getBean(MulService.class);
        //bean.mulTx();
        
        //bookService.checkout("Tom", "ISBN-001");
        //int price = bookService.getPrice("ISBN-001");
        //System.out.println("读取到的数据:"+price);
        //System.out.println(bookService.getClass());
        
        //效果都没改(相当于回滚了),虽然mulTx的两个方法都开新车
        //bookService.mulTx();
        
        System.out.println(bookService.getClass());
        //如果是MulService --mulTx()---调用bookService两个方法
        //BookService---mulTx()--直接调用两个方法
        
        /***
         * MulServiceProxy.mulTx(){
         *         bookServiceProxy.checkout();
         *         bookServiceProxy.updatePrice();
         * }
         * 
         * 
         * 本类方法的嵌套调用就只是一个事务;
         * BookServiceProxy.mulTx(){
         *         checkout();
         *         updatePrice();
         *         //相当于
         *         bookDao.updateStock(isbn);
         *         int price = bookDao.getPrice(isbn);
         *         bookDao.updateBalance(username, price);
         * 
         *         bookDao.updatePrice(isbn, price);
         * }
         */
        
    }

}

流程图解:

Spring基础-11-事务细节

相关推荐

方志朋 / 0评论 2020-04-17