JSP基础(十七)——事务

一、事务的概念

   事务指的是逻辑上的一组操作,组成这组操作的各个单元,要么不全部成功,要么不全部不成功。

   例如:A--B转账,对应于如下两条SQL语句:

   Update from account set money = money + 100 where name='B';

   Update from account set money = money - 100 where name='A';


二、MySQL数据库中操作事务命令

SQL脚本:

/*创建账户表*/
  use jdbcstudy;
 create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
);

/*插入测试数据*/
insert into account(name,money) values('Amy',100);
insert into account(name,money) values('Belly',100);
insert into account(name,money) values('Carry',100);

2.1、开启事务(Start transaction)

使用“start  transacion”开启MySQL数据库的事务,如下图:



在数据库中模拟转账失败的场景,首先执行Update语句让Amy的money减少10块钱,如下图:


然后关闭命令窗口,这要就导致了刚刚执行的Update语句的数据库的事务没有被提交,那么我们对A用户的修改就不算是是真正的修改了,下次在查询Amy的money时,依然还是之前的100,如下图:



2.2、提交事务(commit)

模拟A--B转账成功的场景:



2.3、回滚事务(rollback)

通过手动回滚事务,让所有的操作都失效,这样数据就会回到最初的初始状态!



三、JDBC中使用事务

   当JDBC程序向数据库获取一个Connection对象,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列的JDBC控制事务语句:

Connection.setAutoCommit(false);  // 开启事务(Start Transaction)

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

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


3.1、JDBC使用事务范例

在JDBC代码中演示银行转账案例,使用如下转账操作在同一事务中执行:

"update account set money=money-100 where name='A' "

"update account set money=money+100 where name='B' "

代码如下:

package me.zl.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import me.zl.utils.JdbcUtils;

/**
 * ClassName:TransactionDemo1
 * Description:jdbc中使用事务来模拟转账
 */
public class TransactionDemo1 {

	/**
	 * @Method:testTransaction1
	 * @Description:模拟转账成功时的业务场景
	 */
	public static void testTransaction1() {
		Connection conn = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		
		try {
			conn = JdbcUtils.getConnection();
			conn.setAutoCommit(false);//通知数据库开启事务(Start Transaction)
			
			String sql1 = "update account set money=money-20 where name='Amy'";
			st = conn.prepareStatement(sql1);
			st.executeUpdate();
			
			String sql2 = "update account set money=money+20 where name='Belly'";
			st = conn.prepareStatement(sql2);
			st.executeUpdate();
			
			conn.commit();//上面两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
			System.out.println("成功!!!");
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			JdbcUtils.release(conn, st, rs);
		}	
		
	}
	
	/**
	 * @Method:testTransaction2
	 * @Description:模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
	 */
	public static void testTransaction2() {
		Connection conn = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		
		try {
			conn = JdbcUtils.getConnection();
			conn.setAutoCommit(false);//通知数据库开启事务(Start Transaction)
			
			String sql1 = "update account set money=money-13 where name='Amy'";
			st = conn.prepareStatement(sql1);
			st.executeUpdate();
			
			//用这行代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法执行,事务也无法正常提交,此时数据库会自动执行回滚操作
			int x = 1/0;
			
			String sql2 = "update account set money=money+13 where name='Belly'";
			st = conn.prepareStatement(sql2);
			st.executeUpdate();
			
			conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
			System.out.println("成功!!!");
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			JdbcUtils.release(conn, st, rs);
		}
		
	}
	
