zoukankan      html  css  js  c++  java
  • 事务原理与开发

    什么是事务?


    举个常见的例子:

    张三要转给李四100元钱。

    首先张三的账户上要扣除100元钱,然后李四的账户上增加100元钱。

    但是在这期间,如果出了问题,比如,张三账户上扣掉100元,但是李四的账户上没能加上100元;张三的账户上没能成功扣掉100元,而李四的账户上增加了100元等等。

    这都不能达成我们预期的目的。

    在这个转账的例子中,

    1.首先我们要做到的就是张三和李四转账这个事是一个整体,要么全做,要么都不做。像在化学中的原子是不可分割的。(即原子性  atomicity)

    2.我们要保证这个事情是关于100元的事情,不是200元,也不是50元,是100元!这100元在程序运行中是不变的,执行前后是不变的。(即一致性   consistency)

    3.假如在张三和李四交易的同时,王五凑巧在给张三转200元。假设两个交易是并发执行的。不能出现以下情况:

    即两个交易之间必须是互相隔离的,不能会出现信息错误。(隔离性  isolation)

    4.假设在交易完成后,银行系统坏了,那么必须保证,修复之后的系统记录和系统坏掉之前记录一致。(持久性  durability)

    那么到底什么是事务?


    事务(Transaction)是并发控制的基本单位,指作为单个逻辑工作单元执行的一系列操作,而这些逻辑工作单元需要满足ACID特性(即上面四种特性的英文首字母)。

    JDBC的事务控制:


    connection对象提供了三个方法实现事务逻辑:

    Connection conn=getConnection();//自定义获取Connection对象的方法
    conn.setAutoCommit(false);//开启事务
    conn.commit();//提交事务
    conn.rollback();//回滚事务

    其中,

    conn.setAutoCommit(false);//开启事务

    在方法参数中填入了false关键字,关闭了JDBC的自动提交事务(默认的)。本来如果是默认的自动提交,那么每一条语句操作都将产生结果,这就破坏了事务的原子性,因为这就使得这一系列操作存在了中间状态,这一系列操作也就不是所谓的事务。

    所以,关闭JDBC默认的自动提交,才是开启事务管理。

    conn.commit();//提交事务

    表示事务提交,整个事务结束,整个事务中的sql语句都将生效产生结果。

    conn.rollback();//回滚事务

    表示”回滚“表示我们将会回到事务开始之前的状态。

    关于张三李四转账的示例代码:

    package com.java.transaction;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class Transaction {
    
        
        private static String Driver="com.mysql.jdbc.Driver"; //数据库驱动
        //连接数据库的URL地址
        private static String url="jdbc:mysql://localhost:3306/hellojdbc?useUnicode=true&characterEncoding=UTF-8";
        private static String username="root";//数据库连接用户名
        private static String password="123456";//数据库连接密码
        
        private static Connection conn=null;//数据库连接对象
        private static PreparedStatement pst=null;//预编译语句
        
        //使用静态块的方式加载驱动
        static {
            try {
                //调用Class对象的静态forName()方法加载数据库驱动类
                Class.forName(Driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            
        }
        
        //使用单例模式返回数据库连接对象
        public static Connection getConnection() throws SQLException{
            if(conn==null){
                conn=DriverManager.getConnection(url, username, password);
                return conn;
            }
            return conn;
    
        } 
        
        public static void doTransaction(){
            try {
                conn=getConnection();
                conn.setAutoCommit(false);//开启事务
                pst=conn.prepareStatement("update logintable account = ? where name = ? ");
                pst.setInt(1, 0);
                pst.setString(2, "zhangsan");
                pst.execute();
                pst.setInt(1, 100);
                pst.setString(2, "lisi");
                pst.execute();
                conn.commit();//提交事务
            } catch (SQLException e) {
                if(conn!=null)
                try {
                    conn.rollback();//回滚事务
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally{
                try {
                    if(pst!=null)
                        pst.close();
                    if(conn!=null)
                        conn.close();
                } catch (SQLException e) {
                   e.printStackTrace();
                 }
                
            }
        }
        
        
        
        public static void main(String[] args) {
            doTransaction();
    
        }
    
    }

    检查点:


    JDBC还提供了断点处理和控制的功能。

    我们在事务内部设置断点,当在断点之后的部分出现异常错误的时候,我们还能回到断点,去执行断点后面的另外一条路,而这个过程也被当作一个事务来处理。

    Savepoint sp=null;//声明检查点 
    sp=conn.setSavepoint(); //保存检查点
    conn.rollback(sp);//回滚到断点

    将上面示例代码修改为当向李四转账出现错误时,改转给王五。

    package com.java.transaction;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import java.sql.Savepoint;
    
    public class Transaction {
    
        
        private static String Driver="com.mysql.jdbc.Driver"; //数据库驱动
        //连接数据库的URL地址
        private static String url="jdbc:mysql://localhost:3306/hellojdbc?useUnicode=true&characterEncoding=UTF-8";
        private static String username="root";//数据库连接用户名
        private static String password="123456";//数据库连接密码
        
        private static Connection conn=null;//数据库连接对象
        private static PreparedStatement pst=null;//预编译语句
        
        //使用静态块的方式加载驱动
        static {
            try {
                //调用Class对象的静态forName()方法加载数据库驱动类
                Class.forName(Driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            
        }
        
        //使用单例模式返回数据库连接对象
        public static Connection getConnection() throws SQLException{
            if(conn==null){
                conn=DriverManager.getConnection(url, username, password);
                return conn;
            }
            return conn;
    
        } 
        
        public static void doTransaction(){
            Savepoint sp=null;//声明检查点 
            try {
                conn=getConnection();
                conn.setAutoCommit(false);//开启事务
                pst=conn.prepareStatement("update logintable account = ? where name = ? ");
                pst.setInt(1, 0);
                pst.setString(2, "zhangsan");
                pst.execute();
                sp=conn.setSavepoint(); //保存检查点
                pst.setInt(1, 100);
                pst.setString(2, "lisi");
                pst.execute();
                //conn.commit();//此处不再提交事务
                throw new SQLException(); //为了检验检查点,人工抛一个异常
            } catch (SQLException e) {
                if(conn!=null){
                    try {
                        conn.rollback(sp);//回滚到断点
                        //不给李四转了,改转给王五
                        pst.setInt(1, 100);
                        pst.setString(2, "wangwu");
                        pst.execute();
                        conn.commit();//提交事务
                    } catch (SQLException e1) {
                        e1.printStackTrace();
                    }
                }
                
            }finally{
                try {
                    if(pst!=null)
                        pst.close();
                    if(conn!=null)
                        conn.close();
                } catch (SQLException e) {
                   e.printStackTrace();
                 }
                
            }
        }
        
        
        
        public static void main(String[] args) {
            doTransaction();
    
        }
    
    }

    事务并发执行的几个概念:


    1)脏读:

    一个事务读取了另外一个事务未提交的信息。

    例子:

    张三在向李四转账100元的同时又存上了200元。

    因为事务T1未能提交执行成功,所以本来应该拥有300元的张三,由于脏读(事务T2读取了T1未提交的信息),张三实际上只拥有200元。

    2)不可重复读:

    同一事务中两次读取相同记录,结果不一样。(行记录的值)

    例子:

    事务T1张三连续两次读取自己账户的余额;事务T2张三读取自己账户上的余额后又存上200元。

    3)幻读:

    两次读取的结果包含的行记录不一样。(行记录数)

    事务隔离级别:


    1)读未提交(read  uncommitted)

      允许出现脏读

    2)读提交(read  committed)

      不允许出现脏读,但是可以允许不可重复读。

    3)重复读(repeatable  read)

      不允许出现不可重复读,可能会出现幻读。

    4)串行化(serializable)

      最高事务隔离级别。不允许出现幻读。因为它的级别最高,并发控制最严格。所有的事务都是串行执行的。导致数据库的性能变差。

    注意:

    MySQL默认事务隔离级别为repeatable read。

    事务隔离级别越高,数据库性能就越差,但是编程的难度越低。

    设置JDBC中的隔离级别:

    Connection conn=getConnection();//自定义获取Connection对象的方法
    conn.getTransactionIsolation();//获取事务的隔离级别
    conn.setTransactionIsolation(level);//设置事务的隔离级别
  • 相关阅读:
    Jmeter监控服务器性能
    三种主流的WebService实现方案(REST/SOAP/XML-RPC)简述及比较
    从0到1搭建移动App功能自动化测试平台(0):背景介绍和平台规划
    Jmeter监控系统等资源,ServerAgent端口的修改
    Performance plugin离线安装
    Oracle定义常量和变量
    通过FTP将一个数据文件从A服务器下载到B服务器的整个过程
    Oracle使用%rowtype变量存储一行数据
    Oracle使用%type类型的变量输出结果
    mdf与ldf文件如何还原到SQLserver数据库
  • 原文地址:https://www.cnblogs.com/dudududu/p/8540806.html
Copyright © 2011-2022 走看看