第10天 Transaction事务

今日内容介绍

u 事务管理

u 转账案例

u 事务总结

第1章   事务管理

1.1  事务概述

l  事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.

l  事务作用:保证在一个事务中多次操作要么全都成功,要么全都失败.

1.2  mysql事务操作

sql语句

描述

start transaction;

开启事务

commit;

提交事务

rollback;

回滚事务

 

l  准备数据

# 创建一个表:账户表.

create database webdb;

# 使用数据库

use webdb;

# 创建账号表

create table account(

       id int primary key auto_increment,

       name varchar(20),

       money double

);

# 初始化数据

insert into account values (null,'jack',10000);

insert into account values (null,'rose',10000);

insert into account values (null,'tom',10000);

 

l  操作:

n  MYSQL中可以有两种方式进行事务的管理:

u  自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。

u  手动提交:先开启,再提交

n  方式1:手动提交

start transaction;

update account set money=money-1000 where name='守义';

update account set money=money+1000 where name='凤儿';

commit;

#或者

rollback;

n  方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制

show variables like '%commit%';

 

* 设置自动提交的参数为OFF:

set autocommit = 0;  -- 0:OFF  1:ON

 

l  扩展:Oracle数据库事务不自动提交

 

1.3  JDBC事务操作

Connection对象的方法名

描述

conn.setAutoCommit(false)

开启事务

conn.commit()

提交事务

conn.rollback()

回滚事务

1.3.1 案例代码一

JdbcTxDemo_01.java

package com.itheima_01_jdbc;

 

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.SQLException;

 

import org.junit.Test;

 

import com.itheima.utils.C3P0Utils;

 

public class JdbcTxDemo_01 {

   

    @Test

    public void demo01() throws SQLException{

       

        //模板

       

        Connection conn = null;

        try {

            // 1 获得连接

            conn = C3P0Utils.getConnection();

            // 2 开启事务

            conn.setAutoCommit(false);

           

            //.....

           

            // 3 提交事务

            conn.commit();

           

        } catch (Exception e) {

            // 4 回滚事务

                conn.rollback();

        } finally{

            // 5 释放资源

            if(conn != null){

                conn.close();

            }

        }

       

    }

   

    @Test

    public void demo02() throws SQLException{

       

        //转账

       

        Connection conn = null;

        PreparedStatement psmt = null;

       

        try {

            // 1 获得连接

            conn = C3P0Utils.getConnection();

            // 2 开启事务

            conn.setAutoCommit(false);

           

            String sql = "update account set money=money+? where name=?";

            //具体操作

            psmt = conn.prepareStatement(sql);

            // @1 汇款

            psmt.setInt(1, -100);

            psmt.setString(2, "jack");

            psmt.executeUpdate();

           

            //模拟停电

            int i = 1/0;

           

            // @2 收款

            psmt.setInt(1, 100);

            psmt.setString(2, "rose");

            psmt.executeUpdate();

           

           

            // 3 提交事务

            conn.commit();

        } catch (Exception e) {

            // 4 回滚事务

            conn.rollback();

           

            throw new RuntimeException("程序回滚",e);

        } finally{

            // 5 释放资源

            if(conn != null){

                conn.close();

            }

        }

    }

}

 

1.4  DBUtils事务操作

Connection对象的方法名

描述

conn.setAutoCommit(false)

开启事务

new QueryRunner()

创建核心类,不设置数据源(手动管理连接)

query(conn , sql , handler, params )  或

update(conn, sql , params)

手动传递连接

DbUtils.commitAndClose(conn)  或

DbUtils.rollbackAndClose(conn)

提交并关闭连接

回顾并关闭连接

 

1.4.1 案例代码二

DbUtilsTxDemo_01.java

package com.itheima_02_dbutils;

 

import java.sql.Connection;

 

import org.apache.commons.dbutils.DbUtils;

import org.apache.commons.dbutils.QueryRunner;

import org.junit.Test;

 

import com.itheima.utils.C3P0Utils;

 

public class DbUtilsTxDemo_01 {

   

    @Test

    public void demo01(){

        // 使用DBUtils 模板

        Connection conn = null;

        try {

            // 获得连接

            conn = C3P0Utils.getConnection();

            // 开启事务

            conn.setAutoCommit(false);

           

            //.....

           

           

            // 提交并释放资源

            DbUtils.commitAndCloseQuietly(conn);

        } catch (Exception e) {

            // 回滚并释放资源

            DbUtils.rollbackAndCloseQuietly(conn);

        }

    }

 

