七、JDBC-事务的隔离级别&批量处理

数据库事务的隔离级别

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
脏读: 对于两个事务 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

具体代码实现:

/**
     * ID1 给 ID2 500钱 
     * 关于事务:
     * 1.如果多个操作,每个使用自己单独的连接,则无法保证事务 例 test1演示
     * 2.具体步骤:
     *     1) 事务开始前,取消Connection 的默认的自动提交  setAutoCommit(false);
     *     2) 如果事务的操作都成功,那么就提交事务
     *     3)否则在 try-catch块中回滚
     * try {
     *      
     * conn.setAutoCommit(false);
     * ...
     *     conn.commit();
     * }catch{
     * ...
     *     conn.rollback();
     * }
     */
    @Test 
    public void test2(){
        
        Connection conn = null;
        try {
            conn = JDBC_Tools.getConnection();
            //System.out.println(conn.getAutoCommit());
            
            // 1) 取消自动提交
            conn.setAutoCommit(false);
            
            String sql = "UPDATE rent set money = "
                    + "money - 500 where id = ?";
            
            // 2) 如果事务的操作都成功,那么就提交事务
            update(conn,sql, 1);
            
            //int i = 1 / 0; 
            
            sql = "UPDATE rent set money = "
                    + "money + 500 where id = ?";
            update(conn,sql, 2);
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            
            // 3)否则在 try-catch块中回滚
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            
        }finally{
            JDBC_Tools.relaseSource(conn, null);
        }
    }
public static void update(Connection conn,String sql,Object...objs){
        
        PreparedStatement ps =null;
        try {
            ps = conn.prepareStatement(sql);
            
            for(int i = 0;i<objs.length;i++){
                ps.setObject(i+1, objs[i]);
            }
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBC_Tools.relaseSource(null, ps);
        }
    }

    @Test
    public void test1() {

        String sql = "UPDATE rent set money = "
                + "money - 500 where id = ?";
        DAO.update(sql, 1);
        
        int i = 1 / 0; //一旦出现异常, ID1 减了500,但是 ID2 的钱并没有增加
        
        sql = "UPDATE rent set money = "
                + "money + 500 where id = ?";
        DAO.update(sql, 2);
    }设置隔离级别

 public static <E> E getForValue(String sql){
        
        //1. 得到结果集,该结果只有一行一列
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //1. 获取数据库连接
            conn = JDBC_Tools.getConnection();//System.out.println(conn.getTransactionIsolation());
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            //2. 获取 PreparedStatement 对象
            ps = conn.prepareStatement(sql);
            //2. 取得结果
            rs = ps.executeQuery();
            if(rs.next()){
                return (E)rs.getObject(1);
            }
        }catch(Exception e){
                e.printStackTrace();
        }finally{
        JDBC_Tools.relaseSource(rs,conn, ps);
        }
        return null;
    }

启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
查看当前的隔离级别: SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:  
set  transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;

JDBC批量执行

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

/**
     * 向mysql的testJ数据表中插入100000条记录
     * 测试如何插入用时最短
     * 版本一:使用Statement
     */

版本一:我们使用Statement进行事务的操作

@Test
    public void testBatchWithStatement(){
        Connection connection=null;
        Statement statement=null;
        String sql;
        try {
            connection=JDBCTools.getConnection();
            //放到一个事务里面
            JDBCTools.beginTx(connection);
            statement=connection.createStatement();
            long begin=System.currentTimeMillis();
            for(int i=0;i<100000;i++){
                sql="insert into testj values("+
                (i+1)+", 'name_"+ i+"', '2016-05-08')";
                statement.execute(sql);
            }
            long end=System.currentTimeMillis();
            System.out.println("Time:"+(end-begin));
            JDBCTools.commit(connection);
        } catch (Exception e) {
            e.printStackTrace();
            JDBCTools.rollback(connection);
        }finally{
            JDBCTools.release(null, statement, connection);
        }
    }

运行结果:

Time:8991  

结论一:我们使用Statement插入100000条记录用时8991;

版本二:我们使用PreparedStatement进行事务的操作

@Test
    public void testBatchWithPreparedStatement() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        String sql;
        try {
            connection = JDBCTools.getConnection();
            // 放到一个事务里面
            JDBCTools.beginTx(connection);
            sql = "isnert into testJ values(?,?,?)";
            preparedStatement = connection.prepareStatement(sql);
            long begin = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                preparedStatement.setInt(1, i + 1);
                preparedStatement.setString(2, "name_" + i);
                preparedStatement.setDate(3,
                        new Date(new java.util.Date().getTime()));
                preparedStatement.execute();
            }
            long end = System.currentTimeMillis();
            System.out.println("Time:" + (end - begin));
            JDBCTools.commit(connection);
        } catch (Exception e) {
            e.printStackTrace();
            JDBCTools.rollback(connection);
        } finally {
            JDBCTools.release(null, preparedStatement, connection);
        }
    }

运行结果:
Time:8563

结论2:因为我这里使用的是mysql数据库进行的操作,插入大量数据的时间性能方面的影响不是很大,如果我们换成oracle数据库或其他大型的关系型数据库,事务执行用时相比版本一的1/4;

版本三:批处理插入数据

@Test
    public void testBatchWithBatch() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        String sql=null;
        try {
            connection = JDBCTools.getConnection();
            // 放到一个事务里面
            JDBCTools.beginTx(connection);
            sql = "insert into testJ values(?,?,?)";
            preparedStatement = connection.prepareStatement(sql);
            long begin = System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                preparedStatement.setInt(1, i + 1);
                preparedStatement.setString(2, "name_" + i);
                preparedStatement.setDate(3,
                        new Date(new java.util.Date().getTime()));
                //积攒SQL
                preparedStatement.addBatch();
                //当积攒到一定程度,就统一执行,并且清空先前积攒的SQL
                if((i+1)%300==0){
                    //执行
                    preparedStatement.executeBatch();
                    //清空
                    preparedStatement.clearBatch();
                }
            }
            //如果插入的记录数不是300的整倍数,再执行一次
            if(100000%300!=0){
                //执行
                preparedStatement.executeBatch();
                //清空
                preparedStatement.clearBatch();
            }
            long end = System.currentTimeMillis();
            System.out.println("Time:" + (end - begin));
            JDBCTools.commit(connection);
        } catch (Exception e) {
            e.printStackTrace();
            JDBCTools.rollback(connection);
        } finally {
            JDBCTools.release(null, preparedStatement, connection);
        }
    }

运行结果:4587(又提高了,但是还是不明显)
结论三:批处理事务建议采用版本三的方式,再次建议使用oracle数据库做这个插入数据事务的实验,mysql小数据还成,大量的数据也真呵呵了;

猜你喜欢

转载自blog.csdn.net/m2606707610/article/details/83590792
今日推荐