木木的Java知识整理——事务管理

本文详细介绍了数据库事务的基本概念,如原子性、一致性和隔离性,重点讲解了MySQL的事务处理,包括基本语句、并发问题及解决办法。接着探讨了JDBC事务处理,包括设置自动提交、回滚和提交事务。最后深入讨论了Spring事务管理,涵盖了基本概念、编程式和声明式事务处理,以及Spring事务的隔离级别和传播行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

1.1 事务概念

1、概念:事务一般特指数据库事务,是指作为一个程序执行单元执行的一系列操作,要么完全执行,要么完全不执行。
2、事务的特性:
①原子性(atomicity):一个事务是一个不可分割的工作单位
②一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态
③隔离性(isolation):一个事务的执行不能被其他事物干扰
④持久性(durability):一个事务一旦提交,他对数据库中数据的改变应该是永久性的

二、MySQL事务处理

1.1 基本语句

MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。
MySQL默认以自动提交(autocommit)模式运行。

语句:

  • BEGIN(STARTTRANSACTION) 显式地开启一个事务
  • COMMIT 提交事务,并使用对数据库进行的所有修改变为永久性的
  • ROLLBACK 回滚事务,并撤销正在进行的所有未提交的修改

1.2 并发问题

1.2.1 脏读

在这里插入图片描述
脏读:读取了脏数据,即读取了不合理的、错误的或者本来不存在的数据;
解决方法:通过限制只能读取永久性的数据

脏读:读取数据库中永久的数据

1.2.2 不可重复读

在这里插入图片描述
不可重复读:在事务执行的过程中重复读取的同一项数据,但是读取到的结果是不一样的(由于事务B修改对事务A造成了影响)
解决办法:通过锁行解决

不可重复读:锁行

1.2.3 幻读

在这里插入图片描述
**幻读:**由于事务B插入对事务A造成了影响(如事务A将所有商品的库存量改为0,交叉执行的事务B又插入了库存为100的新商品,此时事务A再次查询修改结果发现仍有一个库存量为100的商品)
**解决办法:**通过锁表解决

幻读:锁表

1.3 事务隔离级别

MySQL事务隔离级别(从上到下执行效率越来越低,隔离级别越来越高)

  • 读未提交(read - uncommitted) 不能解决脏读、不可重复读、幻读等问题
  • 读已提交(read - commited)解决了脏读问题,不能解决不可重复读和幻读等问题
  • 可重复读(repetable-read)解决了脏读和不可重复读的问题,没有解决幻读的问题
  • 串行化(serializable)解决了脏读、不可重复读、和幻读的问题

语句

  • select @@tx_isolation 查询默认隔离级别
  • set session transaction isolation level xxx 设置当前会话隔离级别

三、JDBC事务处理

1.1 基本语句

JDBC事务处理:

  • Connection接口是JDBC进行事务处理的核心,必须是基于一个Connection对象的多个sql操作才能封装成一个事务,不同的Connection对象没办法封装成一个事务;
  • JDBC默认事务处理行为是自动提交;

事务相关方法:

  • setAutoCommit 设置自动提交;
  • rollback 回滚事务;
  • commit 提交事务;
public class OrderTest {
    private String driver = "com.mysql.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3307/os?useUnicode=true&characterEncoding=utf8";
    private String userName = "root";
    private String password = "root";

    @Test
    public void addOrder(){
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url,userName,password);
            connection.setAutoCommit(false);
            Statement statement = connection.createStatement();
            statement.execute("insert into orders values('100002','100001',2,2499,now(),null,null,'刘备','1330000000','成都','待发货')");
            statement.execute("update products set stck=stock-2 where id='100001'");
            statement.close();
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

将两句sql封装成一个事务,当前案例中的第二句sql错误,事务回滚,结果为第一句sql也执行失败。要么两句同时成功,要么同时失败。

1.2 事务隔离级别

隔离级别

  • TRANSACTION_NONE(不支持事务) 0;
  • TRANSACTION_READ_UNCOMMITTED(读未提交)1;
  • TRANSACTION_READ_COMMITTED(读已提交)2;
  • TRANSACTION_REPEATABLE_READ(可重复读)4;
  • TRANSACTION_SERIALIZABLE (串行化)8;

事务隔离级别设置

  • getTransactionIsolation 获取当前隔离级别
  • setTransactionIsolation 设置隔离级别 默认为4

四、Spring事务处理

1.1 基本概念

1.1.1 事务处理API

在这里插入图片描述

1.1.2 SpringTransationDefinition接口

隔离级别

  • ISOLATION_DEFAULT(使用数据库默认)
  • ISOLATION_READ_UNCOMMITTED
  • ISOLATION_READ_COMMITTED
  • ISOLATION_REPEATABLE_READ
  • ISOLATION_SERIALIZABLE
名称解释
ISOLATION_DEFAULT-1这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED1这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED2保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ4这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。
ISOLATION_SERIALIZABLE8这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。

默认超时

  • TIMEOUT_DEFAULT(默认30秒)

1.1.3 Spring事务传播行为

PROPAGTION_REQUIRED

  • 支持当前事务,如果当前没有事务,就新建一个事务(最常用,Spring默认)。
//针对的是b,如果a有事务,则b加入a的事务
public class Propagtion {

    public void a(){
        //begin
        //步骤
        b();
        //commit
    }
    public void b(){
        //步骤
    }
}
//如果a没有事务,b新建一个事务
public class Propagtion {

    public void a(){
        //步骤
        b();
    }
    public void b(){
        //begin
        //步骤
        //commit
    }
}

PROPAGTION_SUPPORTS

  • 支持当前事务,如果当前没有事务,就以非事务方式执行。

如果a存在事务,则b加入a的事务;如果a没有事务,则b以非事务执行。

PROPAGTION_MANDATORY

