spring之事务概述


前言

1、什么是事务
2、事务的四个处理过程
3、事务的四个特性
4、事务使用场景
5、spring实现事务的两种方式
6、事务的传播行为
7、事务的隔离级别

一、事务概述

1、什么是事务

  • 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成果,或者同时失败,这样才能保证数据的安全。
  • 多条DML要么同时成功,要么同时失败,这叫做事务。
  • 事务:Transaction

2、事务的四个处理过程

  • 第一步:开启事务 start transaction
  • 第二步:执行核心业务代码
  • 第三步:提交事务(如果核心业务处理过程中没有出现异常)commit transaction
  • 第四步:回滚事务(如果核心业务处理过程中出现异常)rollback transaction

3、事务的四个特性

  • 原子性:事务是最小的工作单元,不可再分
  • 一致性:事务要求要么同时成功要么同时失败。
  • 隔离性:事务和事务之间因为有隔离性,才能保证互不干扰
  • 持久性:持久性是事务结束的标志

二、引入事务场景

以银行账户转账为例学习事务。两个账户act-001和act-002,act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。(一个减成功,一个加成功,这两个update语句必须同时成功或失败)

1、引入依赖

1、spring-context依赖
2、spring-jdbc
3、mysql驱动 mysql-connector-java
4、德鲁伊连接池 druid
5、@Resource注解 lombok
6、单元测试

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.32</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.20</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
    </dependency>
  </dependencies>

2、数据库创建

在这里插入图片描述
在这里插入图片描述

3、建包

com.powernode.bank.dao

AccountDao 专门负责账户信息的CRUD(增删改查)操作
DAO中只执行SQL语句,没有任何业务逻辑。
也就是说DAO不和业务挂钩

package com.powernode.bank.dao;

import com.powernode.bank.pojo.Account;

public interface AccountDao {
    /**
     * 根据账号查询账户信息
     * @param actno
     * @return
     */
    Account selectByActno(String actno);

    /**
     * 更新账户信息
     * @param act
     * @return
     */
    int update(Account act);
}

com.powernode.bank.dao.impl

AccountDaoImpl

package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActno(String actno) {
        String sql = "select actno,balance from t_act where actno = ?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int update(Account act) {
        String sql = "update t_act set balance = ? where actno = ?";
        int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
        return count;
    }
}

com.powernode.bank.pojo

Account

package com.powernode.bank.pojo;

public class Account {
    private String actno;
    private double balance;

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public double getBalance() {
        return balance;
    }

    public Account() {
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
}

com.powernode.bank.service

AccountService 业务接口
事务就是在这个接口下控制的

package com.powernode.bank.service;

/**
 * 业务接口
 * 事务就是在这个接口下控制的
 */
public interface AccountService {

    /**
     * 转账业务方法
     * @param fromActno 从这个账户转出
     * @param toActno 转入这个账户
     * @param money 转账金额
     */
    void transfer(String fromActno,String toActno,double money);
}

com.powernode.bank.service.impl

AccountServiceImpl

package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;

import javax.annotation.Resource;
@Repository("accountService")
public class AccountServicecImpl implements AccountService {

    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事务,因为在这个方法中要完成所有的转账业务
    @Override
    public void transfer(String fromActno, String toActno, double money) {
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if(fromAct.getBalance() < money){
            throw new RuntimeException("余额不足!!!");
        }
        //余额充足:
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);

        //数据库更新
        int update = accountDao.update(fromAct);
        update += accountDao.update(toAct);

        if(update!=2){
            throw new RuntimeException("转账失败,联系银行");
        }

    }
}

4、spring.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"
       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">

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

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/dududu"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--配置JDBCTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

5、测试程序

package com.powernode;

