数据库---事务

事务简介

事物是指逻辑上的一组操作,组成这组操作的各个单元,要么全部执行成功,要么全部实行失败
例如:张三–李四转账

-- 事物
CREATE TABLE account(
    id INT PRIMARY KEY auto_increment,
      `name` VARCHAR(10),
    money DOUBLE
);
INSERT INTO account(name,money) VALUES('张三',1000),('李四',1000);
-- 张三给李四转账1000块钱
UPDATE account SET money=money-1000 WHERE name='张三';
UPDATE account SET money=money+1000 WHERE name='李四';

数据库开启事务的命令

Connection接口下面的方法:

void setAutoCommit(boolean autoCommit)

autoCommit - 为 true 表示启用自动提交模式;为 false 表示禁用自动提交模式(默认情况下就是true

void commit();

手动提交事物。

void rollback();

回滚(出现异常的时候,所有已经执行成功的代码需要回退到事务开始前的状态)

默认情况下,Connetion对象处于自动提交模式下,这意味着他在执行每个语句后都会自动提交更改。
如果禁用自动提交模式,那么提交更改就必须显示调用commit方法,否则就无法保存数据库的更改。

结束事务

COMMIT 或 ROLLBACK
COMMIT 表示提交,即事务中的多条 SQL 语句所更改的数据会持久化到数据库中。或者 ROLLBACK 表示回滚,即回滚到事务的起点,将之前所做的所有操作撤销。
ROLLBACK 可以结束事务,但不代表会将数据持久化到数据库中,而只有 COMMIT提交才可以将数据持久化到数据库中。

书写结构

private Connection conn = null;
private PreparedStatement ps = null;
try {
    conn.setAutoCommit(false); //将自动提交设置为false
    ps.executeUpdate("修改SQL"); //执行修改操作
    ps.executeQuery("查询SQL"); //执行查询操作
    conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
    conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功
    e.printStackTrace();
}

实例

public void test1() {
   Connection connection = null;
   PreparedStatement preparedStatement = null;
   String sql1 = "UPDATE account SET money=money-1000 WHERE name='张三'";
   String sql2 = "UPDATE account SET money=money+1000 WHERE name='李四'";
   try {
      connection = JDBCUtil.getConnection();
      
      // 为false,表示禁用自动提交(默认情况是true)
      connection.setAutoCommit(false);
      
      preparedStatement = connection.prepareStatement(sql1);
      System.out.println(preparedStatement);
      preparedStatement.executeUpdate();
      
      // ArithmeticException: / by zero
      int i = 3 / 0;
      
      preparedStatement = connection.prepareStatement(sql2);
      System.out.println(preparedStatement);
      preparedStatement.executeUpdate();
      
      // setAutoCommit(false)改成false之后,不会提交数据库,只有调用connection.commit()才提交
      connection.commit();
   } catch (SQLException e) {
      e.printStackTrace();
      try {
         connection.rollback();
      } catch (SQLException e1) {
         e1.printStackTrace();
      }
   } finally {
      JDBCUtil.close(connection, preparedStatement, null);
   }
   
}

*事务四个基本特征或 ACID 特性:

ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction tx)是正确可靠的,所必须具备的四个特性:
原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。

在数据库系统中,一个事务是指:由一系列数据库操作组成的一个完整的逻辑过程。例如银行转帐,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和,构成一个完整的逻辑过程,不可拆分。这个过程被称为一个事务,具有ACID特性。

1、Atomicity(原子性)一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
2、Consistency(一致性)在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
3、Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
4、Durability(持久性)事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

*Spring的事务传播特性(属性、机制)

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为(加粗的三个最重要):

PROPAGATION_REQUIRED:需要, 如果存在一个事务,则支持当前事务, 如果没有事务则开启(原来有就使用原来事物,没有就创建事物,一般Service的中的方法里面有更新的操作操作)

PROPAGATION_SUPPORTS:支持, 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行(原来有就使用原来事物,没有就算了,一般Service的中的方法里面都是查询操作)。

PROPAGATION_MANDATORY: 必要的 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_NEVER:绝不 总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED:嵌套的 如果有就嵌套、没有就开启事务

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

*数据库事务并发Concurrent问题

假设现在有两个事务Transaction01和Transaction02并发执行。
1、脏读(当前事物读到了其他事物更新但是还没有提交的值就是脏读)
①Transaction01将某条记录的AGE值从20修改为30。
②Transaction02读取了Transaction01更新后的值:30。Read Uncommited
③Transaction01回滚,AGE值恢复到了20。④Transaction02读取到的30就是一个无效的值。
只有脏读是读取了别人更新但是还没有提交到数据库的数据,不可重复读和幻读都是读取已经提交数据库的数据。

2、不可重复读
①Transaction01读取了AGE值为20。
②Transaction02将AGE值修改为30。
③Transaction01再次读取AGE值为30,和第一次读取的不一样
这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

3、幻读
①Transaction01读取了STUDENT表中的一部分数据
②Transaction02向STUDENT表中插入了新的行
③Transaction01读取了STUDENT表时,多出了一些行。

不可重复读和幻读读到的都是已经提交到数据库的真实数据,和脏读比起来并不是什么大问题。

*事物隔离级别

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

请添加图片描述
可以在@Transactional的isolation属性中设置隔离级别,并不是隔离级别越高越好,一般是READ COMMITTED读已提交。

Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
在项目中一般用读已提交(Read Commited)这个隔离级别

MyBatis有缓存,读取后再次读取时读到的其实是缓存里的内容,所以不会有变化。
测试时候现将MyBatis的缓存关闭掉:
#禁用二级缓存
mybatis.configuration.cache-enabled=false
#一级缓存指定为statement级别
mybatis.configuration.local-cache-scope=statement

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值