事务操作

事务概述

● 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
● 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.

mysql事务操作

sql语句 描述

start transaction 开启事务–后面的操作是一组操作里面的sql语句不会马上生效
commit 提交事务–全部生效
rollback 回滚事务–全部失效
● 操作
○ MYSQL中可以有两种方式进行事务的管理:
■ 自动提交:MySql默认自动提交。即执行一条sql语句提交一次事务。
■ 手动提交:先开启,再提交

● 方式1:手动提交

–开启事务

start transaction;
update account set money=money-1000 where name=’jack’;
update account set money=money+1000 where name=’rose’;

–提交事务

commit;或者rollback;

● 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
show variables like ‘%commit%’; –> 查看事务是否提交
* 设置自动提交的参数为OFF:
set autocommit = 0; – 0:OFF 1:ON –> 手动设置提交方式

jdbc事务操作

Connection 对象的方法名 描述

conn.setAutoCommit(false) 开启事务–>false设置事务的手动提交 –>true设置事务的自动提交
conn.commit() 提交事务
conn.rollback() 回滚事务

代码演示

public static void main(String[] args) {
Connection conn = null;
Statement st = null;
try {
//获取连接
conn = DruidUtil.getConnection();
// System.out.println(conn);
//使用连接对象开启事务
conn.setAutoCommit(false); //手动设置提交
//获取sql语句执行者对象
st = conn.createStatement();
//开始执行sql
//给jack的钱减去100
/*
* 转账之前开启事务
* 进行转账
* 如果没问题,提交事务
* 如果有问题,回滚事务*/
int row1 = st.executeUpdate(“UPDATE account set money = money - 100 WHERE name = ‘jack’”);
int i = 10 / 0;
int row2 = st.executeUpdate(“UPDATE account SET money = money + 100 WHERE name = ‘rose’”);
//提交事务
conn.commit();
System.out.println(“转账成功”);

    } catch (Exception e) {
        //若出现异常,会被catch到,在此回滚事务
        try {
            if (conn != null){
                conn.rollback();
            }
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        System.out.println("转账失败");
    } finally {
        DruidUtil.release(conn,st,null);
    }
}

JDBC事务案例分层(dao、service)

三层架构:

      ■ 表现层(View)--给用户看,用户在此录入数据,及查看信息
      ■ 业务逻辑层(Service)--处理业务上的逻辑,表现层收到应发金额,然后计算实发金额
      ■ 数据访问层(Dao)--接受业务传来的数据,对数据进行增删改查操作

● 开发中,常使用分层思想
○ 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
● 不同层级结构彼此平等

分层的目的是:

  ○ 解耦  --  降低关联性
  ○ 高内聚 --  每层只做自己的事情
  ○ 可维护性
  ○ 可扩展性
  ○ 可重用性

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

  ○ com.itheima                  公司域名倒写
  ○ com.itheima.dao               dao层
  ○ com.itheima.service        service层
  ○ com.itheima.domain        javabean
  ○ com.itheima.utils              工具

代码演示

**操作事务的连接必须与使用mysql的连接相同,否则不属于事务

按照DAO/service/view的顺序编写代码 –导入相关jar和DruidUtil

DAO

/*转账案例的DAO
* 仅仅对数据库进行增删改查操作
* 并会被service调用
* 两个方法
* 入账+出账*/
public class AccountDao {
//入账操作–指定人加金额
//未抛异常表成功,抛异常表失败
public void inMoney(Connection conn, String inName, int price){
try {
/* conn = DruidUtil.getConnection();*/
PreparedStatement pst = conn.prepareStatement(“update account set money=money + ? where name = ?;”);
pst.setObject(1,price);
pst.setObject(2,inName);
pst.execute();
} catch (Exception e) {
throw new RuntimeException(“入账失败”);
}
}
//出账
public void outMoney(Connection conn,String outName, int price){
try {
/* conn = DruidUtil.getConnection();*/
PreparedStatement pst = conn.prepareStatement(“update account set money=money - ? where name = ?;”);
pst.setObject(1,price);
pst.setObject(2,outName);
pst.execute();
} catch (Exception e) {
throw new RuntimeException(“出账失败”);
}
}
}

Service

/*转账案例的service层
* Service层用来处理业务上的数据
* Service层被表现层调用,里面调用dao*/
public class AccountService {
/*转账方法
* 三个参数
* 步骤
* 1.开启事务
* 2.执行入账和出账
* 3.如果没有问题,提交
* 4.如果有问题,回滚*/
Connection conn;

{
    try {
        conn = DruidUtil.getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public void doAccount(String inName,String outName,int money) {
    try {
        /*//获取连接
        conn = DruidUtil.getConnection();*/
        //开启事务
        conn.setAutoCommit(false);
        //通过调用DAO层执行入账和出账
        AccountDao dao = new AccountDao();
        //执行加钱操作
        dao.inMoney(conn,inName,money);
        System.out.println(10/0);
        dao.outMoney(conn,outName,money);
        //提交事务
        conn.commit();
    } catch (Exception e) {
        //若上面代码出现问题--回滚
        try {
            if(conn != null){
                conn.rollback();
            }
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        //如果转账过程出现问题,并且catch捕获到,即回滚,并且手动抛异常
        throw new RuntimeException(e.getMessage());
    }
}

}

View

/*视图层,用来给用户看,并且用户可以录入信息
* 会调用service层
* 在视图层中,键盘录入入账人姓名,出账人姓名,金额,调用service进行转账操作*/
public class AccountView {
public static void main(String[] args) {
String outName = “jack”;
String inName = “rose”;
int price = 1000;
//调用service层方法
AccountService service = new AccountService();
//调用方法
try {
service.doAccount(inName,outName,price);
System.out.println(“转张成功”);
} catch (Exception e) {
System.out.println(“转账失败”);
System.out.println(e.getMessage());
}
}
}

ThreadLocal

–> 提供一个连接池,确保事务中的连接是一个!!!

分析

在“事务传递参数版”中,我们必须修改方法的参数个数,传递链接,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。
java.lang.ThreadLocal该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。(将数据绑定在当前线程上,该数据可以在当前线程的任何位置使用)

相关知识:ThreadLocal

java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。
结合案例代码
按照DAO/service/view的顺序编写代码 –导入相关jar和DruidUtil

DAO

/*转账案例的DAO
* 仅仅对数据库进行增删改查操作
* 并会被service调用
* 两个方法
* 入账+出账*/
public class AccountDaoThreadLocal {
//入账操作–指定人加金额
//未抛异常表成功,抛异常表失败
public void inMoney(String inName, int price){
Connection conn = null;
try {
conn = ConnectionManager.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
try {
/* conn = DruidUtil.getConnection();*/
PreparedStatement pst = conn.prepareStatement(“update account set money=money + ? where name = ?;”);
pst.setObject(1,price);
pst.setObject(2,inName);
pst.execute();
} catch (Exception e) {
throw new RuntimeException(“入账失败”);
}
}
//出账
public void outMoney(String outName, int price){
Connection conn = null;
try {
conn = ConnectionManager.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
try {
/* conn = DruidUtil.getConnection();*/
PreparedStatement pst = conn.prepareStatement(“update account set money=money - ? where name = ?;”);
pst.setObject(1,price);
pst.setObject(2,outName);
pst.execute();
} catch (Exception e) {
throw new RuntimeException(“出账失败”);
}
}
}

Service

/*转账案例的service层
* Service层用来处理业务上的数据
* Service层被表现层调用,里面调用dao*/
public class AccountServiceThreadLocal {
/*转账方法
* 三个参数
* 步骤
* 1.开启事务
* 2.执行入账和出账
* 3.如果没有问题,提交
* 4.如果有问题,回滚*/

public void doAccount(String inName,String outName,int money) {
    Connection conn = null;
    try {
        conn = ConnectionManager.getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        /*//获取连接
        conn = DruidUtil.getConnection();*/
        //开启事务

// conn.setAutoCommit(false);
//调用ThreadLocal开启线程
ConnectionManager.startTransaction();
//通过调用DAO层执行入账和出账
AccountDaoThreadLocal dao = new AccountDaoThreadLocal();
//执行加钱操作
dao.inMoney(inName,money);
// System.out.println(10/0);
dao.outMoney(outName,money);
//提交事务
conn.commit();
} catch (Exception e) {
//若上面代码出现问题–回滚
try {
if(conn != null){
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
//如果转账过程出现问题,并且catch捕获到,即回滚,并且手动抛异常
throw new RuntimeException(e.getMessage());
}
}
}

View

/*视图层,用来给用户看,并且用户可以录入信息
* 会调用service层
* 在视图层中,键盘录入入账人姓名,出账人姓名,金额,调用service进行转账操作*/
public class AccountViewThreadLocal {
public static void main(String[] args) {
String outName = “jack”;
String inName = “rose”;
int price = 1000;
//调用service层方法
AccountServiceThreadLocal service = new AccountServiceThreadLocal();
//调用方法
try {
service.doAccount(inName,outName,price);
System.out.println(“转张成功”);
} catch (Exception e) {
System.out.println(“转账失败”);
System.out.println(e.getMessage());
}
}
}

ThreadLocal工具类

/*
* 连接管理工具类
* 方法
* 1.获取连接
* 这个获取连接发的方法,使用相同线程,得到同一个连接(从当前超线程上面获取连接)
* 2.开启事务
* 3.提交事务
* 4.回滚事务*/
public class ConnectionManager {
//成员位置获取ThreadLocal对象
static ThreadLocal local = new ThreadLocal<>();
//获取连接,这个获取连接的方法,是从当前线程上面获取
public static Connection getConnection() throws Exception {
Connection conn = local.get();
//如果没有获取到连接,说明是第一次调用方法获取连接,之前未绑定到
//从连接池中获取新连接
if (conn == null){
conn = DruidUtil.getConnection();
//将此连接绑定到当前线程上
local.set(conn);
}
return conn;
}
//定义方法,用来开启事务
public static void startTransaction() throws Exception {
//从当前线程上获取连接,并开启事务
getConnection().setAutoCommit(false);
}
//定义方法用来提交事务
public static void commitTransaction() throws Exception {
getConnection().commit();
}
//回滚事务
public static void rollbackTransaction() throws Exception {
getConnection().rollback();
}
}

事务总结

事务特性:ACID

● 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
● 一致性(Consistency)事务前后数据的完整性必须保持一致。
● 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
● 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

并发访问问题

如果不考虑隔离性,事务存在3中并发访问问题。
● 脏读:一个事务读到了另一个事务未提交的数据.
● 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
● 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

隔离级别:解决问题

● 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题
read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读)
repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读)
serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a)存在:0个问题。
b)解决:3个问题(脏读、不可重复读、虚读)
● 安全和性能对比
○ 安全性:serializable > repeatable read > read committed > read uncommitted
○ 性能 : serializable < repeatable read < read committed < read uncommitted
● 常见数据库的默认隔离级别:
○ MySql:repeatable read
○ Oracle:read committed

代码演示

● 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
查询数据库的隔离级别
show variables like ‘%isolation%’;

select @@tx_isolation;

设置数据库的隔离级别
○ set session transaction isolation level 级别字符串
○ 级别字符串:readuncommitted、read committed、repeatable read、serializable
○ 例如:set session transaction isolation level read uncommitted;
读未提交:readuncommitted
○ A窗口设置隔离级别
■ AB同时开始事务 – start transaction;
■ A 查询
■ B 更新,但不提交
■ A 再查询?– 查询到了未提交的数据
■ B 回滚
■ A 再查询?– 查询到事务开始前数据
读已提交:read committed
○ A窗口设置隔离级别
■ AB同时开启事务
■ A查询
■ B更新、但不提交
■ A再查询?–数据不变,解决问题【脏读】
■ B提交
■ A再查询?–数据改变,存在问题【不可重复读】
可重复读:repeatable read – mysql数据库默认隔离级别
○ A窗口设置隔离级别
■ AB 同时开启事务
■ A查询
■ B更新, 但不提交
■ A再查询?–数据不变,解决问题【脏读】
■ B提交
■ A再查询?–数据不变,解决问题【不可重复读】
■ A提交或回滚
■ A再查询?–数据改变,另一个事务
串行化:serializable
○ A窗口设置隔离级别
○ AB同时开启事务
○ A查询
■ B更新?–等待(如果A没有进一步操作,B将等待超时)
■ A回滚
■ B 窗口?–等待结束,可以进行操作

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值