Spring4---事务管理

什么是事物?

在这里插入图片描述

事物管理问题

在这里插入图片描述

Spring中的事务管理

在这里插入图片描述

事务管理器

在这里插入图片描述

示例:进行事务控制案例

  • 开发结构设计图

在这里插入图片描述

  • 使用到的数据表
drop table book ;
drop  table book_stock;
drop table account;
#use spring;
create table book
(
  isbn      int primary key,
  book_name varchar(100),
  price     double
);

create table book_stock
(
  isbn  int primary key,
  stock int,
  check (stock > 0)
);

create table account
(
  uid       int primary key,
  username varchar(50) ,
  balance  double,
  check (balance > 0)
);


  • 定义数据表传输类
  1. Book
public class Book {
    private Integer isban;
    private String bookName;
    private Double price;
    //getter和setter方法
}
  1. BookStock
public class BookStock {
    private Integer isbn;
    private Integer stock;
        //getter和setter方法
}
  1. Account
public class Account {
    private Integer uid;
    private String username;
    private String balance;
        //getter和setter方法
}
  • 定义DAO层接口—BookShop
package mao.shu.spring.jdbc.transaction.dao;

public interface BookShop {

    /**
     * 根据书的编号查询数据价格
     * @param isban
     * @return
     */
    public double findPriceByisban(Integer isban);

    /**
     * 更新书的库存
     * @param isbn
     * @param number
     * @return
     */
    public boolean updateBookStock(Integer isbn,Integer number);

    /**
     * 更新账户的余额
     * @param uid
     * @param consumption
     * @return
     */
    public boolean updateAccount(Integer uid,Double consumption);
}

  • 定义BookShopImpl实现BookShop接口
  • 使用Spring的JdbcTemplate操作对象完成
package mao.shu.spring.jdbc.transaction.dao.impl;

