事务原理与开发

2018/3/10 18:55:01 人评论 次浏览 分类: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);//设置事务的隔离级别

 

相关知识

  • Servlet 工作原理解析

    Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础。因而掌握 Servlet 的工作原理是成为一名合格的 Java Web 技术开发人员的基本要求。本文将带你认识 Java Web 技术是如何基于 Servlet 工作,你将知道:以 Tomcat 为例了解 Servlet 容…

    2017/7/18 11:11:00

共有访客发表了评论 网友评论

验证码: 看不清楚?