事务操作和批量处理

一、JDBC 事务操作

1.事务回顾

1.1事务的ACID 属性

  • 原子性(Atomicity):事务是不可分割的,要么同时成功,要么同时失败。

  • 一致性(Consistency):事务必须使数据库从一个一致性状态变成另一个一致性状态。

  • 隔离性(Isolation):一个事务的执行不会被其它事务所干扰。

  • 持久性(Durability):事务一旦提交,内存中的内容持久化到文件,并且是永久性的。

1.2数据库并发问题

  • 脏读:有事务A、B,事务A读到了事务B更新但是没有提交的数据。

  • 不可重复读:有事务A、B,事务A先去读一个字段(name),事务B去更新修改(name) 字段,事务A再去读,发现数据变了。

  • 幻读:有事务A、B,事务A先去读整张表的数据,事务B去插入了新数据,事务A再去读的时候,数据变多了。

1.3数据库隔离级别(用于处理并发问题)

  • 读未提交(READ UNCOMMITED):允许事务读取未被其它事务提交的数据。

    • 脏读

    • 不可重复读

    • 幻读

  • 读已提交(READ COMMITED):只允许事务读取已经被其它事务所提交的数据。

    • 可以避免脏读

    • 不可避免不可重复读和幻读

  • 可重复读(REPEATABLE_READ):事务操作某一个字段时,禁止其它事务更新。

    • 避免了脏读和不可重复读

    • 幻读还是可能出现

  • 串行化(SERIALIZABLE):确保事务操作的时候,禁止其它事务操作。

    • 解决掉所有的问题

2.JDBC 事务处理

1.事务特点

  • 事务一旦提交,数据就不可以回滚,不可变了。

  • 数据什么时候提交?

    • 当我们获取连接的时候,默认情况下是自动提交事务;每次执行sql执行成功就会自动提交,不会回滚。

    • 关闭数据库连接的时候,自动提交事务。只要谈事务,多个操作必须使用同一个连接才有意义。

2.JDBC 操作多条SQL的事务

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

  • 所有的SQL执行完之后做 commit()

  • 出现异常回滚,调用 rollback()回滚事务

  • 注意

    • 如果同一个连接没有被关闭,可能会存在重复使用,那需要手动的设置自动提交方式。setAutoCommit(true);

    • 特别是在连接池使用的过程中,调用close 方法先恢复自动提交状态。

3.JDBC 操作银行转账案例

1.需求:上云向张三转账1000块

update account set money = money -1000 where user_name = ?

update account set money = money + 1000 where user_name = ?

2.创建获取连接的工具类

 static private DataSource dataSource = null;

        static {
            Properties properties = new Properties();
            InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties");
            try {
                properties.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //获取德鲁伊连接池中的连接对象
        public static Connection getConn(){

            Connection connection = null;
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
    }

3.实现需求

@Test
    public void accountTest(){
        Connection conn = null;
        try {
            //获取连接对象
            conn = DruidUtil.getConn();
            //设置自动提交为false,手动管理事务
            conn.setAutoCommit(false);

            String sql1 = "update account set money = money - 1000 where user_name = ?";
            PreparedStatement statement = conn.prepareStatement(sql1);
            statement.setString(1,"sy");
            statement.execute();
            int num = 1/0;
            System.out.println(num);

            String sql2 = "update account set money = money + 1000 where user_name = ?";
            PreparedStatement statement1 = conn.prepareStatement(sql2);
            statement1.setString(1,"zs");
            statement1.execute();


            //执行完提交执行
            conn.commit();
            statement.close();
            statement1.close();

        } catch (Exception e) {
            e.printStackTrace();
            try {
                conn.rollback();//遇到异常设置回滚,重置表中数据
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }


        }
    }

二、批量处理

1.使用前提

  • 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率。

2.JDBC 批量处理方法

  • addBatch(String):添加需要批量处理的SQL语句或是参数

  • executeBatch():执行批量处理语句

  • clearBatch():清空缓存的数据

3.业务场景

  • 多条SQL语句的批量处理

  • 一个SQL语句的批量传参

4.批量处理案例

  • 向数据库中插入5万条数据

4.1实现方式1

  • 使用Statement

public void test1() throws SQLException {
        Connection conn = DruidUtil.getConn();
        Statement statement = conn.createStatement();
        for (int i = 0; i < 50000; i++) {
            String sql = "insert into account(user_name) values("+i+")";
            statement.execute(sql);
        }
    }

4.2实现方式2

  • 使用PreparedStatement

 public void test2() throws SQLException {
        long begin = System.currentTimeMillis();
        String sql = "insert into account(user_name) values(?)";
        Connection conn = DruidUtil.getConn();
        PreparedStatement statement = conn.prepareStatement(sql);
        for (int i = 0; i < 20000; i++) {
            statement.setObject(1,i);
            statement.executeUpdate();
        }
        conn.close();
        System.out.println("耗时:"+(System.currentTimeMillis() - begin));
    }

4.3实现方式3

  • addBatch() / executeBatch() / clearBatch()

public void test3() throws SQLException {
        long begin = System.currentTimeMillis();
        String sql = "insert into account(user_name) values(?)";
        Connection conn = DruidUtil.getConn();
        PreparedStatement statement = conn.prepareStatement(sql);
        for (int i = 0; i < 20000; i++) {
            statement.setObject(1,i);
            //存sql
            statement.addBatch();
            if(i %1000 == 0){
                statement.executeBatch();
                //执行完还需要去清缓存
                statement.clearBatch();
            }
        }
        conn.close();
        System.out.println("耗时:"+(System.currentTimeMillis() - begin));
    }

4.4实现方式四(最快)

  • 批处理+事务

public void test4() throws SQLException {
        long begin = System.currentTimeMillis();
        String sql = "insert into account(user_name) values(?)";
        Connection conn = DruidUtil.getConn();
        conn.setAutoCommit(false);
        PreparedStatement statement = conn.prepareStatement(sql);
        for (int i = 0; i < 20000; i++) {
            statement.setObject(1, i);
            //存sql
            statement.addBatch();
            if (i % 1000 == 0) {
                statement.executeBatch();
                //执行完还需要去清缓存
                statement.clearBatch();
            }
        }
        conn.commit();
        conn.close();
        System.out.println("耗时:" + (System.currentTimeMillis() - begin));
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值