import mao.shu.spring.jdbc.transaction.dao.BookShop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookShopImpl implements BookShop {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public double findPriceByisban(Integer isbn) {
        String sql = "SELECT price FROM book WHERE isbn=?";
        Double price = this.jdbcTemplate.queryForObject(sql, new Object[]{isbn}, Double.class);
        return price;
    }

    @Override
    public boolean updateBookStock(Integer isbn, Integer number) {
        if (this.getStockByisbn(isbn) > 0) {
            String sql = "UPDATE book_stock SET stock=stock + ? WHERE isbn=?";
            return this.jdbcTemplate.update(sql, number, isbn) > 0;
        } else {
            try {
                throw new Exception("库存不足");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    @Override
    public boolean updateAccount(Integer uid, Double consumption) {
        if (this.getAccountBalance(uid)>=consumption){
            String sql = "UPDATE account SET balance=balance-? WHERE uid=?";
            return this.jdbcTemplate.update(sql,consumption,uid) > 0;
        }
        return false;
    }

    public Integer getStockByisbn(Integer isbn) {
        String sql = "SELECT stock FROM book_stock WHERE isbn=?";
        Integer stock = this.jdbcTemplate.queryForObject(sql, new Object[]{isbn}, Integer.class);
        return stock;
    }

    public Double getAccountBalance(Integer uid) {
        String sql = "SELECT balance FROM account WHERE uid=?";
        Double balance = this.jdbcTemplate.queryForObject(sql, new Object[]{uid}, Double.class);
        return balance;
    }
}

  • 定义测试类
package mao.shu.spring.jdbc.transaction.dao.impl;

import mao.shu.spring.jdbc.transaction.dao.BookShop;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class BookShopImplTest {
    private ApplicationContext app ;
    private BookShop bookShop;
    @Before
    public void before(){
        this.app = new ClassPathXmlApplicationContext("mao/shu/spring/jdbc/transaction/transaction.xml");
        this.bookShop = this.app.getBean("bookShopImpl",BookShopImpl.class);
    }
    @Test
    public void findPriceByisban() {
        Double price = this.bookShop.findPriceByisban(3);
        System.out.println(price);
    }

    @Test
    public void updateBookStock() {
        //this.bookShop.updateBookStock(1,-112);//抛出异常
        this.bookShop.updateBookStock(1,-1);//正常
    }

    @Test
    public void updateAccount() {
        this.bookShop.updateAccount(1,-1900.5);
    }

}
  • 定义一个ShopService接口
package mao.shu.spring.jdbc.transaction.vo;

public interface ShopService {

    /**
     * 账户消费操作
     * @param uid 账户id
     * @param isbn 购买的书本编号
     * @return 成功返回true,否则返回false;
     */
    public boolean purchase(Integer uid,Integer isbn);
}

  • 定义ShopServiceImpl类时心啊ShopService接口
package mao.shu.spring.jdbc.transaction.service;

import mao.shu.spring.jdbc.transaction.dao.BookShop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ShopServiceImpl implements ShopService  {
    @Autowired
    private BookShop bookShop;

    @Override
    public boolean purchase(Integer uid, Integer isbn) {
        double price = this.bookShop.findPriceByisban(isbn);
        if (this.bookShop.updateAccount(uid,price)){
            return this.bookShop.updateBookStock(isbn,-1);
        }
        return false;
    }
}

  • 定义测试类
package mao.shu.spring.jdbc.transaction.service;

import mao.shu.spring.jdbc.transaction.dao.BookShop;
import mao.shu.spring.jdbc.transaction.dao.impl.BookShopImpl;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class ShopServiceImplTest {
    private ApplicationContext app ;
    private ShopService shop;
    @Before
    public void before(){
        this.app = new ClassPathXmlApplicationContext("mao/shu/spring/jdbc/transaction/transaction.xml");
        this.shop = this.app.getBean("shopServiceImpl", ShopService.class);
    }
    @Test
    public void purchase() {
        this.shop.purchase(1,1);
    }
}
  • 当出现用余额不足的时候会出现以下的异常

在这里插入图片描述

  • 但是数据库书本的数量还是被减少了

在这里插入图片描述

使用事务控制

  • 声明事务控制的时候需要 aspectjweaver-1.5.2 的jar包支持
  1. xml文件的方式配置

在这里插入图片描述

在这里插入图片描述

用 @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">
    <aop:config proxy-target-class="true"></aop:config>
    <!--配置spring注解扫描的基类-->
    <context:component-scan base-package="mao.shu.spring.jdbc.transaction"/>
    <!--读取资源文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--配置c3p0链接数据库对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="driverClass" value="${driver}"/>
        <property name="jdbcUrl" value="${url}"/>


    </bean>

    <!--配置Spring的 Template-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置NamedParameterJdbcTemplate-->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>

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

    <!--启动事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    

</beans>
  • 在ShopServiceImpl 类中的purchase()方法上声明事务管理
@Transactional//声明事务管理
 @Override
 public boolean purchase(Integer uid, Integer isbn) {
     double price = this.bookShop.findPriceByisban(isbn);
     if (this.bookShop.updateAccount(uid,price)){
         return this.bookShop.updateBookStock(isbn,-1);
     }
     return false;
 }
  • 测试方法
package mao.shu.spring.jdbc.transaction.service;

import mao.shu.spring.jdbc.transaction.dao.BookShop;
import mao.shu.spring.jdbc.transaction.dao.impl.BookShopImpl;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class ShopServiceImplTest {
    private ApplicationContext app ;
    private ShopService shop;
    @Before
    public void before(){
        this.app = new ClassPathXmlApplicationContext("mao/shu/spring/jdbc/transaction/transaction.xml");
        this.shop = this.app.getBean("shopServiceImpl", ShopService.class);
    }
    @Test
    public void purchase() {
        this.shop.purchase(1,1);
    }
}
  • 此时由于账户余额不足抛出了异常

在这里插入图片描述

  • 但是数据库中的数据没有被破坏,事务控制成功

在这里插入图片描述

事务的传播属性

在这里插入图片描述

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

在这里插入图片描述

  • 事务传播属性取值

在这里插入图片描述

示例:定义事务传播属性

在这里插入图片描述

  • 定义Cashier接口
package mao.shu.spring.jdbc.transaction.service;

import java.util.List;

public interface Cashier {
    /**
     *  进行顾客结账操作,
     * @param uid 账户id
     * @param isbns 书本编号集合
     * @return
     */
    public boolean checkout(Integer uid, List<Integer> isbns);
}

  • 定义CashierImpl实现Cashier接口
@Controller
public class CashierImpl implements Cashier {
	@Autowired
    private ShopService shopService;
    //设置事务传播级别
    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public boolean checkout(Integer uid, List<Integer> isbns) {
        for (Integer isbn: isbns){
            if(!this.shopService.purchase(uid,isbn)){
                return false;
            }
        }
        return true;
    }
}
  • 测试checkout()方法,为账户设置购买两本书的余额,在使用checkout()方法的时候让账户购买2本书,观察数据是否会变化

在这里插入图片描述

  • 书的单价

在这里插入图片描述

  • 测试方法
 @Test
 public void checkout() {
     Cashier cashier = this.app.getBean("cashierImpl",Cashier.class);
     List<Integer> isbns = new ArrayList<>();
     isbns.add(1);
     isbns.add(1);
     isbns.add(1);
     cashier.checkout(1,isbns);
 }

在这里插入图片描述

  • 数据库中的数据没有发生变化,这是因为"REQUIRED"的事物传播属性,默认情况下多个方法公用一个事务控制,要么全部通过,要么全部失败

在这里插入图片描述

在这里插入图片描述

REQUIRES_NEW 传播行为

在这里插入图片描述

  • 示例:设置REQURES_NEW事务行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean purchase(Integer uid, Integer isbn) {
    double price = this.bookShop.findPriceByisban(isbn);
    if (this.bookShop.updateAccount(uid,price)){
        return this.bookShop.updateBookStock(isbn,-1);
    }
    return false;
}
  • 重置账户余额为100再次执行测试方法

在这里插入图片描述

  • 但此时数据库中的数据被减少了

在这里插入图片描述

事务的隔离级别

  • 并发事务所导致的问题

在这里插入图片描述

  • 使用事务隔离级别解决事务问题

在这里插入图片描述

Spring支持的事务隔离级别

在这里插入图片描述

在Spring4注解中设置隔离级别

  • 在使用@Transactional注解声明式管理事务时可以在注解中使用"isolattion"属性控制事务级别
//isolation属性控制事务级别,默认级别为
  @Transactional(propagation = Propagation.REQUIRES_NEW,
    isolation = Isolation.READ_COMMITTED)READ_COMMITTED
    @Override
    public boolean purchase(Integer uid, Integer isbn) {
        double price = this.bookShop.findPriceByisban(isbn);
        if (this.bookShop.updateAccount(uid,price)){
            return this.bookShop.updateBookStock(isbn,-1);
        }
        return false;
    }

在这里插入图片描述

回滚事务属性

在这里插入图片描述

  • 示例:设置回滚属性
//isolation属性控制事务级别,默认级别为READ_COMMITTED
//rollbackFor 属性控制事务回滚,
@Transactional(propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED,
rollbackFor = SQLException.class)
@Override
public boolean purchase(Integer uid, Integer isbn) {
    double price = this.bookShop.findPriceByisban(isbn);
    if (this.bookShop.updateAccount(uid,price)){
        return this.bookShop.updateBookStock(isbn,-1);
    }
    return false;
}

超时和只读属性

在这里插入图片描述

在这里插入图片描述

  • 示例:定义超时事务属性
    • 超时事务属性可以在"@Transactional"标签中使用timeOut 属性定义,以秒最为单位
    //isolation属性控制事务级别,默认级别为READ_COMMITTED
    //rollbackFor 属性控制事务回滚,
    //timeout 控制超时时间,当事务占用超过所设置的时间,则会进行强制回滚
    @Transactional(propagation = Propagation.REQUIRES_NEW,
    isolation = Isolation.READ_COMMITTED,
    rollbackFor = SQLException.class,
    timeout = 2)
    @Override
    public boolean purchase(Integer uid, Integer isbn) {
        try {//定义线程沉睡5秒,使事务超时,强制回滚
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        double price = this.bookShop.findPriceByisban(isbn);
        if (this.bookShop.updateAccount(uid,price)){
            return this.bookShop.updateBookStock(isbn,-1);
        }
        return false;
    }
  • 示例:定义只读事务属性
    @Transactional(readOnly = true)
    @Override
    public boolean purchase(Integer uid, Integer isbn) {
 
        double price = this.bookShop.findPriceByisban(isbn);
        if (this.bookShop.updateAccount(uid,price)){
            return this.bookShop.updateBookStock(isbn,-1);
        }
        return false;
    }
  • 如果设置了只读,但是缺使用了更新语句,会出现以下的异常

在这里插入图片描述

使用xml文件配置事务

  • 如果使用xml文件的方式注入类的实例,需要为类中的实例设置setter()方法
  • 使用xml文件的方式配置事务,步骤如下
    1. 配置事务管理器
    2. 配置事务属性:定义事务传播,隔离级别,回滚,只读等
    3. 配置事务切入点:以切入点表达式的方式指定哪些类中的方法需要事务控制
<?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:property-placeholder location="classpath:db.properties"/>
    <!--配置c3p0链接数据库对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="driverClass" value="${driver}"/>
        <property name="jdbcUrl" value="${url}"/>
    </bean>

    <!--配置Spring的 Template-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置NamedParameterJdbcTemplate-->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
    </bean>

    <!--配置DAOBean-->
    <bean id="bookShop" class="mao.shu.spring.jdbc.transactionXml.dao.impl.BookShopImpl">
        <property name="jdbcTemplate"  ref="jdbcTemplate"/>
    </bean>
    <!--配置Service的Bean-->
    <bean id="shopService" class="mao.shu.spring.jdbc.transactionXml.service.impl.ShopServiceImpl">
        <property name="bookShop" ref="bookShop"/>
    </bean>
    <!--配置Service的Bean-->
    <bean id="" class="mao.shu.spring.jdbc.transactionXml.service.impl.CashierImpl">
        <property name="shopService" ref="shopService"/>
    </bean>

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



    <!--配置事务属性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--根据方法名称指定事务属性-->
            <tx:method name="checkout" propagation="REQUIRED"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入点,并将事务切入点于事务属性连接起来-->
    <aop:config>
        <!--根据切入点表达式,指定要进行事务控制的方法有哪些
        第一个"*"表示所有的 修饰符和返回值
        第二个"*"表示service包下的所有类
        第三个"*"表示所有的方法
        (..) 表示所有参数类型的方法-->
        <aop:pointcut id="txPointcut" expression="execution(* mao.shu.spring.jdbc.transactionXml.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>
  • 测试
package mao.shu.spring.jdbc.transactionXml.service.impl;

import mao.shu.spring.jdbc.transactionXml.service.Cashier;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class CashierImplTest {
    private ApplicationContext app ;
    @Before
    public void before(){
        this.app = new ClassPathXmlApplicationContext("mao/shu/spring/jdbc/transactionXml/transaction-xml.xml");
    }
    @Test
    public void checkout() {
        Cashier cashier = this.app.getBean("cashier",Cashier.class);
        List<Integer> isbns = new ArrayList<>();
        isbns.add(1);
        isbns.add(1);
        isbns.add(1);
        cashier.checkout(1,isbns);
    }
}
  • 出现账户余额不足的异常

在这里插入图片描述

  • 并且数据库中的数据没有被破坏

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值