数据库-事务

(一)事务概述

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

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

学而不思则罔 思而不学则殆
原文地址:https://www.cnblogs.com/liu-wang/p/8607135.html