zoukankan      html  css  js  c++  java
  • 10.09JavaWeb之JDBC后程

    10.09JavaWeb之JDBC后程

    后程事务

    • 数据库事务

    • DAO及其实现类

    • 数据库连接池

    • Apache-DButils实现CRUD操作

      • QueryRunner类的使用


    数据库事务

    try...catch...throws使用的选择:

    • 单独的方法出现的异常使用throws

    • 当多个方法组合使用的时候使用try...catch...,当方法一抛出异常的时候捕获异常执行方法二

    经典模拟场景--->转账

    AA给BB转账--->数据库事务

        /*
       1、针对于数据表账户表
       2、用户AA给用户BB转账100
       3、sql语句
           1、update user_table set balance = balance - 100 where user = 'AA';
           2、update user_table set balance = balance + 100 where user = 'BB';
        */
       @Test
       public void testUpdate() {
           String sql1 = "update user_table set balance = balance - 100 where user = ?;";
           update(sql1, "AA");

           String sql2 = "update user_table set balance = balance + 100 where user = ?;";
           update(sql2, "BB");

           System.out.println("操作成功!");
      }

    如果发生了异常,那么就会导致数据的最终不一致

        /*
       1、针对于数据表账户表
       2、用户AA给用户BB转账100
       3、sql语句
           1、update user_table set balance = balance - 100 where user = 'AA';
           2、update user_table set balance = balance + 100 where user = 'BB';
        */
       @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");

           System.out.println("操作成功!");
      }
    /*
    如果出现这样的操作没有使用事务进行数据操作,就会导致AA少了100,BB没有增加100
    */

    事务的概念:

    • 一组逻辑操作单元,使数据从一种状态变换到另一种状态--->称为一个数据库事务

    事务处理(事务操作)的特点:

    • 一个事务执行多个操作时,要么所有的事务都被提交(Commit)

    • 有一个事务提交失败整个事务就进行回滚(Rollback)

    确保数据的一致性的特点:

    • 数据的操纵应当是离散的成组的逻辑单元

    • 整个单元当中有一部分失败应当视为事务的失败,数据全部回滚

    /*
    什么是数据库事务?
    1、一组逻辑操作单元:一个或多个DML操作
       1、刚才的AA给BB转账:涉及两个DML操作。是一个事务
       2、AA给BB转账,CC给DD转账这是两个事务
       3、AA给BB转账,CC给BB转账这也是两个事务。
    2、事务处理的原则:
       1、事务作为一个工作单元执行,出现了故障也不能改变这种执行方式--->整体提交or事务回滚
    事务回滚机制:
    1、事务回滚只回滚到最近的commit操作--->数据一旦提交就不可回滚
    2、需要关注哪些操作会导致数据的自动提交
       1、DDL操作一旦执行都会自动提交--->set autocommit = false对于DDL操作失效(DDL操作--->创建一个表、修改一个表中的字段、删除一张表独立的就相当于是一个事务(DDL操作)
       2、DML(增删改查)默认的情况下,一旦执行,自动提交--->可以通过set autocommit = false的方式取消DML操作的自动提交
       3、关闭连接的时候也会将没有提交的数据进行自动提交--->关闭连接会进行一次commit--->在执行(增删改查)的操作的时候不需要关闭数据库连接。保证多个DML操作作为一个事务进行提交
           1、获取链接
           2、进行第一个DML操作
           3、不关闭连接进行第二个DML操作
           调用方法的时候外部传入一个数据库连接
       防止事务的自动提交就要避免上诉的三件事
    */

    示例代码:

    统一的驱动连接调用

        //外部传入连接
       //通用的增删改查方法--->version 1.0
       public int update(Connection conn,String sql, Object ...args) {
           PreparedStatement ps = null;
           try {
               //获取链接
               conn = JDBCUtils.getConnection();
               //预编译sql
               ps = conn.prepareStatement(sql);
               //填充占位符--->使用流的形式
               for (int i = 0; i < args.length; i++) {
                   ps.setObject(i + 1, args[i]);
              }
               //执行语句
               return ps.executeUpdate();
          }catch (Exception e) {
               e.printStackTrace();
          }finally {
               //关闭资源
               /*
               1、由于是外部传入的连接,所以不需要关闭Connection连接
                */
               JDBCUtils.closeResource(null, ps);
          }
           return 0;
      }

    操作过程:

        //#########考虑数据库事务后的转账操作##########
       @Test
       public void testUpdateWithTransaction() {
           Connection conn = null;
           try {
               //获取连接
               conn = JDBCUtils.getConnection();
               System.out.println(conn.getAutoCommit());
               //设置取消事务的自动提交
               conn.setAutoCommit(false);
               System.out.println(conn.getAutoCommit());

               String sql1 = "update user_table set balance = balance - 100 where user = ?;";
               update(conn, sql1, "AA");

               //模拟异常

               String sql2 = "update user_table set balance = balance + 100 where user = ?;";
               update(conn, sql2, "BB");

               System.out.println("操作成功!");

               //提交事务
               conn.commit();
          }catch (Exception e) {
               e.printStackTrace();
               //异常之后回滚事务
               try {
                   conn.rollback();
              }catch (SQLException e1) {
                   System.out.println("事务回滚异常!");
                   e1.printStackTrace();
              }
          }finally {
               //将自动提交改成默认值
               try {
                   conn.setAutoCommit(true);
              }catch (SQLException e2) {
                   e2.printStackTrace();
              }
               //统一关闭资源
               JDBCUtils.closeResource(conn, null);
          }
      }

    注意:

    • 如果Connection没有被关闭,需要恢复自动提交状态。使用setAutoCommit(true)方法。

    • 使用数据库连接池的时候执行close()方法前需恢复自动提交状态

    数据库事务的ACID属性

    • 原子性(Atomicity)

    • 一致性(Consistency)

    • 隔离性(Isolation)

    • 持久性(Durability)


    原子性

    概念:

    事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生

    一致性

    概念:

    事务必须使数据库从一个一致性状态变换到另一个一致性状态

    隔离性

    概念:

    一个事务的执行不能被其他事务干扰。

    • 一个事务内部的操作及使用的数据并对并发的其他事务是隔离的

    • 并发执行的各个事务之间不能互相干扰

    特点:

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

    • 隔离级别越高,数据一致性越好,并发性越弱

    持久性

    数据库的并发问题:

    发生场景:

    同时运行多个事务,事务访问数据库的相同数据。没有采取隔离机制就会导致并发问题。

    并发问题分类:

    • 脏读:两个事务T1和T2。T1读取已经被T2更新但还没有被提交的字段。此时T2进行回滚操作。T1读取到的数据就是临时且无效的。

    • 不可重复读:两个事务T1和T2。T1读取了一个字段,然后T2更新了该字段,T1再次读取同一个字段,值就不同了--->针对update操作

    • 幻读:两个事务T1和T2。T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,如果T1再次读同一个表,会多出几行。--->针对insert操作


    四种隔离级别
    隔离级别描述
    Read Uncommitted 允许事务读取未被其他事务提交的变更。脏读、不可重复读、幻读问题都会出现
    Read Commited 只允许事务读取已经被其他事务提交的变更。可避免脏读但不可重复读和幻读问题仍可能出现
    Repeatable Read(可重复读) 确保事务可以多次从一个字段中读取相同的值。在该事务持续期间禁止其他事物对该字段进行更新。避免脏读、不可重复读但幻读仍然存在
    Serializable(串行化) 确保事务可以从一个表中读取相同的行,事务持续期间禁止其他事物对该表执行插入、更新、删除操作所。所有并发问题都可以避免,但性能十分低下

    MySql默认的事务隔离级别:

    Repeatable Read--->并发性较差,一致性较好

    持久性

    概念:

    一个事务一旦被提交,对数据库中数据的改变是永久性的。接下来的其他操作和数据库故障不会对其有任何影响

    MySql中设置隔离级别

    启动mysql的特点:

    • 每启动一个mysql程序就会获得一个单独的数据库连接

    • 每个数据库连接都有一个全局变量@@tx_isolation表示当前的事务隔离级别

    查看当前的隔离级别:

    SELECT @@tx_isolation;

    设置当前mysql链接的隔离级别:

    set transaction isolation level read committed;
    /*
    set + 事务单词(transaction) + 隔离级别单词(isolation level) + 要设置成的隔离级别单词(read committed)
    */

    设置数据库系统的全局的隔离级别:

    set global transaction isolation level read committed;
    /*
    原有的语法上在事务前面加上adj.全面的
    此时需要退出客户端在重新进入客户端
    */

    创建数据库用户:

    create user xxx identified by aaa;
    #设置用户名:xxx,密码:aaa
    #这时候只会默认提供一个数据库的权限给到该用户--->information_schema

    授予权限:

    #授权通过网络方式登录xxx账户,有所有库所有表的全部权限,设置密码
    grant all privileges on *.* to xxx@'%' identified by '密码';

    #给xxx用户使用本地命令行方式,授予test这个库下的所有表的增删改查权限
    grant select,insert,delete,update on xxx.* to xxx@localhost identified by '密码'
    /*
    grant关键字 + 要赋予的权限 + 数据库.表名(*代表全部表) + to(表示给到哪个用户) + 用户名 + identified by + 密码
    */

    先设置事务不自动提交:

    set autocommit = false;

    在进行查询(此时的事务并没有自动提交):

    #使用root用户进行查询--->已经设置autocommit = false;
    select * from user_table where `user` = 'cc';
    #在使用junkingboy用户进行修改--->已经设置autocommit = false;其目的是为了测试mysql的默认事务隔离级别
    update user_table set `balance` = 3000 where `user` = 'cc';
    #此时事务未曾提交。root用户查出来的结果还是2000,junkingboy用户可以查出结果为3000。
    #因为mysql默认的隔离级别是可重复读。所以即便junkingboy提交了事务但是root没有提交事务root用户查询的结果依旧不会是3000而是2000.
    #使用root用户提交事务,这样就可以查询出junkingboy用户修改以后的数据

    修改全局事务的隔离级别:

    set global transaction isolation level read committed;
    #重新进入两个用户的客户端
    #设置不自动提交事务
    #在junkingboy用户中提交update事务,然后再root用户中再进行读取
    #root用户未提交事务,junkingboy用户提交了事务。那么再root得事务当中可以读取到junkingboy提交以后得数据。
    #没有解决不可重复读得问题

    再次修改全局事务隔离级别:

    set global transaction isolation level read uncommitted;
    #重新进入客户端
    #设置不自动提交事务
    #在junkingboy用户中提交update事务,然后再root用户中再进行读取
    #root用户未提交事务,junkingboy用户也未提交了事务。那么再root得事务当中可以读取到junkingboy提交以后得数据。
    #没有解决脏读的问题

    Java代码层面设置事务隔离级别:--->该设置的隔离级别是针对连接的全局隔离级别

    统一的查询方法:

    /*
    1、将其设置为事务的处理方式。--->将事务作为参数传入函数当中
    注意:
    1、不要再次创建链接Connection
    2、关闭的时候不要关闭连接
    通用查询操作,用于返回数据表中的一条数据(考虑事务操作)
    */
    public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object ...args) {
    // Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
    // //获取数据库连接
    // conn = JDBCUtils.getConnection();
    //预编译sql
    ps = conn.prepareStatement(sql);
    //填充占位符
    for (int i=0; i<args.length; i++) {
    ps.setObject(i+1, args[i]);
    }
    //执行sql保存为结果集对象
    rs = ps.executeQuery();

    //获取结果集元数据
    ResultSetMetaData rsmd = rs.getMetaData();
    //获取列数
    int columnCount = rsmd.getColumnCount();
    //获取结果集
    if (rs.next()) {
    //通过反射获取运行时加载类建立对象的引用--->反射+泛型
    T t = clazz.newInstance(); //--->任何一个类在提供一个JavaBean对象的时候要提供一个空参的public权限的构造器,在这里使用
    /*
    方法当中返回一个t
    t由当前类决定的
    */
    //动态的获取列,列的数量为列数
    for (int j=0; j<columnCount; j++) {
    //动态的获取列值--->结果集当中获取列值
    Object columnValue = rs.getObject(j+1);
    //获取每列的列名
    String columnLabel = rsmd.getColumnLabel(j+1);
    //动态获取加载的类的属性--->获取到域(T类型的)
    Field field = clazz.getField(columnLabel);
    //设置私有属性可访问
    field.setAccessible(true);
    //将对象属性设置成列值
    field.set(t, columnValue);
    }
    return t;
    }
    }catch (Exception e) {
    e.printStackTrace();
    }finally {
    //关闭资源--->注意不要关闭连接
    JDBCUtils.closeResource(null, ps, rs);
    }
    return null;
    }

    查询操作:

        //针对同一张表的同一个数据进行操作。演示事务隔离级别
    @Test
    public void testTransactionSelect() throws Exception {
    Connection conn = JDBCUtils.getConnection();

    //查看数据库隔离级别
    System.out.println(conn.getTransactionIsolation());

    //设置数据库的隔离级别
    conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

    //取消自动提交事务
    conn.setAutoCommit(false);

    //调用查询方法。因为返回的是一个对象。所以需要新建一个JavaBean的类--->返回的是一个User类对象
    //sql语句
    String sql = "select user, password, balance from user_table where user = ?;";
    //注意填充占位符
    User user = getInstance(conn, User.class, sql, "cc");

    System.out.println(user);
    }

    修改操作:

        @Test
    public void testTransactionUpdate() throws Exception {
    //获取连接
    Connection conn = JDBCUtils.getConnection();

    //调用update方法
    TransactionTest tt = new TransactionTest();

    //取消自动提交事务
    conn.setAutoCommit(false);

    //sql
    String sql = "update user_table set balance = ? where user = ?;";
    tt.update(conn, sql, 5000,"CC");

    //线程等待15s
    Thread.sleep(15000);
    System.out.println("修改结束!");
    }

    理解脏读、不可重复读、幻读

    脏读

    脏读是指:

    两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1,未提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是B修改后的结果。

    幻读

    幻读是指:

    两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1并且提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是B修改后的结果。

    不可重复读

    不可重复读是指:

    两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1未曾提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是未修改的结果。此时B提交事务,A再去查询字段1(还在当前事务内)查询到的结果还是未修改的结果。

    只有当A也提交了事务并且再次查询才能够查询到已修改的数据

    It's a lonely road!!!
  • 相关阅读:
    Leetcode(337)-打家劫舍III
    Leetcode(213)-打家劫舍II
    Leetcode(198)-打家劫舍
    Leetcode(32)-最长有效括号
    计数排序
    智能指针
    C++中的explicit
    Leetcode(878)-第 N 个神奇数字
    Leetcode(877)-石子游戏
    C++的memset
  • 原文地址:https://www.cnblogs.com/JunkingBoy/p/15399217.html
Copyright © 2011-2022 走看看