    @Test

    public void demo02(){

        // 转账

        Connection conn = null;

        QueryRunner queryRunner = new QueryRunner();

        try {

            // 获得连接

            conn = C3P0Utils.getConnection();

            // 开启事务

            conn.setAutoCommit(false);

           

            String sql = "update account set money=money+? where name=?";

           

            //@1汇款

            int r = queryRunner.update(conn, sql, -100,"jack");

            //@2收款

            int r2 = queryRunner.update(conn, sql, 100 , "rose");

           

           

            // 提交并释放资源

            DbUtils.commitAndCloseQuietly(conn);

            System.out.println("提交成功");

        } catch (Exception e) {

            e.printStackTrace();

            // 回滚并释放资源

            DbUtils.rollbackAndCloseQuietly(conn);

            System.out.println("程序回滚");

        }

    }

}

 

第2章   转账案例

2.1  案例分析

 

l  开发中,常使用分层思想

n  不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统

n  不同层级结构彼此平等

n  分层的目的是:

u  解耦

u  可维护性

u  可扩展性

u  可重用性

l  不同层次,使用不同的包表示

n  com.itheima           公司域名倒写

n  com.itheima.dao              dao层

n  com.itheima.service  service层

n  com.itheima.domain javabean

n  com.itheima.utils             工具

 

2.2  代码实现

l  步骤1:编写入口程序

public static void main(String[] args) {

              try {

                     String outUser = "jack";

                     String inUser = "rose";

                     Integer money = 100;

                     //2 转账

                     AccountService accountService = new AccountService();

                     accountService.transfer(outUser, inUser, money);

                     //3 提示

                     System.out.println("转账成功");

              } catch (Exception e) {

                     System.out.println("转账失败");

              }

       }

 

l  步骤3:编写AccountService

public class AccountService {

 

       /**

        * 业务层转账的方法:

        * @param from    :付款人

        * @param to :收款人

        * @param money :转账金额

        */

       public void transfer(String from, String to, double money) {

              // 调用DAO:

              AccountDao accountDao = new AccountDao();

              try {

                     accountDao.outMoney(from, money);

                     // int d = 1/0;

                     accountDao.inMoney(to, money);

              } catch (SQLException e) {

                     e.printStackTrace();

              }

             

       }

      

}

 

步骤4:编写AccountDao.java

public class AccountDao {

 

       /**

        * 付款的方法

        * @param name

        * @param money

        * @throws SQLException

        */

       public void outMoney(String name,double money) throws SQLException{

              Connection conn = null;

              PreparedStatement pstmt = null;

              try{

                     // 获得连接:

                     conn = JDBCUtils.getConnection();

                     // 编写一个SQL:

                     String sql = "update account set money = money-? where name=?";

                     // 预编译SQL:

                     pstmt = conn.prepareStatement(sql);

                     // 设置参数:

                     pstmt.setDouble(1, money);

                     pstmt.setString(2, name);

                     // 执行SQL:

                     pstmt.executeUpdate();

              }catch(Exception e){

                     e.printStackTrace();

              }finally{

                     pstmt.close();

                     conn.close();

              }

       }

      

       /**

        * 收款的方法

        * @param name

        * @param money

        * @throws SQLException

        */

       public void inMoney(String name,double money) throws SQLException{

              Connection conn = null;

              PreparedStatement pstmt = null;

              try{

                     // 获得连接:

                     conn = JDBCUtils.getConnection();

                     // 编写一个SQL:

                     String sql = "update account set money = money+? where name=?";

                     // 预编译SQL:

                     pstmt = conn.prepareStatement(sql);

                     // 设置参数:

                     pstmt.setDouble(1, money);

                     pstmt.setString(2, name);

                     // 执行SQL:

                     pstmt.executeUpdate();

              }catch(Exception e){

                     e.printStackTrace();

              }finally{

                     pstmt.close();

                     conn.close();

              }            

       }

}

 

 

2.3  事务管理:传递Connection

l  修改service和dao,service将connection传递给dao,dao不需要自己获得连接

 