	/**
	 * @Method:testTransaction3
	 * @Description:模拟转账过程中出现异常导致有一部分SQL执行失败时手动通知数据库回滚事务
	 */
	public static void testTransaction3() {
		Connection conn = null;
		PreparedStatement st = null;
		ResultSet rs = null;
		
		try {
			conn = JdbcUtils.getConnection();
			conn.setAutoCommit(false);//通知数据库开启事务
			
			String sql1 = "update account set money=money-12 where name='Belly'";
			st = conn.prepareStatement(sql1);
			st.executeUpdate();
			
			//用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交
			int x = 1/0;
			
			String sql2 = "update account set money=money+12 where name='Amy'";
			st = conn.prepareStatement(sql2);
			st.executeUpdate();
			
			conn.commit();//通知数据库提交事务
			System.out.println("成功!!!");
			
		} catch (SQLException e) {
			try {
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}finally{
			JdbcUtils.release(conn, st, rs);
		}		
	}
	
	public static void main(String[] args) {
//		testTransaction1();
//		testTransaction2();
//		testTransaction3();
	}

}

3.2、设置事务回滚点

在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点

Savapoint sp = conn.setSavepoint();

conn.rollback(sp);

conn.commit(); //回滚后必须通知数据库提交事务


设置事务回滚点范例:

package me.zl.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import me.zl.utils.JdbcUtils;

public class TransactionDemo2 {
    public static void testTransaction1() {
    	Connection conn = null;
    	PreparedStatement st = null;
    	ResultSet rs = null;
    	Savepoint sp = null;
    	
    	try {
			conn = JdbcUtils.getConnection();
			conn.setAutoCommit(false);
			
			String sql1 = "update account set money=money-15 where name='Carry'";
			st = conn.prepareStatement(sql1);
			st.executeUpdate();
			
			//设置事务的回滚点
			sp = conn.setSavepoint();
			
			String sql2 = "update account set money=money+15 where name='Amy'";
			st = conn.prepareStatement(sql2);
			st.executeUpdate();
			
			//程序执行到这里出现异常,后面的sql3语句
//			int x = 1/0;
			
			String sql3 = "update account set money=money-10 where name='Carry'";
			st = conn.prepareStatement(sql3);
			st.executeUpdate();
			
			conn.commit();
			
    	} catch (SQLException e) {
    		try {
    			/*
    			 * 上面向数据库发送了3条Update语句,
    			 * sql3语句由于程序出现异常导致无法正常执行,数据库事务而已无法正常提交
    			 * 由于设置的事务回滚点是在sql1语句正常执行完成之后,sql2语句正常执行之前
    			 * 那么通知数据库回滚事务时,不会回滚sql1执行的update操作,只会回滚到sql2执行的update操作
    			 * 也就是说,上面三条update语句中,sql1这条语句的修改操作起作用了
    			 * sql2的修改操作由于事务回滚没有起作用,sql3由于程序异常没用机会执行
    			 */
				conn.rollback(sp);//回滚到设置的事务回滚点
				conn.commit();//通知数据库提交事务
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}finally{
			JdbcUtils.release(conn, st, rs);
		}
    }
	public static void main(String []args) {
		testTransaction1();
	}
}


四、事务的四大特性(ACID)

4.1、原子性(Atomicity)

    原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败


4.2、一致性(Consistency)

  事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。


4.3、隔离性(Isolation)

  事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。


4.4、持久性(Durability)

   持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

 


事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别

五、事务的隔离级别

多个线程开启各自事务操作数据库中数据时 ,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

5.1事务不考虑隔离性可能引发的问题

    ▶ 脏读

    指一个事务读取了另外一个事务未提交的数据。


    ▶不可重复读

    指在一个事务内部读取表中的某一行数据,多次读取结果不同。、


    ▶虚读

    指在一个事务内读取到了别的事务插入的数据,导致前后读取的不一致。


脏读不可重复读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重读了前一事务已提交的事务。



5.2、事务隔离性的设置语句

mysql数据库共定义了四种隔离级别:

▶Serializable(可串行化):可避免脏读、不可重复读、虚读。

▶Repeatable read(可重复读):可避免脏读、不可重复读。

▶Read committed(读已提交):可避免脏读。

▶Read uncommitted(读未提交):最低级别,以上情况无法保证。


mysql数据库查询当前当前事务隔离级别:select  @@tx_isolation;

mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)

mysql数据库设置事务的隔离级别:set Transaction isolation level 隔离级别名;




5.3、使用MySQL数据库演示不同隔离级别下的并发问题

  同时打开两个窗口模拟2个用户并发访问数据库

▶当把事务的隔离级别设置为read uncommitted时,会引发脏读、不可重复读和虚读

       A窗口

    set transaction isolation level  read uncommitted;--设置A用户的数据库隔离级别为Read uncommitted(读未提交)

    start transaction; --开启事务

    select * from account;  --查询A账户中现有的钱,转到B窗口进行操作

    select * from account ; --发现a多了100元,这时候A读到了B未提交的数据(脏读)

  B窗口
    start transaction; --开启事务
    update account set money=money+100 where name='A'; --不要提交,转到A窗口查询

当把事务的隔离级别设置为read uncommitted时,会引发脏读、不可重复读,避免了虚读

      A窗口
    set transaction isolation level  read committed;
    start transaction;
    select * from account;--发现a帐户是1000元,转到b窗口
    select * from account;--发现a帐户多了100,这时候,a读到了别的事务提交的数据,两次读取a帐户读到的是不同的结果(不可重复读)
  B窗口
    start transaction;
    update account set money=money+100 where name='aaa';
    commit;--转到a窗口


当把事务的隔离级别设置为repeatable read(mysql默认)时,会引发虚读,避免了虚读不可重复读

      A窗口
    set transaction isolation level repeatable read;
    start transaction;
    select * from account;--发现表有4个记录,转到b窗口
    select * from account;--可能发现表有5条记录,这时候发生了a读取到另外一个事务插入的数据(虚读)
  B窗口
    start transaction;
    insert into account(name,money) values('ggg',1000);
    commit;--转到a窗口


当把事务的隔离级别设置为Serializable时,会避免所有问题

      A窗口
    set transaction isolation level Serializable;
    start transaction;
    select * from account;--转到b窗口

  B窗口
    start transaction;
    insert into account(name,money) values('ggg',1000);--发现不能插入,只能等待a结束事务才能插入











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值