zoukankan      html  css  js  c++  java
  • JDBC事务控制管理

    今天是学习计划的第二天,感觉自己的学习热情还是很高涨的啊,那我们就趁热打铁,开始今天的学习。
    今天的学习内容是JDBC的事务控制管理。
    首先是概念性的内容
    事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。这是我对于事务的理解。
    举个例子: A转账给B,对应如下的两条sql语句
    update from account set money = money - 100 where name = 'A'
    update from account set money = money + 100 where name = 'B'
    在现实生活中,这两条sql语句要么就应该同时成功,要么就应该同时失败,否则用户的账户就会产生问题。
    在MySQL数据库中,默认情况下,一条sql语句就是一个单独的事务,事务是自动提交的
    在Oracle数据库中,默认情况下,事务不是自动提交的,所有sql语句都处于一个事务中,需要手动进行事务提交。
    数据库事务命令

    • start transaction 开启事务
    • rollback 回滚事务
    • commit 提交事务

    我们来操作一个案例感受一下。
    首先设置账户表(account)  在表中插入一些初始数据

    create table account(
    	id int primary key not null auto_increment,
    	name varchar(40),
    	money double
    );
    
    insert into account values(1,'aaa',1000);
    insert into account values(2,'bbb',1000);
    insert into account values(3,'ccc',1000);
    

    然后打开我们的控制台进入数据库,输入 start transaction;这时候我们去修改数据,输入update account set money = money - 100 where name ='aaa';,然后输入查询语句select * from account;查询
    在这里插入图片描述
    此时说明更新语句成功执行了,但是请注意,我们在更新数据之前使用start transaction语句开启了一个事务,所以如果我们不手动提交,事务是不会被提交的,我们可以打开另一个控制台进入数据库,并查询该表
    在这里插入图片描述
    会发现,名字为aaa账户的余额并没有被改变,这就是事务的状态。
    其实,在事务管理中执行sql语句,都会使用数据库内的临时表保存,在没有进行事务提交或者回滚的前提下,其它用户是无法看到操作结果的。
    回到第二个用户的操作中,当我输入commit;提交事务时,我们再次查询表数据
    在这里插入图片描述
    这个时候第二个用户才能查询到第一个用户更新的数据。
    我们继续在第一个用户的数据库中操作,重新开启一个事务,然后输入update account set money = money + 100 where name ='bbb';,查询表数据
    在这里插入图片描述bbb账户的金额多了100,此时,我们可以使用回滚操作,通俗地讲,就是撤销上一次的sql语句,输入rollback;然后重新查询表数据
    在这里插入图片描述
    会发现,bbb账户的金额又变为了1000,说明回滚操作生效了。
    需要注意的是,sql语言中只有DML才能被事务管理,那什么是DML,DML就是数据操纵语言,是SQL语言中,负责对数据库对象运行数据访问工作的指令集,以INSERT、UPDATE、DELETE三种指令为核心,分别代表插入、更新与删除,所以只有这三个关键字才能被事务管理,其它语句是不能被事务管理的。
    这样事务的基本操作都在控制台进行了对应的练习,接下来我们了解一下JDBC在项目中是如何控制事务的。
    在MyEclipse中新建一个名为demo的的web项目,然后导入数据库的驱动,这个东西我就不提供了,在官网或者其它地方都能够下载。为了方便接下来的数据库操作,我先写了一个简易的JDBC工具类,新建JDBCUtils.java文件

    /**
     * JDBC 工具类,抽取公共方法
     * 
     * @author seawind
     * 
     */
    public class JDBCUtils {
    	private static final String DRIVERCLASS;
    	private static final String URL;
    	private static final String USER;
    	private static final String PWD;
    
    	static {
    		ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
    		DRIVERCLASS = bundle.getString("DRIVERCLASS");
    		URL = bundle.getString("URL");
    		USER = bundle.getString("USER");
    		PWD = bundle.getString("PWD");
    	}
    
    	// 建立连接
    	public static Connection getConnection() throws Exception {
    		loadDriver();
    		return DriverManager.getConnection(URL, USER, PWD);
    	}
    
    	// 装载驱动
    	private static void loadDriver() throws ClassNotFoundException {
    		Class.forName(DRIVERCLASS);
    	}
    
    	// 释放资源
    	public static void release(ResultSet rs, Statement stmt, Connection conn) {
    		if (rs != null) {
    			try {
    				rs.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			rs = null;
    		}
    
    		release(stmt, conn);
    	}
    
    	public static void release(Statement stmt, Connection conn) {
    		if (stmt != null) {
    			try {
    				stmt.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			stmt = null;
    		}
    		if (conn != null) {
    			try {
    				conn.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			conn = null;
    		}
    	}
    }
    

    还要有一个配置文件,因为在工具类中我是通过配置文件获取属性值进行数据库连接的
    注意,一定要在src目录下新建dbconfig.properties文件

    DRIVERCLASS=com.mysql.jdbc.Driver
    URL=jdbc:mysql:///test
    USER=root
    PWD=123456
    

    新建TransferTest.java文件进行测试,我们先不用事务管理来编写一下转账操作

    @Test
    public void demo1(){
    	//模拟转账操作,先不使用事务管理
    	Connection conn = null;
    	PreparedStatement stmt = null;
    	try {
    		conn = JDBCUtils.getConnection();
    		String sql1 = "update account set money = money - 100 where name = 'aaa'";
    		String sql2 = "update account set money = money + 100 where name = 'bbb'";
    		
    		stmt = conn.prepareStatement(sql1);
    		stmt.executeUpdate();
    		
    		stmt = conn.prepareStatement(sql2);
    		stmt.executeUpdate();
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    

    先查询一下数据库表
    在这里插入图片描述
    此时三个用户的账户余额都为1000,现在运行刚才编写的测试代码,然后重新查询表数据
    在这里插入图片描述
    账户余额发生了对应的改变,说明测试代码运行成功了。这里没有使用事务管理,两句sql语句其实就是两个事务,所以如果在两个事务之间出现了一点问题,整个转账的过程就会出现意外,我们可以制造一点问题出来。

    @Test
    public void demo1(){
    	//模拟转账操作,先不使用事务管理
    	Connection conn = null;
    	PreparedStatement stmt = null;
    	try {
    		conn = JDBCUtils.getConnection();
    		String sql1 = "update account set money = money - 100 where name = 'aaa'";
    		String sql2 = "update account set money = money + 100 where name = 'bbb'";
    		
    		stmt = conn.prepareStatement(sql1);
    		stmt.executeUpdate();
    		
    		//在两个事务中间制造一个异常
    		int d = 1 / 0;
    		
    		stmt = conn.prepareStatement(sql2);
    		stmt.executeUpdate();
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    

    我们在两个事务之间加上int d = 1 / 0;的代码,这样就会在执行完第一条事务之后产生异常从而中断程序运行,第二条事务也就得不到执行,看看是不是会发生这样的事情。
    我们运行测试代码,程序报错,然后查询一下表数据
    在这里插入图片描述
    会发现,aaa用户的账户余额少了100,而bbb用户的余额并没有被改变,显然这种事情是不能被发生在现实生活中的银行业务中的。在这种情况下,为了保证两条sql语句的一致性,我们需要使用事务管理。在程序代码中,JDBC是会自动提交我们的事务的,我们可以通过Connection类的setAutoCommit(false)方法来关闭自动提交,从而获得控制事务提交的权利。
    在TransferTest.java文件中编写第二个测试方法

    @Test
    public void demo2(){
    	//模拟转账操作,使用事务管理
    	Connection conn = null;
    	PreparedStatement stmt = null;
    	try {
    		conn = JDBCUtils.getConnection();
    		//在连接获得后,开启事务
    		conn.setAutoCommit(false);	//关闭自动提交
    		String sql1 = "update account set money = money - 100 where name = 'aaa'";
    		String sql2 = "update account set money = money + 100 where name = 'bbb'";
    		
    		stmt = conn.prepareStatement(sql1);
    		stmt.executeUpdate();
    		
    		int d = 1 / 0;
    		
    		stmt = conn.prepareStatement(sql2);
    		stmt.executeUpdate();
    		
    		//如果程序能走到这一步  说明两句sql都执行成功	提交事务
    		conn.commit();
    	} catch (Exception e) {
    		//表示在执行转账的过程产生了异常	需要将两句sql语句进行回滚
    		try {
    			conn.rollback();
    		} catch (SQLException e1) {
    			e1.printStackTrace();
    		}
    		e.printStackTrace();
    	}
    }
    

    现在,执行测试代码,程序报错,查询表数据
    在这里插入图片描述
    三个账户的余额均没有发生变化,说明两句sql语句被回滚了,当你删掉错误代码,重新运行,发现表数据被相应地修改了,这样就达到了事务管理的目的。
    这是基本的JDBC控制事务的方法了。
    再来了解一些高级的事务操作,我们假设,当事务特别复杂的时候,有些情况不会回滚到事务的最开始状态,这时候就需要将事务回滚到指定位置,此时就需要知道 事务回滚点(SavePoint)。
    我们先创建一张person表

    create table person(
    	id int primary key,
    	name varchar(40)
    );
    

    新建一个测试方法

    @Test
    	public void demo3(){
    		//创建person表	向表中插入两万条数据
    		//如果插入过程中	发生错误 要保证插入的条数是1000的整数倍
    		Connection conn = null;
    		PreparedStatement stmt = null;
    		try {
    			conn  = JDBCUtils.getConnection();
    			String sql = "insert into person values(?,?)";
    			//预编译sql
    			stmt = conn.prepareStatement(sql);
    			for(int i = 0;i <= 20000;i++){
    				stmt.setInt(1, i);
    				stmt.setString(2, "name" + i);
    				//添加到批处理
    				stmt.addBatch();
    			
    			if(i == 4699){
    				int d = 1 / 0;
    			}
    			
    			//每隔200次向数据库发送一次
    			if(i % 200 == 0){
    				stmt.executeBatch();
    				stmt.clearBatch();
    			}
    		}
    		
    		//为了确保缓存的sql都提交了
    		stmt.executeBatch();
    	} catch (Exception e) {
    		e.printStackTrace();
    	}finally{
    		JDBCUtils.release(stmt, conn);
    	}
    }
    

    我们故意在循环内制造一个错误,运行测试代码,程序报错,查询表数据
    在这里插入图片描述
    只有4600条数据,那么该如何获得1000的整数倍的数据记录呢?我们可以在获得连接之后获得一个回滚点,然后在循环中每隔1000条数据就重新保存一下回滚点,然后在异常处理代码块中写conn.rollback(savepoint);回滚到回滚点。具体代码如下:

    @Test
    public void demo3(){
    	//创建person表	向表中插入两万条数据
    	//如果插入过程中	发生错误 要保证插入的条数是1000的整数倍
    	Connection conn = null;
    	PreparedStatement stmt = null;
    	Savepoint savepoint = null;
    	try {
    		conn  = JDBCUtils.getConnection();
    		
    		//开启事务
    		conn.setAutoCommit(false);
    		//保存一次回滚点
    		savepoint = conn.setSavepoint();
    		
    		String sql = "insert into person values(?,?)";
    		//预编译sql
    		stmt = conn.prepareStatement(sql);
    		for(int i = 1;i <= 20000;i++){
    			stmt.setInt(1, i);
    			stmt.setString(2, "name" + i);
    			//添加到批处理
    			stmt.addBatch();
    			
    			if(i == 4699){
    				int d = 1 / 0;
    			}
    			
    			//每隔200次向数据库发送一次
    			if(i % 200 == 0){
    				stmt.executeBatch();
    				stmt.clearBatch();
    			}
    			
    			if(i % 1000 == 0){//1000的整数倍
    				//保存回滚点
    				savepoint = conn.setSavepoint();
    			}
    		}
    		
    		//为了确保缓存的sql都提交了
    		
    		//如果执行到这 说明程序没有错误
    		conn.commit();
    		stmt.executeBatch();
    	} catch (Exception e) {
    		//回滚事务	回滚到存储点
    		try {
    			conn.rollback(savepoint);
    			conn.commit();
    		} catch (SQLException e1) {
    			e1.printStackTrace();
    		}
    		e.printStackTrace();
    	}finally{
    		JDBCUtils.release(stmt, conn);
    	}
    }
    

    先在数据库控制台输入truncate person;来清除person表中的所有数据,然后执行测试代码,查询表数据
    在这里插入图片描述
    会发现,当前只有4000条数据了,因为程序出现异常,事务记录了第4000条记录的回滚点,并在出现异常之后回滚到了第4000条数据,至此,我们的目的也就实现了。

  • 相关阅读:
    tips
    数学建模-预测模型优缺(搬运)
    数学建模-灰色预测模型GM(1,1)_MATLAB
    Floyd算法_MATLAB
    第二章 运算方法与运算器(浮点数的加减法,IEEE754标准32/64浮点规格化数)
    面向对象
    for循环
    if---else
    airflow的web任务管理
    airflow原理
  • 原文地址:https://www.cnblogs.com/blizzawang/p/11411301.html
Copyright © 2011-2022 走看看