Java 数据库编程6---事务处理

事务介绍

  事务处理在数据库开发中有着非常重要的作用,所谓事务就是所有的操作要么一起成功,要么一起失败事务本身具有原子性(Atomicity),一致性(Consistency),隔离性或独立性(Isolation),持久性(Durability)4个特性,这4个特性也被称为ACID特性。

  • 原子性:原子性是事务最小的单元,是不可再分割的单元,相当于一个个小小的数据库操作,如果这些操作必须同时完成,如果有一个失败了,则一切的操作将全部失败。例如,A给B转账,如果A转账失败了,则B收账也就失败。
  • 一致性:指的是在数据库操作的前后是完全一致的,保证数据的有效性,如果事务正常操作则系统会维持有效性,如果事务出现了错误,则回到最原始的状态,也要维持其有效性,这样保证事务开始时和结束时系统处于一致状态。例如:A给B转账,如果转账成功,则保持其一致性,即A的钱减少x元,B的钱增加x元。如果现在A给B转账失败,则保持操作之前的一致性,即A的钱不会减少,B的钱不会增加
  • 隔离性:多个事务可以同时进行而且彼此之间无法访问,只有当事务完成最终操作时才可以看到结果。
  • 持久性:当一个系统崩溃是,一个事务依然可以坚持提交,当一个事务完成后,操作的结果保存在磁盘中,永远不会被回滚。例如在转账操作时,所有资金数都保存在磁盘中,所以即使系统发生错误,用户账户里的资金也不会增加或减少。

MySQL对事务的支持

  在MySQL中提供了如下表所示的几个命令,可以进行事务的处理。

序号 命令 描述
1 set autocommit=0 取消自动提交处理,开启事务处理
2 set autocommit=1 打开自动提交处理,关闭事务处理
3 start transaction 启动事务
4 begin 启动事务相当于执行start transaction
5 commit 提交事务
6 roolback 回滚全部操作
7 savepoint 事务保存点名称 设置事务保存点
8 rollback to savepoint 保存点名称 回滚操作保存点

  以上所有操作都是针对一个session的,在数据库操作中把每一个连接到此数据库上的用户都称为一个session
在MySQL中,如果要应用事务处理,则应该按照以下的顺序输入命令。

  • 取消自动提交:执行set autocommint=0。这样所有的更新指令并不会立刻发送到数据库的表中,而只存在于当前的session
  • 开启事务:执行start transactionbegin
  • 编写数据库更新语句:如增加,修改,删除。可以在编写的更新语句之前记录事务的保存点,使用savepoint命令
  • 提交事务:如果确信数据库的修改没有任何的错误,则使用commit提交事务。在提交之前对数据库所做的全部操作都保存在session中。
  • 事务回滚:如果发现执行的SQL语句有错误,则使用rollback命令全部撤销,或者使用rollback to savepoint记录点,让其回滚到指定的位置。
    当一个事务进行时,其他的session是无法看到此事务的操作状态的,即此session对数据库所做的一切修改,如果没有提交事务,则其他session是无法看到当前session操作结果的。

    执行JDBC的事务处理

      先来看一种情况,现在要求在数据库中执行5条SQL语句,这些SQL语句本身需要保持一致,即要么同时成功,要么同时失败。
      先来看看不使用事务的一个例子:

不使用事务处理,一次性插入5条数据

为了演示方便,这里先把test表中的记录全都删除掉。

  • 可以在Navicat for Mysql(或者其他图形化工具)中选中记录删除,
    这里写图片描述

  • 也可以使用命令删除:

 delete * from test;

使用命令删除test表中的全部记录
删除后test表为空表:
删除所有表中的记录
实例:使用批处理往数据库中添加5条记录