  • 支持当前事务,如果当前没有事务,就抛出异常。

PROPAGTION_REQUIRES_NEW

  • 新建事务,如果当前存在事务,把当前事务挂起。

PROPAGTION_NOT_SUPPORTED

  • 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGTION_NEVER

  • 以非事务方式执行操作,如果当前存在事务,则抛出异常。

b必须以非事务方式执行,如果a没有事务,则正常执行;如果a存在事务,则抛出异常。

PROPAGTION_NESTED

  • 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,就新建一个事务。
名称解释
PROPAGATION_REQUIRED0支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。
PROPAGATION_SUPPORTS1支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY2支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW3新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED4以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER5以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED6如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

1.2 准备

1、 持久层:采用DAO模式,建立实体类和数据库表映射(ORM映射)。也就是哪个类对应哪个表,哪个属性对应哪个列。持久层的目的就是,完成对象数据和关系数据的转换。

2、业务层:采用事务脚本模式。将一个业务中所有的操作封装成一个方法,同时保证方法中所有的数据库更新操作,即保证同时成功或同时失败。避免部分成功部分失败引起的数据混乱操作。

3、表现层:采用MVC模式。全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写

M称为模型,也就是实体类。用于数据的封装和数据的传输。

V为视图,也就是GUI组件,用于数据的展示。

C为控制,也就是事件,用于流程的控制

业务功能是多步时,希望以事务的方式把它设置成为原子性
在业务层封装事务才是最合理的
spring封装事务在业务层做文章

配置事务管理器
介绍完Spring事务的管理的流程大概是怎么走的。接下来可以动手试试Spring是如何配置事务管理器的
例如我在spring-mybatis中配置的:

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

这配置不是唯一的,可以根据自己项目选择的数据访问框架灵活配置事务管理器

配置了事务管理器后,事务当然还是得我们自己去操作,Spring提供了两种事务管理的方式:编程式事务管理和声明式事务管理。

1.3 编程式事务处理

编程式事务管理我们可以通过PlatformTransactionManager实现来进行事务管理,同样的Spring也为我们提供了模板类TransactionTemplate进行事务管理,下面主要介绍模板类,我们需要在配置文件中配置

 <!--配置事务管理的模板-->
 <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
     <property name="transactionManager" ref="transactionManager"></property>
     <!--定义事务隔离级别,-1表示使用数据库默认级别-->
     <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
     <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
 </bean>

TransactionTemplate帮我们封装了许多代码,节省了我们的工作。下面我们写个单元测试来测测。
为了测试事务回滚,专门建了一张tbl_accont表,用于模拟存钱的一个场景。service层主要代码如下,后面会给出全部代码的github地址,有需要的朋友请移步查看。
BaseSeviceImpl

 //方便测试直接写的sql
 @Override
 public void insert(String sql, boolean flag) throws Exception {
     dao.insertSql(sql);
     // 如果flag 为 true ,抛出异常
     if (flag){
         throw new Exception("has exception!!!");
     }
 }
 //获取总金额
 @Override
 public Integer sum(){
     return dao.sum();
 }

dao对应的sum方法

<select id="sum" resultType="java.lang.Integer">
    SELECT SUM(money) FROM tbl_account;
</select>

下面看看测试代码

import com.gray.service.BaseSevice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private BaseSevice baseSevice;

    @Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try{
                    baseSevice.insert("INSERT INTO tbl_account VALUES (100);",false);
                    baseSevice.insert("INSERT INTO tbl_account VALUES (100);",false);
                } catch (Exception e){
                    //对于抛出Exception类型的异常且需要回滚时,需要捕获异常并通过调用status对象的setRollbackOnly()方法告知事务管理器当前事务需要回滚
                    status.setRollbackOnly();
                    e.printStackTrace();
                }
           }
        });
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
}

当baseSevice.insert的第二个参数为false时,我们假设插入数据没有出现任何问题;
当第二个参数为true时,insert会抛出一个异常,这是事务就应该回滚,数据前后不应该有变化。

1.4 声明式事务处理

声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解,随着Spring和Java的版本越来越高,大家越趋向于使用注解的方式,下面我们两个都说。
1.基于tx和aop命名空间的xml配置文件
配置文件

<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="insert" propagation="REQUIRED" read-only="false"  rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="pointCut" expression="execution (* com.gray.service.*.*(..))"/>
    <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
</aop:config>

测试代码

@Test
public void transTest() {
    System.out.println("before transaction");
    Integer sum1 = baseSevice.sum();
    System.out.println("before transaction sum: "+sum1);
    System.out.println("transaction....");
    try{
        baseSevice.insert("INSERT INTO tbl_account VALUES (100);",true);
    } catch (Exception e){
        e.printStackTrace();
    }
    System.out.println("after transaction");
    Integer sum2 = baseSevice.sum();
    System.out.println("after transaction sum: "+sum2);
}

2.基于@Transactional注解
这种方式最简单,也是最为常用的,只需要在配置文件中开启对注解事务管理的支持。

<!-- 声明式事务管理 配置事物的注解方式注入-->
<tx:annotation-driven transaction-manager="transactionManager"/>

然后在需要事务管理的地方加上@Transactional注解,如:

@Transactional(rollbackFor=Exception.class)
public void insert(String sql, boolean flag) throws Exception {
    dao.insertSql(sql);
    // 如果flag 为 true ,抛出异常
    if (flag){
        throw new Exception("has exception!!!");
    }
}

rollbackFor属性指定出现Exception异常的时候回滚,遇到检查性的异常需要回滚,默认情况下非检查性异常,包括error也会自动回滚。
测试代码和上面那个一样 。

部分参考:Spring事务管理详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值