(续)SSM整合之spring笔记(声明式事务)(P110-116)

 

目录

一 .声明式事务概念

二 .基于注解的声明式事务

1 .准备工作 

(1)加入依赖

(2)创建jdbc.properties 

(3)配置Spring的配置文件     tx-annotation.xml

(4)创建表

 (5)创建组件   创建三层架构  

(6) 添加扫描组件

 (7) 添加自动装配@Autowired

(8) 写buyBook方法   买书的方法  

(9) 在BookService里面创建买书的方法 

(10) 找到实现类BookServiceImpl对买书的方法进行重写 

 (11)创建getPriceByBookId   updateStock    updateBalance方法

​编辑

(12)  找到实现类BookDaoImpl      对方法进行重写

(13)创建jdbcTemplate

(14)方法重写

二  .  测试无事务情况

三  .  加入事务

①添加事务配置

②添加事务注解

 ③观察结果

④ @Transactional注解标识的位置

四 、事务属性之只读

①介绍

②使用方式

③注意

五.  事务属性之超时 

①介绍

②使用方式

③观察结果

六.  事务属性之回滚策略

①介绍

②使用方式

③观察结果

七.  事务属性之事务隔离级别

八.  事务属性之事务传播行为

①介绍

②测试

③观察结果


一 .声明式事务概念

编程式事务

事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
  细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到    复用。
声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出
来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处 1 :提高开发效率
好处 2 :消除了冗余的代码
好处 3 :框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性
能等各个方面的优化
所以,我们可以总结下面两个概念:
编程式 自己写代码 实现功能
声明式 :通过 配置 框架 实现功能

二 .基于注解的声明式事务

1 .准备工作 

(1)加入依赖

<packaging>jar</packaging>

<dependencies>

    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- Spring 持久化层支持jar包 -->
    <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
    <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- Spring 测试相关   spring整合Junit的依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.31</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.1</version>
    </dependency>

</dependencies>

(2)创建jdbc.properties 

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

(3)配置Spring的配置文件     tx-annotation.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.atguigu.spring"></context:component-scan>

    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

      <!-- 配置数据源 -->

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
        <!-- 配置 JdbcTemplate -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
             <!-- 装配数据源 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>

(4)创建表

CREATE TABLE `t_book` (
`book_id` int ( 11 ) NOT NULL AUTO_INCREMENT COMMENT ' 主键 ' ,
`book_name` varchar ( 20 ) DEFAULT NULL COMMENT ' 图书名称 ' ,
`price` int ( 11 ) DEFAULT NULL COMMENT ' 价格 ' ,
`stock` int ( 10 ) unsigned DEFAULT NULL COMMENT ' 库存(无符号) ' ,
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT= 3 DEFAULT CHARSET=utf8;
insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values ( 1 , ' 斗破苍
' , 80 , 100 ),( 2 , ' 斗罗大陆 ' , 50 , 100 );
CREATE TABLE `t_user` (
`user_id` int ( 11 ) NOT NULL AUTO_INCREMENT COMMENT ' 主键 ' ,
`username` varchar ( 20 ) DEFAULT NULL COMMENT ' 用户名 ' ,
`balance` int ( 10 ) unsigned DEFAULT NULL COMMENT ' 余额(无符号) ' ,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT= 2 DEFAULT CHARSET=utf8;
insert into `t_user`(`user_id`,`username`,`balance`) values ( 1 , 'admin' , 50 );

 (5)创建组件   创建三层架构  

控制层:controller.BookController
持久层:dao.BookDao    dao.impl.BookDaoImpl  
业务层:service.BookService     service.impl.BookServiceImpl

控制层:controller.BookController

package com.atguigu.spring.controller;

import org.springframework.stereotype.Controller;

@Controller
public class BookController {
    
}
 

 持久层:dao.BookDao         dao .impl.BookDaoImpl

dao.BookDao 

package com.atguigu.spring.dao;

public interface BookDao {

}
 

 dao .impl.BookDaoImpl

package com.atguigu.spring.dao.impl;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl {
    
}

 业务层:service.BookService           service.impl.BookServiceImpl

 service.BookService

package com.atguigu.spring.service;

public interface BookService {
    
    
}
 

  service.impl.BookServiceImpl

package com.atguigu.spring.service.impl;

import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl {


}
 

(6) 添加扫描组件

    <!--扫描组件-->
    <context:component-scan base-package="com.atguigu.spring"></context:component-scan>

 

 (7) 添加自动装配@Autowired

package com.atguigu.spring.controller;

import com.atguigu.spring.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;
    
}

package com.atguigu.spring.service.impl;

import com.atguigu.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl {

    @Autowired
    private BookDao bookDao;


}

 

(8) 写buyBook方法   买书的方法  

package com.atguigu.spring.controller;

import com.atguigu.spring.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;


    public void buyBook(Integer userId, Integer bookId){
        bookService.buyBook(userId, bookId);
    }

}

(9) 在BookService里面创建买书的方法 

 

package com.atguigu.spring.service;

public interface BookService {

    /**
     * 买书
     * @param userId
     * @param bookId
     */
    void buyBook(Integer userId, Integer bookId);
}
 

(10) 找到实现类BookServiceImpl对买书的方法进行重写 

         

 

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    public void buyBook(Integer userId, Integer bookId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

 (11)创建getPriceByBookId   updateStock    updateBalance方法

package com.atguigu.spring.dao;

public interface BookDao {

    /**
     * 根据图书的id查询图书的价格
     * @param bookId
     * @return
     */
    Integer getPriceByBookId(Integer bookId);

    /**
     * 更新图书的库存
     * @param bookId
     */
    void updateStock(Integer bookId);

    /**
     * 更新用户的余额
     * @param userId
     * @param price
     */
    void updateBalance(Integer userId, Integer price);
}

 

(12)  找到实现类BookDaoImpl      对方法进行重写

 

(13)创建jdbcTemplate

在配置文件中配置了JdbcTemplate  测试事务功能的时候就是 使用JdbcTemplate执行sql语句 的过程中来测试的

package com.atguigu.spring.dao.impl;

@Repository
public class BookDaoImpl  implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Integer getPriceByBookId(Integer bookId) {
        return null;
    }

    @Override
    public void updateStock(Integer bookId) {

    }

    @Override
    public void updateBalance(Integer userId, Integer price) {

    }
}

 JdbcTemplate在iOC容器里面有 因为 当前配置文件已经配置了一个bean  所以我们这里可以自动装配@Autowired

(14)方法重写

package com.atguigu.spring.dao.impl;


import com.atguigu.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl  implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
     //根据图书的id查询图书的价格
    @Override
    public Integer getPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
    }
    
     //更新图书的库存
    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    //更新用户的余额
    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}

