MySQL事务(JDBC事务管理)

目录

1、什么是事务?

2、MySQL中的事务

3、JDBC事务管理

3.1、API方法

3.2、环境搭建

3.3、不使用回滚出现的问题

3.4、使用事务回滚

4、事务回滚点

5、事务的特性ACID

5.1、事务的隔离级别

5.1.1、脏读

5.1.2、不可重复读

5.1.3、幻读(虚读)

5.2、数据库的隔离级别

5.3、隔离级别的性能问题


1、什么是事务?

指逻辑上一组操作,要么同时成功,要么同时失败。

2、MySQL中的事务

mysql中,默认事务是自动事务的。一条sql一个事务

start transaction  -- 开启一个事务。以后的sql都在一个事务中。更改的内容不会自动提交。
rollback        -- 事务的回滚—同时失败的情况。--事务结束,并且全部失败,数据回复到开始之前的状态
commit -- 事务的提交----同时成功---事务结束。全部成功。
-- mysql默认类似这样
start transaction ;
update account set money = money -100 where name = ‘a’;
commit;
start transaction;
update account set money = money +100 where name = ‘b’;
commit;

我们也可以手动去控制事务

-- 自身手动控制事务。
start transaction ;
update account set money = money -100 where name = ‘a’;

update account set money = money +100 where name = ‘b’;
commit;

3、JDBC事务管理

3.1、API方法

方法 说明
setAutoCommit(Boolean boolean) 相当于start transaction 设置为false开启事务为手动
rollback() 事务回滚
commit() 事务提交

3.2、环境搭建

如下图,创建一个web工程,在WEB-INF下创建lib包,里面放入mysql连接包。

这里提供了mysql5版本和mysql8版本的jar包,对应的就是mysql的版本,如果是mysql5系列版本的就用5.16的jar包,如果是mysql8系列的版本就用8.0.16版本jar包。

链接:https://pan.baidu.com/s/1loxaN41BXlfdePT_8DDBDw 
提取码:388i

放入后一定要导入,右键jar包点击ADD Library。

在src包下创建jdbc.properties文件

jdbc.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test6
jdbc.username=root
jdbc.password=xxx

在src包下创建util包存放JDBC工具类

public class JDBCUtil {
    public JDBCUtil() {
    }

    private static String className;
    private static String url;
    private static String username;
    private static String password;
    private static Connection conn = null;

