隔离区别对待?如何捆绑?Java中的jdbc数据库事务及其隔离级别

简介: 隔离区别对待?如何捆绑?Java中的jdbc数据库事务及其隔离级别

文章目录


一、数据库事务简介

二、JDBC事务处理

三、事务的ACID属性

四、数据库的隔离级别

五、设置隔离级别


一、数据库事务简介


事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。


事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。


为确保数据库中数据的一致性,数据的操作应当是离散的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。


二、JDBC事务处理


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


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

①当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。

②关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。


JDBC程序中为了让多个 SQL 语句作为一个事务执行:

①调用 Connection 对象的 setAutoCommit(false) 以取消自动提交事务

②在所有的 SQL 语句都成功执行后,调用 commit() 方法提交事务

③在出现异常时,调用 rollback() 方法回滚事务


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


案例:用户AA向用户BB转账100


测试类:


package jdbc;
import utils.jdbcUtils;
import java.sql.Connection;
import java.sql.SQLException;
/**
 * @Author: Yeman
 * @Date: 2022-01-30-16:04
 * @Description:
 */
public class UpdateDate2 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            //获取连接
            conn = jdbcUtils.getSqlConnection();
            //取消自动提交,开启事务
            conn.setAutoCommit(false);
            //预编译sql语句
            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            jdbcUtils.updateDate(conn,sql1,"AA");
            //模拟网络异常
            System.out.println(10 / 0);
            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            jdbcUtils.updateDate(conn,sql2,"BB");
            //若没有异常则提交
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //若有异常则回滚
            try {
                conn.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
            //恢复自动提交
            try {
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //关闭conn连接
            jdbcUtils.closeResource(conn,null);
        }
    }
}


工具类:


package utils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
 * @Author: Yeman
 * @Date: 2022-01-12-14:34
 * @Description:操作数据库的工具类
 */
public class jdbcUtils {
    /**
     * @Description 获取数据库连接
     * @Param []
     * @return java.sql.Connection
     **/
    public static Connection getSqlConnection() 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;
    }
    /**
     * @Description 关闭连接和Statement资源
     * @Param [conn, ps]
     * @return void
     **/
    public static void closeResource(Connection conn, Statement ps){
        try {
            if (ps != null) ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static void closeResource(Connection conn, Statement ps, ResultSet res){
        try {
            if (ps != null) ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (res != null) res.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * @Description 通用增删改操作2.0
     * @Param
     * @return
     **/
    public static int updateDate(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        try {
            //预编译sql语句,获取PreparedStatement实例
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //执行操作
            return ps.executeUpdate(); //返回操作的字段数,没有则为0
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            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 再次读取同一个表,就会多出几行。


数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。


一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好,但并发性越弱。


Mysql 4种隔离级别:


ab065698873c4a949fa9287c133b87c4.png


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


五、设置隔离级别


案例:一个线程查询,另一个线程更新,当更新的线程尚未提交时查询的为初始的值,提交后查询到的为更新后的值。(这里将隔离级别设置为READ_COMMITTED)


测试类:


package jdbc;
import javabean.User;
import utils.Query;
import utils.jdbcUtils;
import java.sql.Connection;
import java.util.List;
/**
 * @Author: Yeman
 * @Date: 2022-01-30-22:27
 * @Description:
 */
public class TransactionTest {
    public static void main(String[] args) {
        //用于更新的线程
        TransactionThread transactionThread = new TransactionThread();
        Thread thread = new Thread(transactionThread);
        thread.start();
        //用于查询的线程
        Connection conn = null;
        try {
            conn = jdbcUtils.getSqlConnection();
            String sql = "select user,password,balance from user_table where user = ?";
            //设置隔离级别为提交可读
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            List<User> query1 = Query.Query(conn, User.class, sql, "CC");
            query1.forEach(System.out :: println);
            //延迟5秒后再次查询
            Thread.sleep(5000);
            List<User> query2 = Query.Query(conn,User.class,sql,"CC");
            query2.forEach(System.out :: println);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            jdbcUtils.closeResource(conn,null);
        }
    }
}


用于更新的线程类:


package jdbc;
import utils.jdbcUtils;
import java.sql.Connection;
/**
 * @Author: Yeman
 * @Date: 2022-01-30-22:47
 * @Description:
 */
public class TransactionThread implements Runnable{
    @Override
    public void run() {
        Connection conn = null;
        try {
            conn = jdbcUtils.getSqlConnection();
            //取消自动提交
            conn.setAutoCommit(false);
            String sql = "update user_table set balance = ? where user = ?";
            jdbcUtils.updateDate(conn,sql,5000,"CC");
            //设置延迟3秒后再提交
            Thread.sleep(3000);
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            jdbcUtils.closeResource(conn,null);
        }
    }
}


工具类utils同上案例


测试结果:


d3e21e1600ab4011b854048a97d6a728.png


相关文章
|
28天前
|
SQL 关系型数据库 MySQL
乐观锁在分布式数据库中如何与事务隔离级别结合使用
乐观锁在分布式数据库中如何与事务隔离级别结合使用
|
14天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
21天前
|
数据库
什么是数据库的事务隔离级别,有什么作用
【10月更文挑战第21】什么是数据库的事务隔离级别,有什么作用
12 3
|
21天前
|
存储 SQL JSON
介绍一下RDBMS和NoSQL数据库之间的区别
【10月更文挑战第21天】介绍一下RDBMS和NoSQL数据库之间的区别
47 2
|
21天前
|
存储 关系型数据库 数据挖掘
什么是数据库的事务隔离级别
【10月更文挑战第21】什么是数据库的事务隔离级别
15 1
|
22天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
27天前
|
存储 数据库 数据库管理
数据库事务安全性控制如何实现呢
【10月更文挑战第15天】数据库事务安全性控制如何实现呢
|
27天前
|
存储 数据库 数据库管理
什么是数据库事务安全性控制
【10月更文挑战第15天】什么是数据库事务安全性控制
|
27天前
|
供应链 数据库
数据库事务安全性控制有什么应用场景吗
【10月更文挑战第15天】数据库事务安全性控制有什么应用场景吗
|
27天前
|
存储 关系型数据库 MySQL
数据库的事务控制
【10月更文挑战第15天】数据库的事务控制
22 2