事务

作者: 茶还是咖啡 | 来源:发表于2019-10-01 13:12 被阅读0次

事务

事务是以一种可靠、一致的方式、访问和操作数据库中数据的程序单元。

事务的4大特性ACID

  • 原子性
    事务中的多个操作要么都完成,要不都不完成,不会只完成其中的一部分
  • 一致性
    事务执行之后,状态改变是一致的或者说结果是完整的。
  • 隔离性
    当前事务的操作对于其他事务是否可见
  • 持久性
    事务提交之后,操作的结果会进入数据库被永久保存。如果事务没有提交,出现数据库宕机,数据不会被保存到数据库中。
eg: 张三给李四转100元钱
image.png

原子性:转钱成功后,张三的账户一定是扣掉了100元,李四的账户一定是增加了100元,不会出现单方面账户变化。
一致性:转账成功后,张三的账户扣掉的一定是100元,李四的账户一定是增加了100元,不会出现张三的账户被扣了120元而李四的账户增加了80。
隔离性:张三给李四转账执行的时候的中间状态是否对李四可见,比如张三的账户被扣了100元后,李四的账户还没有加100元这个中间状态是否对李四可见。
持久性:转账成功后(事务提交后)数据被永久存储在数据库中,不会出现数据丢失的情况。

用SQL实现事务
  1. 创建数据库和表数据
create database test;
use test;
create table t_user(id int PRIMARY key auto_increment,username varchar(32),amount int);
insert t_user(username,amount) values('张三',500),('李四',500);
image.png
  1. 转账事务
START TRANSACTION; 
UPDATE t_user SET amount = amount - 100 WHERE username = '张三';
UPDATE t_user SET amount = amount + 100 WHERE username = '李四';
COMMIT;

mysql 的默认隔离级别是可重复读,事务执行的任何中间状态都不会被其他事务看到,也就是说,在转账事务执行的时候,如果另一个事务进行查询操作,查到的账户余额都是事务开始时的余额即,张三,500;李四,500。

使用JDBC实现事务

public class LocalJdbcTransaction {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Connection connection = getConnection();
        //关闭自动提交,相当于开启事务
        connection.setAutoCommit(false);

        String plusSQL = "UPDATE t_user SET amount = amount + 100 WHERE username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setString(1, "张三");
        plusPS.execute();

        String minusSQL = "UPDATE t_user SET amount = amount - 100 WHERE username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setString(1, "李四");
        minusPS.execute();
        
        plusPS.close();
        minusPS.close();
        //提交事务
        connection.commit();
    }

    /**
     * 获取数据库连接
     *
     * @return Connection
     * @throws SQLException           SQLException
     * @throws ClassNotFoundException ClassNotFoundException
     */
    private static Connection getConnection() throws SQLException, ClassNotFoundException {
        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&serverTimezone=UTC";
        String username = "root";
        String password = "******";
        Class.forName(driver);
        return DriverManager.getConnection(url, username, password);
    }
}

FOR UPDATE


image.png

[场景]
张三和王五都想给李四转账100元钱。
初始情况下,理想情况下,转账后,李四的账户应该是300元。
转账的逻辑是这样的:

  1. 查询李四账户的余额。
  2. 李四的账户余额增加100元。

张三和王五的转账逻辑分别在不同的事务中执行。


image.png
  1. 张三开启事务,查询到李四的账户余额是100元。
  2. 王五开启事务,查询到李四的账户余额也是100元。
  3. 张三给李四的余额增加了100元,李四的余额变成了200元,张三提交事务。
  4. 王五给李四的余额也增加了100元,但是是在第一个查询到余额的基础上增加的100元,即100+100=200元。王五提交事务。
    转账完成后,发现李四的账户只多了100元。有人可能说王五别先查询李四的账户,等到张三执行完转转操作后王五在查询李四的账户就好了,但是!!!首先我们无法控制这个先后顺序,因为这在两个线程中,而且最重要的是mysql的隔离级别是可重复读,也就是说张三在没有提交事务之前,王五查询的结果将一直都是100元。