package my.transaction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TransactionDemo1
{
    public static final String driver="com.mysql.jdbc.Driver";
    public static final String url="jdbc:mysql://127.0.0.1:3306/usersinfo";
    public static final String user="root";
    public static final String password="root";
    public static void main(String[] args) throws ClassNotFoundException, SQLException, ParseException
    {
        Connection con;
        PreparedStatement pstmt;
        String sql="insert into test(id,name,sex,grade,major,birthday) "
                + "values(?,?,?,?,?,?)";
        Class.forName(driver);
        con=DriverManager.getConnection(url,user,password);
        pstmt=con.prepareStatement(sql);
        java.util.Date today;
        java.sql.Date birthday;
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        String toString;
        for(int i=0;i<5;i++)
        {
            if(i==1)
            {
                //故意再次输入已经存在的学号S1000
                pstmt.setString(1, "S1000");//id
                pstmt.setString(2, "小明_"+i);//name
                pstmt.setString(3, "男");//sex
                pstmt.setString(4, "大_"+i);//grade
                pstmt.setString(5, "计算机科学与技术");//major
                today=new java.util.Date();
                //获取今天的日期
                today=format.parse(format.format(today));
                birthday=new java.sql.Date(today.getTime());
                pstmt.setDate(6, birthday);//birthday
                //打印编译好的SQL语句
                toString=pstmt.toString();
                System.out.println("mysql>"+toString.substring(toString.lastIndexOf(":")+1).trim());
                //加入批处理等待执行
                pstmt.addBatch();
            }
            else {
                pstmt.setString(1, "S100"+i);//id
                pstmt.setString(2, "小明_"+i);//name
                pstmt.setString(3, "男");//sex
                pstmt.setString(4, "大_"+i);//grade
                pstmt.setString(5, "计算机科学与技术");//major
                today=new java.util.Date();
                //获取今天的日期
                today=format.parse(format.format(today));
                birthday=new java.sql.Date(today.getTime());
                pstmt.setDate(6, birthday);//birthday
                //加入批处理等待执行
                toString=pstmt.toString();
                System.out.println("mysql>"+toString.substring(toString.lastIndexOf(":")+1).trim());
                pstmt.addBatch();
            }
        }
        //执行批处理
        int[] result=pstmt.executeBatch();
        System.out.println("成功插入了"+result.length+"条数据");
        pstmt.close();
        con.close();
    }
}

运行结果:

  • 控制台输出:
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_0','男','大_0','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_1','男','大_1','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1002','小明_2','男','大_2','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1003','小明_3','男','大_3','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1004','小明_4','男','大_4','计算机科学与技术','2018-07-1')
Exception in thread "main" java.sql.BatchUpdateException: Duplicate entry 'S1000' for key 'PRIMARY'
at......
  • test表中的数据:
    test表中的数据
    从上面的运行结果中来看,test表中只插入了部分的数据。程序只执行正确的SQL语句,但如果要求这些语句是一个整体,只有所有语句都能正确执行的时候才一起全部插入到test表中,如果有一条语句执行错误,这都不插入到test表中。这时,上面的程序不能达到这样的要求。使用事务处理可以达到上述要求的功能。
    进行事务处理的步骤:

  • 取消Connection中设置的自动提交方式:con.setAutoCommit(false);

  • 如果批处理操作成功,则执行提交事务:con.commit();
  • 如果操作失败,则肯定会发生异常,在异常处理语句中让事务回滚:con.rollback();
  • 如果需要,可以设置Savepoint: Savepoint sp=con.setSavepoint();
    实例:事务的基本操作步骤
    还是和之前一样,为了演示方便,先把test表中的全部记录都删除掉:
delete from test;

代码:

