数据库事务 | JDBC中使用事务 | spring中的事务管理

本文详细介绍了事务的概念,包括ACID特性、隔离级别,并重点讲解了JDBC中如何控制事务,如设置隔离级别、回滚点。接着探讨了Spring事务管理的原理,包括TransactionDefinition中的事务属性,如传播行为、隔离级别等。同时,通过实例展示了Spring编程式和声明式事务的使用,以及事务回滚规则。最后讨论了Spring事务管理默认只对运行期异常进行回滚的情况。
摘要由CSDN通过智能技术生成

参考自:

https://baijiahao.baidu.com/s?id=1611918898724887602&wfr=spider&for=pc

https://blog.csdn.net/weixin_28760063/article/details/81369266

https://www.jb51.net/article/88229.htm

https://www.jb51.net/article/134469.htm

https://www.cnblogs.com/ysocean/p/7617620.html#_label5

https://blog.csdn.net/lee_sire/article/details/72904822

目录

事务的基本概念

什么是事务

事务的ACID特性

事务的隔离级别

JDBC中使用事务

事务控制语句

设置隔离级别

设置事务回滚点

JDBC中使用事务例子

spring中的事务管理

Spring事务原理

TransactionDefinition 基本事务属性的定义

传播行为

隔离级别

只读

事务超时

回滚规则

Spring 编程式事务和声明式事务的区别 

spring事务实例

不用事务实现转账

编程式事务处理实现转账(TransactionTemplate )

声明式事务处理实现转账(基于AOP的 xml 配置)  

声明式事务处理实现转账(基于AOP的 注解 配置) 

Spring的事务管理默认只对运行期异常进行回滚


 

事务的基本概念

什么是事务

        事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一个sql语句,一组sql语句或整个程序。

       事务的开始和结束可以由用户显式控制,如果用户没有显式的定义事务,则由数据库管理系统按照默认规定自动划分事务。例如:mysql数据库默认一条sql语句一个事务,默认会开启并提交事务.

       在SQL中,定义事务的语句一般有三条:

BEGIN TRANSACTION;

COMMIT;

ROLLBACK;

事务通常是以 BEGIN TRANSACTION开始,以COMMIT或ROLLBACK结束;COMMIT表示提交,即提交事务的所有操作,具体地说,就是将事务中所有对数据库的更新写回到磁盘上的物理数据库中,事务正常结束;ROLLBACK表示回滚,即在事务运行过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的更新操作全部撤销,回到事务开始时的状态。

事务的ACID特性

原子性(Atomicity)

   原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。

一致性(Consistency)

  事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。

隔离性(Isolation)

   一个事务的执行不能被其他事务干扰。事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

持久性(Durability)

  持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

  事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别

事务的隔离级别

多个线程开启各自的事务来操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

如果事务不考虑隔离性,可能会引发如下问题:

1、脏读

  脏读指一个事务读取了另外一个事务未提交的数据。 

         事务A访问了数据库,它干了一件事情,往数据库里加上了新来的牛人的名字,但是没有提交事务。

         insert into T values (4, '牛D');

         这时,来了另一个事务B,他要查询所有牛人的名字。

         select Name from T;

         这时,如果没有事务之间没有有效隔离,那么事务B返回的结果中就会出现“牛D”的名字。这就是“脏读(dirty read)”。

2、不可重复读

  不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。

       事务A访问了数据库,他要查看ID是1的牛人的名字,于是执行了

       select Name from T where ID = 1;

       这时,事务B来了,因为ID是1的牛人改名字了,所以要更新一下,然后提交了事务。

       update T set Name = '不牛' where ID = 1;

       接着,事务A还想再看看ID是1的牛人的名字,于是又执行了

       select Name from T where ID = 1;

       结果,两次读出来的ID是1的牛人名字竟然不相同,这就是不可重复读(unrepeatable read)。

3、虚读(幻读)

  虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致

       事务A访问了数据库,他想要看看数据库的牛人都有哪些,于是执行了

       select * from T;

       这时候,事务B来了,往数据库加入了一个新的牛人。

       insert into T values(4, '牛D');

       这时候,事务A忘了刚才的牛人都有哪些了,于是又执行了。

       select * from T;

       结果,第一次有三个牛人,第二次有四个牛人。

       相信这个时候事务A就蒙了,刚才发生了什么?这种情况就叫“幻读(phantom problem)”。

为了防止出现脏读、不可重复读、幻读等情况,我们就需要根据我们的实际需求来设置数据库的隔离级别。

事务隔离级别

  1. Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
  2. Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
  3. Read committed(读已提交):可避免脏读情况发生。
  4. Read uncommitted(读未提交):最低级别,以上情况均无法保证。

mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)

oracle数据库默认的事务隔离级别是:read commited(读已提交)

查看数据库默认的隔离级别:select @@tx_isolation; 
设置隔离级别:set session transaction isolation level

JDBC中使用事务