二  .  测试无事务情况

让用户id为1的用户  去购买图书id为1的图书

可以看到库存够  余额不够  书的价格是80  余额只有50  

 创建测试类TxByAnnotationTest

package com.atguigu.spring.test;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        
        bookController.buyBook(1, 1);

    }

}
报错

Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`ssm`.`t_user`.`balance` - 80)'

UNSIGNED:无符号  

结果超出范围

 

我们当前是没有事物的情况

在mysql默认的情况下 我们当前的一个sql语句 独占一个事物 并且自动提交  

所以在当前没有设置事物有情况下  我们的三个sql 都是独占一个事物 并且自动提交

所以我们在实现事物的过程中   一定要把事物的自动提交给关闭掉   因为如果不关闭 每一个sql语句是独占一个事物的  

总结:在mysql 默认的情况下  一个sql语句 独占一个事物 并且自动提交  

观察数据库 发现更新图书的库存可以成功   更新用户的余额不成功  

原来

现在

 

 

三  .  加入事务

①添加事务配置

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

    <!--扫描组件-->
    <context:component-scan base-package="com.atguigu.spring"></context:component-scan>

    <!--引入jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

   <!--
   开启事务的注解驱动
      通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
      ->
     <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
     是这个默认值,则可以省略这个属性 -->
     
    <!--
        开启事务的注解驱动
        将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
        transaction-manager属性设置事务管理器的id
        若事务管理器的bean的id默认为transactionManager,则该属性可以不写
    -->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

 

 拓展:注意:导入的名称空间需要 tx 结尾的那个。

②添加事务注解

因为 service 层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在 service 层处理 在BookServiceImpl buybook() 添加注解 @Transactional
package com.atguigu.spring.service.impl;

import com.atguigu.spring.dao.BookDao;
import com.atguigu.spring.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional  //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

现在我们在t_book表里面的库存由98改成100

原来

 

 改成100

 ③观察结果

由于使用了Spring的声明式事务,更新库存和更新余额都没有执行

 从新执行  

package com.atguigu.spring.test;
/**
 * 声明式事务的配置步骤:
 * 1、在Spring的配置文件中配置事务管理器
 * 2、开启事务的注解驱动
 * 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
 * @Transactional注解标识的位置:
 * 1、标识在方法上
 * 2、标识在类上,则类中所有的方法都会被事务管理
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){

        bookController.buyBook(1, 1);

    }

}

 总结:

/**
 * 声明式事务的配置步骤:
 * 1、在Spring的配置文件中配置事务管理器
 * 2、开启事务的注解驱动
 * 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
 * @Transactional注解标识的位置:
 * 1、标识在方法上
 * 2、标识在类上,则类中所有的方法都会被事务管理
 */

@Transactional注解标识的位置