import com.powernode.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTxTest {
    @Test
    public void testSpringTx(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = (AccountService) ac.getBean("accountService");
        try {
            accountService.transfer("act_001","act_002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("转账失败");
        }
    }
}

6、运行结果(成功)

在这里插入图片描述
在这里插入图片描述

7、模拟异常

在两条update语句之间模拟异常

 //数据库更新
        int update = accountDao.update(fromAct);

        //模拟异常
        String s = null;
        s.toString();

        update += accountDao.update(toAct);

当前数据库中的数据:

在这里插入图片描述
执行测试程序【5】:

在这里插入图片描述
在这里插入图片描述
丢了一万块钱

事务管理:

@Repository("accountService")
public class AccountServicecImpl implements AccountService {

    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事务,因为在这个方法中要完成所有的转账业务
    @Override
    public void transfer(String fromActno, String toActno, double money) {
//1、开启事务


//2、执行核心代码
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if(fromAct.getBalance() < money){
            throw new RuntimeException("余额不足!!!");
        }
        //余额充足:
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);

        //数据库更新
        int update = accountDao.update(fromAct);
        update += accountDao.update(toAct);

        if(update!=2){
            throw new RuntimeException("转账失败,联系银行");
        }
//3、如果执行业务流程过程中,没有异常提交事务

//4、如果执行业务流程过程中,遇到异常则回滚事务
    }
}

三、Spring对事务的支持

1、Spring实现事务的两种方式

  • 编程式事务:通过编写代码的方式来实现事务的管理
  • 声明式事务:1、基于注解方式 2、基于XML配置方式

2、Spring事务管理API

spring 对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以spring专门针对事务开发了一套API,核心接口如下:
在这里插入图片描述
PlatformTransactionManager接口:spring事务管理器核心接口:

DataSourceTransactionManager:支持JdbcTemplate、Mybatis、Hibernate等事务管理
JtaTransaction
如果要在spring中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务

3、解决上述代码没有事务的问题

spring声明式事务之使用注解方式:

3.1配置spring.xml

1、配置事务管理器
管理事务需要将connection.setAutocommit(false)–>需要Connection对象—>需要连接对象—>需要数据源

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

2、添加命名空间

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

3、开启事务注解驱动器
告诉spring框架,采用注解的方式去控制事务

<!--事务注解驱动器-->
    <tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>

3.2使用事务

在业务实现类上/方法 添加注解@Transactional即可

写在类上:代表这个类中所有的方法都应用事务
写在方法上:只是针对这个方法应用事务

在这里插入图片描述
重新执行测试程序(空指针异常):
在这里插入图片描述
数据库中的数据(一分钱没丢):
在这里插入图片描述
假设没有异常,删掉空指针异常的代码,运行结果:
在这里插入图片描述
数据库中的数据:

在这里插入图片描述

四、事务之传播行为

@Transactional的属性:
在这里插入图片描述
较为重要的:传播行为(propagation)、隔离级别(isolation)

什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?

事务传播行为在spring框架中被定义为枚举类型
在这里插入图片描述

七种传播行为

1、REQUIRED:支持当前事务,如果不存在就新建一个【没有就新建,有就加入】
2、SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
3、MANDATORY:必须运行在一个事务中,如果当前没有事务发生,将抛出一个异常【有就加入,没有就抛出异常】
4、REQUIRES_NEW:开启一个新的事物,如果一个事务已经存在,则将这个事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
5、NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
6、NEVER:以非事务方式运行,如果事务存在,抛出异常【不支持事务,存在就抛异常】
7、NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样【有事务的话,就在这个事务里嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样】

不设置的话默认是REQUIRED

五、事务之隔离级别

事务隔离级别类似于教室A和教室B之间的一道墙,隔离级别越高表示墙体越厚,隔音效果越好。

数据库中读取数据存在的三大问题:(三大读问题)【重要】

  • 脏读:读取到没有提交到数据库中的数据,叫做脏读
  • 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样
  • 幻读:读到的数据是假的

事务隔离级别包括四个级别:【重要】
在这里插入图片描述

  • 读未提交:READ_UNCOMMITTED

这种隔离级别,存在脏读问题

  • 读提交:READ_COMMITTED

解决了脏读问题,其他事务提交之后才能读到,但存在不可重复读问题

  • 可重复读:REPEATABLE_READ

解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题

  • 序列化:SERIALIZABLE

解决了幻读问题,事务排队执行。不支持并发

在这里插入图片描述

如果是Oracle数据库 默认是READ_COMMITTED
如果是MySQl数据库 默认是REPEATABLE_READ


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值