• 数据库-事务


    (一)事务概述

    1.什么是事务

    一件事情有n个组成单元 要不这n个组成单元同时成功 要不n个单元就同时失败

    就是将n个组成单元放到一个事务中

    2.mysql的事务

    默认的事务:一条sql语句就是一个事务 默认就开启事务并提交事务

    手动事务:

    1)显示的开启一个事务:start transaction

    2)事务提交:commit代表从开启事务到事务提交 中间的所有的sql都认为有效 真正的更新数据库

    3)事务的回滚:rollback 代表事务的回滚 从开启事务到事务回滚 中间的所有的   sql操作都认为无效数据库没有被更新

    (二)JDBC事务操作

    默认是自动事务:

    执行sql语句:executeUpdate()  ---- 每执行一次executeUpdate方法 代表      事务自动提交

    通过jdbc的API手动事务:

    开启事务:conn.setAutoComnmit(false);

    提交事务:conn.commit();

    回滚事务:conn.rollback();

    注意:控制事务的connnection必须是同一个

    执行sql的connection与开启事务的connnection必须是同一个才能对事务进行控制

    //通过jdbc去控制事务
    
          Connection conn = null;
    
          //1、注册驱动
    
          try {
    
             Class.forName("com.mysql.jdbc.Driver");
    
             //2、获得connection
    
             conn = DriverManager.getConnection("jdbc:mysql:///web19", "root", "root");
    
            
    
             //手动开启事务
    
             conn.setAutoCommit(false);
    
            
    
             //3、获得执行平台
    
             Statement stmt = conn.createStatement();
    
            
    
             //4、操作sql
    
             stmt.executeUpdate("update account set money=5000 where name='tom'");
    
            
    
            
    
             //提交事务
    
             conn.commit();
    
            
    
             stmt.close();
    
             conn.close();
    
          } catch (Exception e) {
    
             try {
    
                conn.rollback();
    
             } catch (SQLException e1) {
    
                e1.printStackTrace();
    
             }
    
             e.printStackTrace();
    
          }

    (三)DBUtils事务操作

    1.QueryRunner

    有参构造:QueryRunner runner = new QueryRunner(DataSource dataSource);

    有参构造将数据源(连接池)作为参数传入QueryRunner,QueryRunner会从连 接池中获得一个数据库连接资源操作数据库,所以直接使用无Connection参数  的update方法即可操作数据库

    无参构造:QueryRunner runner = new QueryRunner();

    无参的构造没有将数据源(连接池)作为参数传入QueryRunner,那么我们在使    用QueryRunner对象操作数据库时要使用有Connection参数的方法

    Connection conn = null;
    
          try {
    
             QueryRunner runner = new QueryRunner();
    
            
    
             //runner.update("update account set money=15000 where name='tom'");
    
             //获得一个Connection
    
             conn = DataSourceUtils.getConnection();
    
            
    
             //开启事务
    
             conn.setAutoCommit(false);
    
            
    
             runner.update(conn, "update account set money=15000 where name='tom'");
    
             //提交或回滚事务
    
             conn.commit();
    
            
    
          } catch (SQLException e) {
    
             try {
    
                conn.rollback();
    
             } catch (SQLException e1) {
    
                e1.printStackTrace();
    
             }
    
             e.printStackTrace();
    
          }

    (四)Demo 转账

     

    部分代码

    注意:事务的处理是在service层面

    Jsp

       

     <form action="${pageContext.request.contextPath }/transfer" method="post">
    
           转出账户:<inputtype="text"name="out"><br>
    
           转入账户:<inputtype="text" name="in"><br>
    
           转账金额:<input type="text" name="money"><br>
    
           <input type="submit" value="确认转账"><br>
    
        </form>

    Web

         

     //接受转账的参数
    
          String out = request.getParameter("out");
    
          String in = request.getParameter("in");
    
          String moneyStr = request.getParameter("money");
    
          double money = Double.parseDouble(moneyStr);
    
         
    
          //调用业务层的转账方法
    
          TransferService service = new TransferService();
    
          boolean isTransferSuccess = service.transfer(out,in,money);
    
         
    
          response.setContentType("text/html;charset=UTF-8");
    
         
    
          if(isTransferSuccess){
    
             response.getWriter().write("转账成功!!!");
    
          }else{
    
             response.getWriter().write("转账失败!!!");
    
          }

    Service

    注意:处理事务是在这个层次,请看下面的注释.开启事务需要想dao层传递connection

    这样可以保证是同一个Connncetion.

    但是这样是不合理的,因为Connection应该是dao层出现,又要用同一个Connection

    请看下面知识点ThreadLocal

    public boolean transfer(String out, String in, double money) {
    
         
    
          TransferDao dao = new TransferDao();
    
         
    
          boolean isTranferSuccess = true;
    
          //Connection conn = null;
    
          try {
    
            
    
             //开启事务
    
             conn = DataSourceUtils.getConnection();
    
             conn.setAutoCommit(false);
    
             //转出钱的方法 
    
             dao.out(conn,out,money);
    
             //int i = 1/0;
    
             //转入钱的方法
    
             dao.in(conn,in,money);
    
            
    
          } catch (Exception e) {
    
             isTranferSuccess = false;
    
             //回滚事务
    
             try {
    
                conn.rollback();
    
             } catch (SQLException e1) {
    
                e1.printStackTrace();
    
             }
    
             e.printStackTrace();
    
          } finally{
    
             try {
    
                conn.commit();
    
             } catch (SQLException e) {
    
                e.printStackTrace();
    
             }
    
          }
    
         
    
          return isTranferSuccess;
    
         
    
       }
    
    Dao
    
    public void out(Connection conn,String out, double money) throws SQLException {
    
          QueryRunner runner = new QueryRunner();
    
          String sql = "update account set money=money-? where name=?";
    
          runner.update(conn, sql, money,out);
    
       }
    
     
    
       public void in(Connection conn,String in, double money) throws SQLException {
    
          QueryRunner runner = new QueryRunner();
    
          String sql = "update account set money=money+? where name=?";
    
          runner.update(conn, sql, money,in);
    
       }

    (五)使用ThreadLocal绑定连接资源

     

    更改上面的代码

    注意:和上面的代码是有区别的

    MyDataSourceUtils

    //获得Connection ----- 从连接池中获取
    
       private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    
      
    
       //创建ThreadLocal
    
       private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    
      
    
       //开启事务
    
       public static void startTransaction() throws SQLException{
    
          Connection conn = getCurrentConnection();
    
          conn.setAutoCommit(false);
    
       }
    
      
    
       //获得当前线程上绑定的conn
    
       public static Connection getCurrentConnection() throws SQLException{
    
          //从ThreadLocal寻找 当前线程是否有对应Connection
    
          Connection conn = tl.get();
    
          if(conn==null){
    
             //获得新的connection
    
             conn = getConnection();
    
             //将conn资源绑定到ThreadLocal(map)上
    
             tl.set(conn);
    
          }
    
          return conn;
    
       }
    
      
    
       public static Connection getConnection() throws SQLException{
    
          return dataSource.getConnection();
    
       }
    
     
    
       //回滚事务
    
       public static void rollback() throws SQLException {
    
          getCurrentConnection().rollback();
    
       }
    
     
    
       //提交事务
    
       public static void commit() throws SQLException {
    
          Connection conn = getCurrentConnection();
    
          conn.commit();
    
          //将Connection从ThreadLocal中移除
    
          tl.remove();
    
          conn.close();
    
         
    
       }

    Service

    public boolean transfer(String out, String in, double money) {
    
         
    
          TransferDao dao = new TransferDao();
    
         
    
          boolean isTranferSuccess = true;
    
          //Connection conn = null;
    
          try {
    
            
    
             //开启事务
    
             //conn = DataSourceUtils.getConnection();
    
             //conn.setAutoCommit(false);
    
            
    
             //开启事务
    
             MyDataSourceUtils.startTransaction();
    
            
    
             //转出钱的方法 
    
             dao.out(out,money);
    
             //int i = 1/0;
    
             //转入钱的方法
    
             dao.in(in,money);
    
            
    
          } catch (Exception e) {
    
             isTranferSuccess = false;
    
             //回滚事务
    
             try {
    
                MyDataSourceUtils.rollback();
    
             } catch (SQLException e1) {
    
                e1.printStackTrace();
    
             }
    
             e.printStackTrace();
    
          } finally{
    
             try {
    
                MyDataSourceUtils.commit();
    
             } catch (SQLException e) {
    
                e.printStackTrace();
    
             }
    
          }
    
         
    
          return isTranferSuccess;
    
         
    
       }

    Dao

    public void out(String out, double money) throws SQLException {
    
          QueryRunner runner = new QueryRunner();
    
          Connection conn = MyDataSourceUtils.getCurrentConnection();
    
          String sql = "update account set money=money-? where name=?";
    
          runner.update(conn, sql, money,out);
    
       }
    
     
    
       public void in(String in, double money) throws SQLException {
    
          QueryRunner runner = new QueryRunner();
    
          Connection conn = MyDataSourceUtils.getCurrentConnection();
    
          String sql = "update account set money=money+? where name=?";
    
          runner.update(conn, sql, money,in);
    
       }

    (六)事务的特性和隔离级别

    (概念性问题---面试)

    1.事务的特性ACID

             1)原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作           要么都发生,要么都不发生。 

            2)一致性(Consistency)一个事务中,事务前后数据的完整性必须保持一致。

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

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

    2.并发访问问题----由隔离性引起

    如果不考虑隔离性,事务存在3中并发访问问题。

    1)脏读:B事务读取到了A事务尚未提交的数据   ------  要求B事务要读取A事    务提交的数据

    2)不可重复读:一个事务中 两次读取的数据的内容不一致  ----- 要求的是一个事      务中多次读取时数据是一致的  --- unpdate

    3)幻读/虚读:一个事务中 两次读取的数据的数量不一致  ----- 要求在一个事务多    次读取的数据的数量是一致的 --insert  delete

    3.事务的隔离级别

    1)read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决

    2)read committed:读取已经提交的数据 :可以解决脏读 ---- oracle默认的

    3)repeatable read:重读读取:可以解决脏读 和 不可重复读 ---mysql默认的

    4)serializable:串行化:可以解决 脏读 不可重复读 和 虚读---相当于锁表

    注意:mysql数据库默认的隔离级别

    查看mysql数据库默认的隔离级别:select @@tx_isolation

    设置mysql的隔离级别:set session transaction isolation level 设置事务隔离级别


    (七)总结:

                    mysql的事务控制:

                           开启事务:start transaction;

                           提交:commit;

                           回滚:rollback;

                    JDBC事务控制:

                           开启事务:conn.setAutoCommit(false);

                            提交:conn.commit();

                            回滚:conn.rollback();

                      DBUtils的事务控制 也是通过jdbc

                          

                      ThreadLocal:实现的是通过线程绑定的方式传递参数

     

                      概念:

                           事务的特性ACID

                           并发问题:脏读、不可重读、虚读幻读

                           解决并发:设置隔离级别

                                 read uncommitted

                                 read committed

                                 repeatable read (mysql默认)

                                 serialazable

     

                           隔离级别的性能:

                                 read uncommitted>read committed>repeatable read>serialazable

                           安全性:

                                 read uncommitted<read committed<repeatable read<serialazable

     


    作者:8亩田
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.

    本文如对您有帮助,还请多帮 【推荐】 下此文。
    如果喜欢我的文章,请关注我的公众号
    如果有疑问,请下面留言

    学而不思则罔 思而不学则殆
  • 相关阅读:
    21. Merge Two Sorted Lists
    496. Next Greater Element I
    (转载)深度学习的weight initialization
    Python collections模块
    Iterables vs. Iterators vs. Generators
    (转)iPhone开发关于UDID和UUID的一些理解
    uniqueIdentifier在ios7不支持后的替代方法
    Android——列表视图 ListView(一)Arrayadapter
    Android——对话框2(日期和时间对话框)
    Android——子线程操作主线程
  • 原文地址:https://www.cnblogs.com/liu-wang/p/8607135.html
Copyright © 2020-2023  润新知