package my.transaction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TransactionDemo2
{
    public static final String driver="com.mysql.jdbc.Driver";
    public static final String url="jdbc:mysql://localhost:3306/usersinfo";
    public static final String user="root";
    public static final String password="root";
    public static void main(String[] args) throws ClassNotFoundException,SQLException, ParseException
    {
        Connection con;
        PreparedStatement pstmt;
        String sql="insert into test(id,name,sex,grade,major,birthday) "
                + "values(?,?,?,?,?,?)";
        Class.forName(driver);
        con=DriverManager.getConnection(url,user,password);
        //第一步,取消自动提交
        con.setAutoCommit(false);
        pstmt=con.prepareStatement(sql);
        java.util.Date today;
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        java.sql.Date birthday;
        String toString;
        for(int i=0;i<5;i++)
        {
            if(i==1)
            {
                //故意再次输入已经存在的学号S1000
                pstmt.setString(1, "S1000");//id
                pstmt.setString(2, "小丽_"+i);//name
                pstmt.setString(3, "女");//sex
                pstmt.setString(4, "大_"+i);//grade
                pstmt.setString(5, "软件工程");//major
                today=new java.util.Date();
                //获取今天的日期
                today=format.parse(format.format(today));
                birthday=new java.sql.Date(today.getTime());
                pstmt.setDate(6, birthday);//birthday
                //打印编译好的SQL语句
                toString=pstmt.toString();
                System.out.println("批处理操作>"+toString.substring(toString.lastIndexOf(":")+1).trim());
                //加入批处理等待执行
                pstmt.addBatch();
            }
            else {
                pstmt.setString(1, "S100"+i);//id
                pstmt.setString(2, "小明_"+i);//name
                pstmt.setString(3, "男");//sex
                pstmt.setString(4, "大_"+i);//grade
                pstmt.setString(5, "计算机科学与技术");//major
                today=new java.util.Date();
                //获取今天的日期
                today=format.parse(format.format(today));
                birthday=new java.sql.Date(today.getTime());
                pstmt.setDate(6, birthday);//birthday
                //加入批处理等待执行
                toString=pstmt.toString();
                System.out.println("批处理操作>"+toString.substring(toString.lastIndexOf(":")+1).trim());
                pstmt.addBatch();
            }
        }
        try
        {
            //执行批处理操作,如果都成功,则会执行到该条下面的语句
            int[] success=pstmt.executeBatch();
            System.out.println("操作成功,成功插入了:"+success.length+"条记录");
            //如果批处理操作成功(不发生异常),则提交事务
            con.commit();
        } catch (Exception e)
        {
            //如果批处理操作不成功,出现了异常
            //事务回滚
            e.printStackTrace();
            System.out.println("操作异常,事务回滚");
            con.rollback();
        }
        pstmt.close();
        con.close();
    }
}

运行结果:

  • 控制台部分输出:
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_0','男','大_0','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1000','小丽_1','女','大_1','软件工程','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1002','小明_2','男','大_2','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1003','小明_3','男','大_3','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1004','小明_4','男','大_4','计算机科学与技术','2018-07-10')
java.sql.BatchUpdateException: Duplicate entry 'S1000' for key 'PRIMARY'
    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2045)
    at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1468)
    at my.transaction.TransactionDemo2.main(TransactionDemo2.java:73)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'S1000' for key 'PRIMARY'
    at 
......略
操作异常,事务回滚
  • test表中的数据:
    依然是空表
    以上程序中加入了事务的处理操作,这样当更新出错后,数据库会进行回滚,则所有的更新操作全部失效。
    在程序中可以加入若干的Savepoint作为事务的保存点。
    实例:回滚到保存点,后面输入的不算数
package my.transaction;

import java.sql.Statement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TransactionDemo3
{
    public static final String driver="com.mysql.jdbc.Driver";
    public static final String url="jdbc:mysql://localhost:3306/usersinfo";
    public static final String user="root";
    public static final String password="root";
    public static void main(String[] args) throws ClassNotFoundException,SQLException, ParseException
    {
        Connection con;
        Statement stmt;
        //加载数据库驱动
        Class.forName(driver);
        con=DriverManager.getConnection(url,user,password);
        //第一步关闭自动提交
        con.setAutoCommit(false);
        //创建操作
        stmt=con.createStatement();
        stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
                + "values('G1000','小赵','男','大三','计算机科学与技术','2015-6-9')");
        stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
                + "values('G1001','小钱','男','大三','计算机科学与技术','2015-6-9')");
        //穿件一个保存点
        Savepoint sp=con.setSavepoint();
        stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
                + "values('G1002','小孙','男','大三','计算机科学与技术','2015-6-9')");
        stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
                + "values('G1003','小李','男','大三','计算机科学与技术','2015-6-9')");

        try{
            //回滚的哦保存点,则后面插入的两行不算数
            System.out.println("后面输入的不算数");
            con.rollback(sp);
            //提交事务
            con.commit();

        }catch(Exception e)
        {
        }

        stmt.close();
        con.close();
    }

}

运行结果:

后面输入的不算数

test表中的数据:
只插入保存点之前的记录
可以看到test表中只插入了保存点之前的两条记录。

猜你喜欢

转载自blog.csdn.net/qq_21808961/article/details/80984122