Web-第十三天 基础加强-JDBC高级开发事务【悟空教程】

Web-第十三天 基础加强-JDBC高级开发事务【悟空教程】

基础加强-第13天JDBC高级开发事务

第1章 JDBC高级开发事务

1.1 事务管理

1.1.1 事务概述

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

1.1.2 mysql事务操作

sql语句

描述

start transaction;

开启事务

commit;

提交事务

rollback;

回滚事务

  • 准备数据

# 创建一个表:账户表.

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);

  • 操作:
    • MYSQL中可以有两种方式进行事务的管理:
      • 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
      • 手动提交:先开启,再提交
    • 方式1:手动提交

start transaction;

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

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

commit;

#或者

rollback;

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

show variables like '%commit%';

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

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

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

1.1.3 JDBC事务操作

Connection对象的方法名

描述

conn.setAutoCommit(false)

开启事务

conn.commit()

提交事务

conn.rollback()

回滚事务

//事务模板代码

public void demo01() throws SQLException{

// 获得连接

Connection conn = null;

try {

//#1 开始事务

conn.setAutoCommit(false);

//.... 加钱 ,减钱

//#2 提交事务

conn.commit();

} catch (Exception e) {

//#3 回滚事务

conn.rollback();

} finally{

// 释放资源

conn.close();

}

}

1.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.2 案例分析

  • 开发中,常使用分层思想
    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
    • 不同层级结构彼此平等
    • 分层的目的是:
      • 解耦
      • 可维护性
      • 可扩展性
      • 可重用性
  • 不同层次,使用不同的包表示
    • cn.com.javahelp 公司域名倒写
    • cn.com.javahelp.dao dao层
    • cn.com.javahelp.service service层
    • cn.com.javahelp.domain javabean
    • cn.com.javahelp.utils 工具

1.3 代码实现

  • 步骤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("转账失败");

}

}

  • 步骤2:编写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();

}

}

}

  • 步骤3:编写AccountDao

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();

}

}

}

1.4 事务管理:传递Connection

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

1.4.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);

}

}

1.4.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);

}

}

1.5 提高:ThreadLocal

1.5.1 案例介绍

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

1.5.2 相关知识:ThreadLocal

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

1.5.3 分析

1.5.4 实现

1.5.4.1 工具类JDBCUtils

//连接池

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

//给当前线程绑定 连接

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);

}

}

1.5.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);

}

}

1.5.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);

}

}

1.6 案例总结:事务总结

1.6.1 事务特性:ACID

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

1.6.2 并发访问问题

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

1. 脏读:一个事务读到了另一个事务未提交的数据.

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

3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

1.6.3 隔离级别:解决问题

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

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

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

b) 解决:0个问题

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

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

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

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

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

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

4. 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

1.6.4 演示

  • 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
  • 查询数据库的隔离级别

show variables like '%isolation%';

select @@tx_isolation;

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

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-07-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏沃趣科技

如何使用HammerDB进行MySQL基准测试

背 景 服务器配置:960G sandisk单盘SSD,32 core,128G内存; 数据库关键参数:innodb_buffer_pool_size=72G,...

1.6K40
来自专栏Java面试通关手册

结合Spring发送邮件的四种正确姿势,你知道几种?

测试使用的环境是企业主流的SSM 框架即 SpringMVC+Spring+Mybatis。为了节省时间,我直接使用的是我上次的“SSM项目中整合Echarts...

15230
来自专栏耕耘实录

RHEL7.X系列及周边Linux发行版中,关于MBR与GPT的选择一些思考与建议

存储的选型、规划与管理等工作一直以来都是日常系统运维工作中的重点。MBR与GPT两种类型的分区表的选择与使用则是在磁盘管理中需要根据应用场景来注或考虑的要点。结...

13720
来自专栏菩提树下的杨过

IBM WebSphere MQ 7.5基本用法

一、下载7.5 Trial版本 http://www.ibm.com/developerworks/downloads/ws/wmq/ 这是下载网址,下载前先必...

43280
来自专栏吴生的专栏

使用Spring AOP实现MySQL数据库读写分离案例分析

分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量。

16120
来自专栏JAVA同学会

Mybatis Generator 使用com.mysql.cj.jdbc.Driver遇到的问题

Mybatis Generator 使用com.mysql.cj.jdbc.Driver遇到的问题

18810
来自专栏玄魂工作室

Hacker基础之Linux篇:基础Linux命令十六

今天我们来学习几个小知识,不一定是Linux的命令,都是用于查看Linux的系统信息的

17230
来自专栏乐沙弥的世界

基于Linux (RHEL 5.5) 安装Oracle 10g RAC

    本文所描述的是在Red Hat 5.5下使用vmware server 来安装Oracle 10g RAC(OCFS + ASM),本文假定你的RHEL...

16030
来自专栏青青天空树

springboot配置读写分离

  近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,...

29030
来自专栏芋道源码1024

分布式消息队列 RocketMQ源码解析:事务消息

本文主要基于 RocketMQ 4.0.x 正式版 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请...

56460

扫码关注云+社区

领取腾讯云代金券