@Transactional 标识在方法上,咋只会影响该方法
@Transactional 标识的类上,咋会影响类中所有的方法

、事务属性之只读

①介绍

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

②使用方式

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional(readOnly = true)   //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){

        bookController.buyBook(1, 1);

    }

}

③注意

对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification
are not allowed

报错:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed 

我们当前的连接是只读的  我们的数据修改是不被允许的

只有当前的事物全部是查询操作的时候才可以用只读

 

五.  事务属性之超时 

①介绍

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间 占用资源,大概率是因为程序运行出现了问题(可能是Java 程序或 MySQL 数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。

②使用方式

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional(

            //readOnly = true
            timeout = 3
    )   //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {

        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
如果在规定的时间内  事物没有执行完  那就可强制回滚并抛出异常
报错:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Nov 08 22:19:55 CST 2022
事物真是异常  

六.  事务属性之回滚策略

①介绍

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过 @Transactional 中相关属性设置回滚策略
rollbackFor 属性:需要设置一个 Class 类型的对象
rollbackForClassName 属性:需要设置一个字符串类型的全类名
noRollbackFor 属性:需要设置一个 Class 类型的对象
rollbackFor 属性:需要设置一个字符串类型的全类名

②使用方式


@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional(

            //readOnly = true
            //timeout = 3


    )   //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {

     /*   try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
        System.out.println(1 / 0);  //数学运算异常   运行时异常


    }
}

原来

 现在

 结果:

 



@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional(

            //readOnly = true
            //timeout = 3
            noRollbackFor = ArithmeticException.class  //不造成回滚


    )   //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {

     /*   try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
        System.out.println(1/0);  //数学运算异常   运行时异常


    }
}

③观察结果

虽然购买图书功能中出现了数学运算异常( ArithmeticException ),但是我们设置的回滚策略是,当 出现ArithmeticException 不发生回滚,因此购买图书的操作正常执行

 

七.  事务属性之事务隔离级别

①介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事 务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同 的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
读未提交: READ UNCOMMITTED
允许 Transaction01 读取 Transaction02 未提交的修改。
读已提交: READ COMMITTED
要求 Transaction01 只能读取 Transaction02 已提交的修改。
可重复读: REPEATABLE READ
确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它
事务对这个字段进行更新。
串行化: SERIALIZABLE
确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其它
事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

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

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

②使用方式



@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;


    @Override
    @Transactional(

            //readOnly = true
            //timeout = 3
           // noRollbackFor = ArithmeticException.class    不造成回滚
            // noRollbackForClassName = "java.lang.ArithmeticException"  //不造成回滚
            isolation = Isolation.DEFAULT


    )   //使用事务进行管理
    public void buyBook(Integer userId, Integer bookId) {

     /*   try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
        System.out.println(1/0);  //数学运算异常   运行时异常


    }
}

@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

 

八.  事务属性之事务传播行为

①介绍

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

②测试

设置mysql

 

 创建接口CheckoutService

public interface CheckoutService {

    /**
     * 结账
     * @param userId
     * @param bookIds
     */
    void checkout(Integer userId, Integer[] bookIds);
}
创建实现类 CheckoutServiceImpl

@Service
public class CheckoutServiceImpl implements CheckoutService {

    @Autowired
    private BookService bookService;

    @Override
    //@Transactional
    public void checkout(Integer userId, Integer[] bookIds) {
        for (Integer bookId : bookIds) {
            bookService.buyBook(userId, bookId);
        }
    }
}
BookController 中添加方法:
@Controller
public class BookController {

    @Autowired
    private BookService bookService;
    @Autowired
    private CheckoutService checkoutService;

    public void buyBook(Integer userId, Integer bookId){
        bookService.buyBook(userId, bookId);
    }

    public void checkout(Integer userId, Integer[] bookIds){
        checkoutService.checkout(userId, bookIds);
    }

}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest {

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){

       // bookController.buyBook(1, 1);
        bookController.checkout(1, new Integer[]{1,2});

    }

}

在数据库中将用户的余额修改为 100

③观察结果

可以通过 @Transactional 中的 propagation 属性设置事务传播行为
修改 BookServiceImpl buyBook() 上,注解 @Transactional propagation 属性
@Transactional(propagation = Propagation.REQUIRED) ,默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook() checkout() 中被调 用,checkout() 上有事务注解,因此在此事务中执行。所购买的两本图书的价格为 80 50 ,而用户的余 额为100 ,因此在购买第二本图书时余额不足失败,导致整个 checkout() 回滚,即只要有一本书买不 了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW) ,表示不管当前线程上是否有已经开启 的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook() 的事务中执行,因此第一本图 书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook() 中回滚,购买第一本图书不受 影响,即能买几本就买几本

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值