2.3.1 service层

       public void transfer(String outUser,String inUser,int money){

              Connection conn =null;

              try{

                     //1 获得连接

                     conn = JdbcUtils.getConnection();

                     //2 开启事务

                     conn.setAutoCommit(false);

                    

                     accountDao.outMoney(conn,outUser, money);

                     //断电

                     //int i = 1 / 0;

                     accountDao.inMoney(conn,inUser, money);

                    

                     //3 提交事务

                     conn.commit();

              } catch (Exception e) {

                     try {

                            //回顾

                            if (conn != null) {

                                   conn.rollback();

                            }

                     } catch (Exception e2) {

                     }

                     throw new RuntimeException(e);

              } finally{

                     JdbcUtils.closeResource(conn, null, null);

              }

             

       }

 

2.3.2 dao层

/**

        * 汇款

        * @param outUser 汇款人

        * @param money -

        */

       public void outMoney(Connection conn, String outUser , int money){

              //Connection conn = null;

              PreparedStatement psmt = null;

              ResultSet rs = null;

              try {

                     //1 获得连接

                     //conn = JdbcUtils.getConnection();

                     //2 准备sql语句

                     String sql = "update account set money = money - ? where username = ?";

                     //3预处理

                     psmt = conn.prepareStatement(sql);

                     //4设置实际参数

                     psmt.setInt(1, money);

                     psmt.setString(2, outUser);

                     //5执行

                     int r = psmt.executeUpdate();

                     System.out.println(r);

              } catch (Exception e) {

                     throw new RuntimeException(e);

              } finally{

                     //6释放资源

                     JdbcUtils.closeResource(null, psmt, rs);

              }

       }

       /**

        * 收款

        * @param inUser 收款人

        * @param money +

        */

       public void inMoney(Connection conn,String inUser , int money){

              //Connection conn = null;

              PreparedStatement psmt = null;

              ResultSet rs = null;

              try {

                     //1 获得连接

                     //conn = JdbcUtils.getConnection();

                     //2 准备sql语句

                     String sql = "update account set money = money + ? where username = ?";

                     //3预处理

                     psmt = conn.prepareStatement(sql);

                     //4设置实际参数

                     psmt.setInt(1, money);

                     psmt.setString(2, inUser);

                     //5执行

                     int r = psmt.executeUpdate();

                     System.out.println(r);

              } catch (Exception e) {

                     throw new RuntimeException(e);

              } finally{

                     //6释放资源

                     JdbcUtils.closeResource(null, psmt, rs);

              }

       }

 

2.4  提高:ThreadLocal

2.4.1 案例介绍

在“事务传递参数版”中,我们必须修改方法的参数个数,传递链接,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。

 

2.4.2 相关知识:ThreadLocal

java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是一个Map,key存放的当前线程,value存放需要共享的数据。

 

 

2.4.3 分析

 

 

2.4.4 实现

2.4.4.1       工具类JDBCUtils

//连接池

private static ComboPooledDataSource dataSource = new ComboPooledDataSource("itcast");

       //给当前线程绑定 连接

       private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();

      

       /**

        * 获得连接

        * @return

        */

       public static Connection getConnection(){

              try {

                     //#1从当前线程中, 获得已经绑定的连接

                     Connection conn = local.get();

                     if(conn == null){

                            //#2 第一次获得,绑定内容 – 从连接池获得

                            conn = dataSource.getConnection();

                            //#3 将连接存 ThreadLocal

                            local.set(conn);

                     }

                     return conn; //获得连接

              } catch (Exception e) {

                     //将编译时异常 转换 运行时 , 以后开发中 运行时异常使用比较多的。

                     // * 此处可以编写自定义异常。

                     throw new RuntimeException(e);

                     // * 类与类之间 进行数据交换时,可以使用return返回值。也可以自定义异常返回值,调用者try{} catch(e){ e.getMessage() 获得需要的数据}

                     //throw new MyConnectionException(e);

              }

       }

 

2.4.4.2       service层

       public void transfer(String outUser,String inUser,int money){

             

              Connection conn =null;

              try{

                     //1 获得连接

                     conn = JdbcUtils.getConnection();

                     //2 开启事务

                     conn.setAutoCommit(false);

                    

                     accountDao.out(outUser, money);

                     //断电

                     //int i = 1 / 0;

                     accountDao.in(inUser, money);

                    

                     //3 提交事务

                     conn.commit();

              } catch (Exception e) {

                     try {

                            //回顾

                            if (conn != null) {

                                   conn.rollback();

                            }

                     } catch (Exception e2) {

                     }

                     throw new RuntimeException(e);

              } finally{

                     JdbcUtils.closeResource(conn, null, null);

              }

             

             

       }

 

