zoukankan      html  css  js  c++  java
  • JDBC(3)-数据库事务

    一、环境搭建(复习)

    1. 首先建立lib目录然后要把对应的jar包导进来
    2. 然后就是jdbc.properties文件
    user=root
    password=123456
    url=jdbc:mysql://localhost:3306/test
    driverClass=com.mysql.jdbc.Driver
    
    1. 然后就是数据库的工具类了JDBCUtils
    package com.md.util;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Properties;
    
    public class JDBCUtils {
    	
    	/**
    	 * 获取数据库的连接
    	 * @return
    	 * @throws Exception
    	 */
    
    	public static Connection getConnection() throws Exception {
    		//1.加载配置文件
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(is);
            
            //2.读取配置信息
            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");
    
            //3.加载驱动
            Class.forName(driverClass);
    
            //4.获取连接
            Connection conn = DriverManager.getConnection(url,user,password);
           
            return conn;
    	}
    	
    	
    	/**
    	 * 关闭资源
    	 * @param conn
    	 * @param ps
    	 * @return 
    	 */
    	public static void closeResource(Connection conn , PreparedStatement ps) {
    		try {
    			if(ps != null)
    				ps.close();
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    		try {
    			if(conn != null)
    				conn.close();
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * 关闭资源 , 有结果集调用这个
    	 * @param conn
    	 * @param ps
    	 * @param rs
    	 */
    	
    	public static void closeResource(Connection conn , PreparedStatement ps , ResultSet rs) {
    		try {
    			if(ps != null)
    				ps.close();
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    		try {
    			if(conn != null)
    				conn.close();
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    		try {
    			if(rs != null)
    				rs.close();
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    
    
    1. 测试连接是否成功

    二、数据库事务介绍

    • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
    • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
    • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态

    三、JDBC事务处理

    • 数据一旦提交,就不可回滚。

    • 数据什么时候意味着提交?

      • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
      • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
    • JDBC程序中为了让多个 SQL 语句作为一个事务执行:

      • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
      • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
      • 在出现异常时,调用 rollback(); 方法回滚事务

      若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

    数据定义:DDL:用于定义SQL模式、基本表、视图和索引的创建和撤消操作,CREATE ALTER DROP TRUNCATE COMMENT RENAME。

    数据操纵:DML:数据操纵分成数据查询和数据更新两类。数据更新又分成插入、删除、和修改三种操作

    DDL操作一旦执行,都会自动提交,即使setAutoCommit(false)也无效

    DML设置setAutoCommit(false)可以取消自动提交事务

    1. 案例:

    用户AA向用户BB转账100

    普通版:

    @Test
    	public void testUpdate() {
    		String sql1 = "update user_table set balance = balance - 100 where user = ?";
    		update(sql1,"AA");
            
            // 模拟网络异常
    		System.out.println( 10 / 0);
    		
    		String sql2 = "update user_table set balance = balance + 100 where user = ?";
    		update(sql2,"BB");
    
    	}
    	
    	
    	// 通用的增删改
    	public void update(String sql,Object ... args){
    		Connection conn = null;
    		PreparedStatement ps = null;
    		try {
    			//1.获取数据库的连接
    			conn = JDBCUtils.getConnection();
    			
    			//2.获取PreparedStatement的实例 (或:预编译sql语句)
    			ps = conn.prepareStatement(sql);
    			//3.填充占位符,sql的下标从1开始,而数组下标从0开始
    			for(int i = 0;i < args.length;i++){
    				ps.setObject(i + 1, args[i]);
    			}
    			
    			//4.执行sql语句
    			ps.execute();
    		} catch (Exception e) {
    			
    			e.printStackTrace();
    		}finally{
    			//5.关闭资源
    			JDBCUtils.closeResource(conn, ps);
    			
    		}
    	}
    

    此时AA账号就少了100,而BB账户也没有多,所以

    2. 升级版(重点):

    考虑到事务的操作

    此时要避免上面的情况,

    • 由于我们平时使用的DML操作,所以DDL不用考虑
    • 连接对象创建时,调用 Connection 对象的 setAutoCommit(false)取消自动提交事务,以及后面执行完sql提交事务commit(),和出现异常时回滚rollback()
    • 最后重要的是,之前我们写的每执行一条sql都建立数据库连接以及关闭数据库连接,这样每执行一条sql都自动提交了,所以执行时只有建立一次数据库连接才行
    	@Test
    	public void testUpdate(){
    		
    		Connection conn = null;
    		try {
                //  0. 获取数据库连接
    			conn = JDBCUtils.getConnection();
    			
    			// 1. 取消数据的自动提交
    			conn.setAutoCommit(false);
    			
    			String sql1 = "update user_table set balance = balance - 100 where user = ?";
    			update(conn,sql1,"AA");
    			
    			// 模拟网络异常
    			System.out.println( 10 / 0);
    			
    			String sql2 = "update user_table set balance = balance + 100 where user = ?";
    			update(conn,sql2,"BB");
    					
    			System.out.println("转账成功");
    			
    			// 2. 提交数据
    			conn.commit();
    		} catch (Exception e) {
    			e.printStackTrace();
    			// 3. 执行出现异常,就回滚
    			try {
    				conn.rollback();
    			} catch (SQLException e1) {
    				e1.printStackTrace();
    			}
    		}finally {
                // 只有整个操作成功之后才关闭数据库的连接
    			JDBCUtils.closeResource(conn, null);
    		}
    		
    		
    	}
    	
    	
    	// 升级版,主要修改一下方面
    	// 1. conn从外面传进来
    	// 2. 关闭时不关闭conn连接
    	public int update(Connection conn,String sql,Object ... args){
    		PreparedStatement ps = null;
    		try {
    			//1.获取PreparedStatement的实例 (或:预编译sql语句)
    			ps = conn.prepareStatement(sql);
    			//2.填充占位符,sql的下标从1开始,而数组下标从0开始
    			for(int i = 0;i < args.length;i++){
    				ps.setObject(i + 1, args[i]);
    			}
    			
    			//3.执行sql语句,返回影响行数
    			return ps.executeUpdate();
    		} catch (Exception e) {
    			
    			e.printStackTrace();
    		}finally{
    			//4.关闭资源,此时没有关闭数据库连接,保证使用的是只有一个连接
    			JDBCUtils.closeResource(null, ps);
    		}
    		
    		return 0;
    	}
    
    

    此时就解决了上面的问题,即使中间出现异常,不会出现少钱的情况

    四、事务的ACID属性

    1. 原子性(Atomicity)
      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
    2. 一致性(Consistency)
      事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
    3. 隔离性(Isolation)
      事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    4. 持久性(Durability)
      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

    五、数据库的并发问题

    • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
      • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
      • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
      • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
    • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
    • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

    六、四种隔离级别

    • 数据库提供的4种事务隔离级别:

    • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED
    • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

    七、在MySql中设置隔离级别

    • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。

    • 查看当前的隔离级别:

      SELECT @@tx_isolation;
      
    • 设置当前 mySQL 连接的隔离级别:

      set  transaction isolation level read committed;
      
    • 设置数据库系统的全局的隔离级别:

      set global transaction isolation level read committed;
      
    • 补充操作:

      • 创建mysql数据库用户:

        create user tom identified by 'abc123';
        
      • 授予权限

        #授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
        grant all privileges on *.* to tom@'%'  identified by 'abc123'; 
        
         #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
        grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123'; 
        
        

    八、升级版的通用方法

    考虑到了事务

    1. 查询一

    主要修改了Connection conn是从外面传进来的,而且关闭时没有关闭conn

    那个返回集合的也是这样的修改

    	// 通用的针对于不同表的查询:返回一个对象 (version 2.0)
    	public <T> T getInstance(Connection conn,Class<T> clazz, String sql, Object... args) {
    		PreparedStatement ps = null;
    		ResultSet rs = null;
    		try {
    		
    			// 2.预编译sql语句,得到PreparedStatement对象
    			ps = conn.prepareStatement(sql);
    
    			// 3.填充占位符
    			for (int i = 0; i < args.length; i++) {
    				ps.setObject(i + 1, args[i]);
    			}
    
    			// 4.执行executeQuery(),得到结果集:ResultSet
    			rs = ps.executeQuery();
    
    			// 5.得到结果集的元数据:ResultSetMetaData
    			ResultSetMetaData rsmd = rs.getMetaData();
    
    			// 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
    			int columnCount = rsmd.getColumnCount();  // 得到列数
    			if (rs.next()) {
                    
    				T t = clazz.newInstance();
                    
    				for (int i = 0; i < columnCount; i++) {// 遍历每一个列
    
    					// 获取列值,相当于值
    					Object columnVal = rs.getObject(i + 1);
    					// 获取列的别名:列的别名,使用类的属性名充当,相当于属性
    					String columnLabel = rsmd.getColumnLabel(i + 1);
    
    					// 6.2使用反射,给对象的相应属性赋值
    					Field field = clazz.getDeclaredField(columnLabel);
    					field.setAccessible(true);
    					field.set(t, columnVal);
    
    				}
    
    				return t;
    
    			}
    		} catch (Exception e) {
    
    			e.printStackTrace();
    		} finally {
    			// 7.关闭资源
    			JDBCUtils.closeResource(null, ps, rs);
    		}
    
    		return null;
    
    	}
    

    2. 增删改

    public int update(Connection conn,String sql,Object ... args){
    		PreparedStatement ps = null;
    		try {
    			//1.获取PreparedStatement的实例 (或:预编译sql语句)
    			ps = conn.prepareStatement(sql);
    			//2.填充占位符,sql的下标从1开始,而数组下标从0开始
    			for(int i = 0;i < args.length;i++){
    				ps.setObject(i + 1, args[i]);
    			}
    			
    			//3.执行sql语句,返回影响行数
    			return ps.executeUpdate();
    		} catch (Exception e) {
    			
    			e.printStackTrace();
    		}finally{
    			//4.关闭资源,此时没有关闭数据库连接,保证使用的是只有一个连接
    			JDBCUtils.closeResource(null, ps);
    		}
    		
    		return 0;
    	}
    
  • 相关阅读:
    【C#进阶系列】06 类型和成员基础
    纪中5日T1 1564. 旅游
    纪中17日T1 2321. 方程
    纪中17日T2 2322. capacitor
    纪中10日T1 2313. 动态仙人掌
    纪中14日听课小结 图论 最短路 二分图 差分约束
    一个抓猫的游戏 消遣GAME 持续更新中!
    洛谷P1464 Function  HDU P1579 Function Run Fun
    洛谷P1976 鸡蛋饼
    纪中12日T1 2307. 选择
  • 原文地址:https://www.cnblogs.com/mengd/p/13423495.html
Copyright © 2011-2022 走看看