    static {
        Properties pro = null;
        InputStream is = null;
        try {
            is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            pro = new Properties();
            pro.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        className = pro.getProperty("jdbc.className");
        url = pro.getProperty("jdbc.url");
        username = pro.getProperty("jdbc.username");
        password = pro.getProperty("jdbc.password");
        //加载驱动
        try {
            Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //获取连接
        try {
            conn = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        return conn;
    }

    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        try {
            if (rs != null) {
                rs.close();
                rs = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (stmt != null) {
                stmt.close();
                stmt = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                conn.close();
                conn = null;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void close(Statement stmt, Connection conn) {
        try {
            if (stmt != null) {
                stmt.close();
                stmt = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                conn.close();
                conn = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

创建mysql数据库和表

CREATE TABLE `account` (
  `name` varchar(20) NOT NULL,
  `money` int(255) DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加数据,模拟a向b进行转账,起始两人都是1000 

 

3.3、不使用回滚出现的问题

为了演示不使用事务带来的问题,创建一个测试包,在其测试类中进行编写。

注意事项:该测试方法中finally一定要写commit()方法,因为事务一旦开启,如果不提交,则mysql默认会进行回滚,try里一旦报错,finally里面的代码也会被执行,为了使异常时不让其回滚,方便我们看到效果,我们需要手动加入提交。

public class TransactionTest {
    @Test
    public void test1() {
        Connection conn = JDBCUtil.getConnection();
        Statement stmt = null;
        try {
            stmt = conn.createStatement();
            conn.setAutoCommit(false);
            //模拟a向b转账100元
            stmt.executeUpdate("update  account set money= money-100 where name='a'");
            //手动创建异常  模拟转账期间出现网络异常问题
            int a = 1 / 0;
            //b收到a转账的100元
            stmt.executeUpdate("update account set money=money+100 where name='b'");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                //finally块中加入commit提交,不论try是否报错,一定会进行提交,否则看不到效果
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JDBCUtil.close(stmt, conn);
        }
    }
}

运行结果

可以看到结果报了运算异常的错误,这也是在情理之中的

那我们去数据库看看数据会发生什么

可以发现,这时就出现bug了,a少了100元这是对的,但是b缺没有收到a转出去的100元,这a会损失100元,这种严重的问题是绝不能出现在实际开发过程中的,有这种好事电商不亏死O(∩_∩)O。

那如何解决这种严重的BUG呢,就要使用事务中的回滚进行解决。

3.4、使用事务回滚

在catch里面加入了一个rollback回滚,这样try里面一旦报错,进入到catch块中就会进行回滚操作,把finally中的commit删除,因为finally中写commit只是为了让我们看到效果。

public class TransactionTest {
    @Test
    public void test1() {
        Connection conn = JDBCUtil.getConnection();
        Statement stmt = null;
        try {
            stmt = conn.createStatement();
            conn.setAutoCommit(false);
            stmt.executeUpdate("update  account set money= money-100 where name='a'");
            //手动创建异常
            int a = 1 / 0;
            stmt.executeUpdate("update account set money=money+100 where name='b'");
            conn.commit();

        } catch (Exception e) {
            try {
                //调用回滚方法
                conn.rollback();
                conn.commit();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally{
             //释放资源
             JDBCUtil.close(stmt, conn);
        }
    }
}

再次测试

可以发现,这次都没有改变,因为遵循了事务的原则,要不同时失败要不同时成功,一旦有一方失败,则整体失败。

4、事务回滚点

方法 说明
setSavepoint() 设置回滚点

如果一遇到错误就回滚,那么也不一定是好事,比如现在有1w条数据,前面9998条数据都修改成功了,当9999条数据修改操作时出现了异常,那么前面9998也会被回滚掉,等于说修改操作白做了,所以,为了解决这一现象,需要使用回滚点,每10条数据设置一个回滚点,当第9999出现问题后,只会回滚9990-9999这10条数据,前面的9990条数据是不会被回滚的。

首先,先创建一个表为t_user

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

下面我演示一下1000条数据

 @Test
    public void test2() {
        Connection conn = JDBCUtil.getConnection();
        PreparedStatement pstmt = null;
        Savepoint save = null;
        try {

            pstmt = conn.prepareStatement("insert into user(name) values(?)");
            //开启事务
            conn.setAutoCommit(false);
            for (int i = 1; i < 1000; i++) {
                //在999条数据时出现异常
                if (i == 999) {
                    int a = 10 / 0;
                }
                pstmt.setString(1, "itssl");
                pstmt.executeUpdate();

                //每10条数据创建一个回滚点
                if (i % 10 == 0) {
                    save = conn.setSavepoint();
                }
            }
            conn.commit();
        } catch (Exception e) {
            try {
                //回滚并提交
                conn.rollback(save);
                conn.commit();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JDBCUtil.close(pstmt,conn);
        }
    }

可以看到,前面的990条数据都是添加成功的,而后面10条会被回滚掉。

5、事务的特性ACID

原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位(最小的一个整体),事务中的操作要么都发生,要么都不发生。 
一组操作时一个整体。不能分割。

一致性(Consistency)
事务前后数据的完整性必须保持一致。
一致性和原子性相关。只有都成功或者,都失败(原子性) ,就可以保证事务的一致性。
事务的前后的内容一直。 
转账: a 1000 b 1000 a给b转100;
转账之前 a和b的总额 2000
转账之后 也要2000;

隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。

多个事务是独立存在的。多个事务不能够相互干扰。
a –b---事务A
c-d---事务B
如果A失败了。不能够影响B

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

5.1、事务的隔离级别

5.1.1、脏读

一个事务读取了另外一个事务没有的提交的数据。非常严重。尽可能去避免掉脏读

比如:事务一开启事务后进行a减100元,b加100元,但是此时没有提交,而与此同时另一个事务开启事务进行读操作,select该表里的内容,发现a:900,b:1100,后来事务一提交事务(这里指回滚),后来事务二又查询了一次,发现数据又变回1000:1000,这会让事务二的用户感到迷惑

5.1.2、不可重复读

一个事务读取另外一个提交过的数据。造成另外一个事务,多次读取的【内容】不一致,数据的内容的改变。 update

5.1.3、幻读(虚读)

一个事务读取另外一个事务已经提交的数据。但是这里面强调的数据数目的改变。insert,delete。比如该表原始数据为9个,事务一新增数据后提交,事务二查询该表,数量为10,而事务一又把数据给删除了,事务二又查询该表,发现数量又变为了9。

5.2、数据库的隔离级别

读未提交

Read uncommitted (读未提交) 最低级别,以上情况均无法保证。 两个人的操作都是一样

事务一 

set session transaction isolation level Read uncommitted;
select @@tx_isolation;
start transaction;
update account set money = money-100 where name ='jack';
update account set money = money+100 where name ='rose';
rollback;

事务二

set session transaction isolation level Read uncommitted;
select @@tx_isolation;
start transaction;
select * from account;

 

读已提交

Read committed;(读已提交) 可避免脏读情况发生(避免不了虚读以及不可重复读)

set session transaction isolation level Read committed;
select @@tx_isolation;
start transaction;
update account set money = money-100 where name ='jack';
update account set money = money+100 where name ='rose';
commit;
set session transaction isolation level Read committed;
select @@tx_isolation;
start transaction;
select * from account;

可重复读

Repeatable read :可以避免脏读,不可重复度。

set session transaction isolation level Repeatable read;
select @@tx_isolation;
start transaction;
update account set money = money-100 where name ='jack';
update account set money = money+100 where name ='rose';
commit;
set session transaction isolation level Repeatable read;
select @@tx_isolation;
start transaction;
select * from account;

 

可串行化

串行化的可以避免所有的问题。 数据库让其他的事务进行等待,等待一个事务结束之后,这个事务再去操作。

set session transaction isolation level Serializable;
select @@tx_isolation;
start transaction;
update account set money = money-100 where name ='jack';
update account set money = money+100 where name ='rose';
commit;
set session transaction isolation level Serializable;
select @@tx_isolation;
start transaction;
select * from account;

5.3、隔离级别的性能问题

性能比较
Serializable  性能最差:事务一个一个执行的。排队。

Serializable  < Repeatable read < Read committed < Read uncommitted

安全性比较
Serializable 安全性最好:所有问题避免掉。

Serializable > Repeatable read > Read committed > Read uncommitted

Read uncommitted 避免不了最重问题,脏读。
Serializable:性能太差。

mysql (默认)-- Repeatable read;(可重复读)
oracle(默认) -- Read committed;(读已提交)

猜你喜欢

转载自blog.csdn.net/select_myname/article/details/126763157