2.4.4.3       dao层

       /**

        * 汇款

        * @param outUser 汇款人

        * @param money -

        */

       public void out(String outUser , int money){

              Connection conn = null;

              PreparedStatement psmt = null;

              ResultSet rs = null;

              try {

                     //1 获得连接

                     conn = JdbcUtils.getConnection();

                     //2 准备sql语句

                     String sql = "update account set money = money - ? where username = ?";

                     //3预处理

                     psmt = conn.prepareStatement(sql);

                     //4设置实际参数

                     psmt.setInt(1, money);

                     psmt.setString(2, outUser);

                     //5执行

                     int r = psmt.executeUpdate();

                     System.out.println(r);

              } catch (Exception e) {

                     throw new RuntimeException(e);

              } finally{

                     //6释放资源--不能关闭连接

                     JdbcUtils.closeResource(null, psmt, rs);

              }

       }

       /**

        * 收款

        * @param inUser 收款人

        * @param money +

        */

       public void in(String inUser , int money){

              Connection conn = null;

              PreparedStatement psmt = null;

              ResultSet rs = null;

              try {

                     //1 获得连接

                     conn = JdbcUtils.getConnection();

                     //2 准备sql语句

                     String sql = "update account set money = money + ? where username = ?";

                     //3预处理

                     psmt = conn.prepareStatement(sql);

                     //4设置实际参数

                     psmt.setInt(1, money);

                     psmt.setString(2, inUser);

                     //5执行

                     int r = psmt.executeUpdate();

                     System.out.println(r);

              } catch (Exception e) {

                     throw new RuntimeException(e);

              } finally{

                     //6释放资源--注意:不能关闭链接

                     JdbcUtils.closeResource(null, psmt, rs);

              }

       }

 

第3章   事务总结

3.1  事务特性:ACID

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

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

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

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

 

3.2  并发访问问题

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

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

 

3.3  隔离级别:解决问题

l  数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。

  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。

a)     存放:3个问题(脏读、不可重复读、虚读)。

b)     解决:0个问题

  1. read committed 读已提交,一个事务读到另一个事务已经提交的数据。

a)     存放:2个问题(不可重复读、虚读)。

b)     解决:1个问题(脏读)

  1. repeatable read  :可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。

a)     存放:1个问题(虚读)。

b)     解决:2个问题(脏读、不可重复读)

  1. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。

a)     存放:0个问题。

b)     解决:3个问题(脏读、不可重复读、虚读)

l  安全和性能对比

n  安全性:serializable > repeatable read > read committed > read uncommitted

n  性能 : serializable < repeatable read < read committed < read uncommitted

l  常见数据库的默认隔离级别:

n  MySql:repeatable read

n  Oracle:read committed

 

 

3.4  演示

l  隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】

l  查询数据库的隔离级别

show variables like '%isolation%';

select @@tx_isolation;

 

l  设置数据库的隔离级别

n  set session transaction isolation level 级别字符串

u  级别字符串:read uncommitted、read committed、repeatable read、serializable

n  例如:set session transaction isolation level read uncommitted;

 

 

l  读未提交:read uncommitted

n  A窗口设置隔离级别

n  AB同时开始事务

n  A 查询

n  B 更新,但不提交

n  A 再查询?-- 查询到了未提交的数据

n  B 回滚

n  A 再查询?-- 查询到事务开始前数据

 

l  读已提交:read committed

n  A窗口设置隔离级别

n  AB同时开启事务

n  A查询

n  B更新、但不提交

n  A再查询?--数据不变,解决问题【脏读】

n  B提交

n  A再查询?--数据改变,存在问题【不可重复读】

 

l  可重复读:repeatable read

n  A窗口设置隔离级别

n  AB 同时开启事务

n  A查询

n  B更新, 但不提交

n  A再查询?--数据不变,解决问题【脏读】

n  B提交

n  A再查询?--数据不变,解决问题【不可重复读】

n  A提交或回滚

n  A再查询?--数据改变,另一个事务

 

l  串行化:serializable

n  A窗口设置隔离级别

n  AB同时开启事务

n  A查询

n  B更新?--等待(如果A没有进一步操作,B将等待超时)

n  A回滚

n  B 窗口?--等待结束,可以进行操作