[解决方案]
解决这样的问题最好的方式是加for update锁。
for update锁,会在查询的时候锁定该行,知道事务提交的时候才释放锁。

  1. 如果没有用在事务中使用for update锁,那么该锁不会生效
  2. 如果使用该锁的时候,查询语句没有索引,那么该锁会锁定整张表。造成资源的大量浪费


    image.png

    王五在查询李四余额的时候会阻塞查询语句,等待张三事务提交,张三提交事务后,王五查询到正确的余额,即:200,王五继续给李四转账,李四收获300。

[具体实现]

    public void test() throws SQLException, ClassNotFoundException {
        Connection connection = getConnection();
        //关闭自动提交,相当于开启事务
        connection.setAutoCommit(false);
        String selectSQL = "SELECT * from t_user where username = ? for update";
        PreparedStatement selectQuery;
        selectQuery = connection.prepareStatement(selectSQL);
        selectQuery.setString(1,"李四");
        ResultSet resultSet = selectQuery.executeQuery();
        Long amount = null;
        while (resultSet.next()){
            String username = resultSet.getString(2);
            amount = resultSet.getLong(3);
            System.out.println(username+"==>"+amount);
        }
        String minusSQL = "UPDATE t_user SET amount = ? + 100 WHERE username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setLong(1,amount);
        minusPS.setString(2, "李四");
        minusPS.execute();
        minusPS.close();
        //提交事务
        connection.commit();
    }

Spring 事务

Spring事务抽象

  • PlatFormTransactionManager
    事务管理器的接口;包括事务开启,提交,回滚等操作
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
  • TransactionDefinition
    事务的定义,可以通过该接口定义事务的属性,例如:传播属性,隔离属性
public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    int TIMEOUT_DEFAULT = -1;

    int getPropagationBehavior();

    int getIsolationLevel();

    int getTimeout();

    boolean isReadOnly();

    @Nullable
    String getName();

}
  • TransactionStatus
    相当于事务运行过程中的句柄。
public interface TransactionStatus extends SavepointManager, Flushable {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    @Override
    void flush();

    boolean isCompleted();
}
编程形式使用Spring事务
@Service
public class OrderService {
    @Resource
    private PlatformTransactionManager txManager;
    
    void buyTicket(){
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = txManager.getTransaction(def);
        try {
            // todo 执行业务代码
            txManager.commit(status);
        }catch (Exception e){
            txManager.rollback(status);
        }
    }
}

声明式事务管理
方法体上加@Transactional

相关文章

  • java事务

    1、java事务介绍 2、JDBC事务 3、JTA事务 1、java事务介绍 java事务分类:JDBC事务、...

  • 事务、MySQL与Python交互、Python 中操作 MyS

    1、事务 事务操作分两种:自动事务(默认)、手动事务 手动事务的操作流程 开启事务:start transacti...

  • 数据库事务书目录

    数据库事务 事务概念 本地事务 全局事务 全局事务的定义 J2EE中全局事务的实现 全局事务的优缺点 基于消息的分...

  • MySQL事务

    MySQL-innodb-事务 事务的特性 事务的生命周期 失败的事务: 事务的控制语句 面试题 事务日志-red...

  • mysql事务隔离机制及其隔离级别、实现原理分析

    目录 事务特性ACID属性 并发事务带来的问题 事务隔离级别 事务实现原理 事务特性ACID属性 事务特性指的就是...

  • MULE事务配置

    在mule的事务可能为jdbc事务,jms事务,xa事务等,多种事务.这里讲解事务的几个动作: 相关的文档:htt...

  • 事务—事务模型

    程序猿基础知识的学习、理解、整理——事务(方方土) 事务,看似很简单,其实很复杂,作为一个程序猿,你对事务到底了解...

  • 事务—XA事务

    程序猿基础知识的学习、理解、整理——事务(方方土) 什么是XA事务?在什么场景下会出现XA事务? @Transac...

  • 事务—事务模式

    程序猿基础知识的学习、理解、整理——事务(方方土) 什么是事务模式?这里提到的事务模式,主要是指在系统设计过程中的...

  • JDK动态代理给Spring事务埋下的坑

    service 模拟动态代理事务 测试类 预测结果模拟事务:开启事务execute doLink模拟事务:关闭事务...

网友评论

    本文标题:事务

    本文链接:https://www.haomeiwen.com/subject/uzccpctx.html