事务控制语句

 当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列的JDBC控制事务语句.

  • Connection.setAutoCommit(false);     //开启事务(start transaction)
  • Connection.rollback();                         //回滚事务(rollback)
  • Connection.commit();                          //提交事务(commit)

设置隔离级别

Connection.setTransactionIsolation(int level) :参数可选值如下:

  • Connection.TRANSACTION_READ_UNCOMMITTED;  //读未提交
  • Connection.TRANSACTION_READ_COMMITTED;       //读已提交
  • Connection.TRANSACTION_REPEATABLE_READ;      //可重复读
  • Connection.TRANSACTION_READ_SERIALIZABLE。    //序列化

设置事务回滚点

  在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点:

  Savepoint sp = connection.setSavepoint();
  connection.rollback(sp);
  connection.commit();                                      //回滚后必须通知数据库提交事务

JDBC中使用事务例子

这是单元测试类,所以有@Before   @Test

package com.test.transaction.tset;
import org.junit.Before;
import org.junit.Test;
import java.sql.*;


public class TransactionTest {

    private Connection con = null;
    private Statement stmt = null;
    private ResultSet rs = null;
    private String url = "jdbc:mysql://localhost:3306/transaction?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC";
    private String username = "root" ;
    private String password = "123456789" ;
    private String sql1 = "update account set money = money+100 where name='B'";
    private String sql2 = "update account set money = money-100 where name='A'";

  

    /**
     * @Description:连接数据库
     */

    @Before
    public void connect(){
        try{
            //加载MySql的驱动类
            Class.forName("com.mysql.cj.jdbc.Driver") ;
        }catch(ClassNotFoundException e){
            System.out.println("找不到驱动程序类 ,加载驱动失败!");
            e.printStackTrace() ;
        }

        try{
            con = DriverManager.getConnection(url, username, password ) ;
            stmt = con.createStatement() ;
        }catch(SQLException se){
            System.out.println("数据库连接失败!");
            se.printStackTrace() ;
        }
    }


     /**
     * @Description:模拟转账成功时的业务场景
     */

    @Test
    public  void testTransaction1(){
        try {
            //通知数据库开启事务(start transaction)
            con.setAutoCommit(false);
            stmt.execute(sql1);
            stmt.execute(sql2);
            //提交事务
            con.commit();
            System.out.println("success");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                con.close();
            } catch (SQLException e) {
                System.out.println("数据库连接关闭失败!");
                e.printStackTrace();
            }
        }
    }

     /**
     * @Description:模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
     */

    @Test
    public  void testTransaction2(){
        try {
            //通知数据库开启事务(start transaction)
            con.setAutoCommit(false);
            stmt.execute(sql1);
            //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
            int x = 1 / 0;
            stmt.execute(sql2);
            //提交事务
            con.commit();
            System.out.println("success");
        } catch (Exception e) {
            e.printStackTrace(); 
        }finally {
            try {
                con.close();
            } catch (SQLException e) {
                System.out.println("数据库连接关闭失败!");
                e.printStackTrace();
            }
        }
    }

     /**
     * @Description:模拟转账过程中出现异常导致有一部分SQL执行失败时手动通知数据库回滚事务
     */

    @Test
    public void testTransaction3(){
        try {
            //通知数据库开启事务(start transaction)
            con.setAutoCommit(false);
            stmt.execute(sql1);
            //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
            int x = 1 / 0;
            stmt.execute(sql2);
            //提交事务
            con.commit();
            System.out.println("success");
        } catch (SQLException e) {
            try {
                //捕获到异常之后通知数据库回滚
                con.rollback();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            try {
                con.close();
            } catch (SQLException e) {
                System.out.println("数据库连接关闭失败!");
                e.printStackTrace();
            }
        }
    }

    /**
     * @Description:设置事务回滚点
     */

    @Test
    public void testTransaction4(){
        Savepoint sp = null;
        String sql3 = "update account set money = money + 100 where name='C'";
        try {
            //通知数据库开启事务(start transaction)
            con.setAutoCommit(false);

            stmt.execute(sql1);
            //stmt.execute(sql2);

            sp = con.setSavepoint();

            stmt.execute(sql2);
            //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作

            int x = 1 / 0;

            stmt.execute(sql3);
            //提交事务
            con.commit();
            System.out.println("success");
        } catch (Exception e) {
            try {
                //捕获到异常之后通知数据库回滚
                con.rollback(sp);
                con.commit();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            try {
                con.close();
            } catch (SQLException e) {
                System.out.println("数据库连接关闭失败!");
                e.printStackTrace();
            }
        }
    }


}

spring中的事务管理

Spring事务原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
 1.获取连接 Connection con = DriverManager.getConnection()
 2.开启事务con.setAutoCommit(false);
 3.执行CRUD
 4.提交事务/回滚事务 con.commit() / con.rollback();
 5.关闭连接 con.close(); 

使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。
那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子
 1.配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
 2.spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
 3.真正的数据库层的事务提交和回滚是通过bin log或者redo log实现的。

TransactionDefinition 基本事务属性的定义

什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:

  

 

传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值