当前位置: 首页 > article >正文

【JDBC】转账案例

  1. 回顾
    1. 使用工具类查询表

需求: 查询student表的所有数据,把数据封装到一个集合中

  1. 数据准备

#创建表

CREATE  TABLE student(

sid INT,

name VARCHAR(100),

age INT,

sex VARCHAR(100)

)

#插入数据

INSERT INTO student VALUES(1,'张三',18,'女'),(2,'李四',19,'男'),(3,'王五',20,'女'),(4,'赵六',21,'男')

  1. 创建工程,导入jar包和工具类

连接池配置文件内容:

driverClassName = com.mysql.jdbc.Driver

url = jdbc:mysql://localhost:3306/day05pre

username = root

password = root

initialSize = 5

maxActive = 10

minIdle = 3

maxWait = 60000

德鲁伊连接池工具类

import java.io.InputStream;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class DruidUtil {

//1.创建一个连接池对象

private static DataSource dataSource;

//静态代码创建,这样第一次使用这个类的时候就可以直接创建DataSource对象了

static{

try {

//读取Druid.properties文件中的数据 创建连接池对象

InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("Druid.properties");

//创建properties集合载入流中数据

Properties pro = new Properties();

pro.load(is);

//Druid工具载入pro集合中的数据 创建数据源对象

dataSource = DruidDataSourceFactory.createDataSource(pro);

} catch (Exception e) {

e.printStackTrace();

}

}

//2.创建方法 返回一个连接

public static Connection getConn() throws SQLException{

return dataSource.getConnection();

}

//3.关闭所有资源

public static void closeAll(ResultSet rs, PreparedStatement pst, Connection conn) {

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if (pst != null) {

try {

pst.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

  1. 创建student类

这个类的字段与数据库字段对应

public class Student {

private int sid;

private String name;

private int age;

private String sex;

   构造

   set/get

   toString

}

  1. 创建测试类书写查询代码

/*

 * 查询student类中的所有的信息 展示到控制台上

 *

 * */

@Test

public  void selectAll() throws Exception{

//1.获取链接

Connection conn = DruidUtil.getConn();

System.out.println("Demo04 ======>selectAll() ======> 获取链接完毕 conn= "+conn);

//2.通过链接获取SQL的发射器

String sql = "select * from student";

PreparedStatement pst = conn.prepareStatement(sql);

//3.发射SQL语句 得到结果集

ResultSet rs = pst.executeQuery();

System.out.println("Demo04 ======>selectAll() ======> 发射完毕 rs= "+rs);

//创建一个集合

List<Student> list = new ArrayList<Student>();

//4.处理结果集

while(rs.next()){

Student s = new Student(rs.getInt("sid"), rs.getString("name"), rs.getInt("age"), rs.getString("sex"));

list.add(s);

}

//5.关闭资源

DruidUtil.closeAll(rs, pst, conn);

//遍历

for(Student stu: list){

System.out.println(stu);

}

}

  1. JDBC转账案例

    1. 需求:

完成转账功能

    1. 实现思路:

    1. 实现步骤
  1. 数据准备

创建数据表和数据

# 创建账号表

CREATE TABLE account(

id INT PRIMARY KEY AUTO_INCREMENT,

NAME VARCHAR(20),

money DOUBLE

);

# 初始化数据

INSERT INTO account VALUES (NULL,'张三',10);

INSERT INTO account VALUES (NULL,'李四',10);

  1. Dao层

创建两个方法分别实现取钱和存钱

import java.sql.Connection;

import java.sql.PreparedStatement;

import com.czxy.util.DruidUtil;

public class AccountDao {

/*

 * 从指定的账户中取钱(减钱)

 * */

public void outMoney(String name,int money) throws Exception{

//1.获取链接

Connection conn = DruidUtil.getConn();

//2.发射器

String sql = "UPDATE account SET money=money-? WHERE NAME=?";

PreparedStatement pst = conn.prepareStatement(sql);

pst.setInt(1, money);

pst.setString(2, name);

//3.发射

int num = pst.executeUpdate();

//4.处理结果

System.out.println("本次执行的影响的行数是: num="+num);

//5.关闭资源

DruidUtil.closeAll(null, pst, conn);

}

/*

 * 从指定的账户中存钱(加钱)

 * */

public void inMoney(String name,int money) throws Exception{

//1.获取链接

Connection conn = DruidUtil.getConn();

//2.发射器

String sql = "UPDATE account SET money=money+? WHERE NAME=?";

PreparedStatement pst = conn.prepareStatement(sql);

pst.setInt(1, money);

pst.setString(2, name);

//3.发射

int num = pst.executeUpdate();

//4.处理结果

System.out.println("本次执行的影响的行数是: num="+num);

//5.关闭资源

DruidUtil.closeAll(null, pst, conn);

}

}

  1. Service层

创建一个方法完成转账业务

import com.czxy.dao.AccountDao;

public class AccountService {

/**

 * 实现转账

 * @param srcName : 钱的来源

 * @param descName : 钱的去向

 * @param money : 钱数

 * */

public void transfer(String srcName,String descName,int money){

AccountDao ad = new AccountDao();

try {

// -钱

ad.outMoney(srcName, money);

// +钱

ad.inMoney(descName, money);

System.out.println("转账完毕 ");

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

  1. 测试类

创建一个测试类 ,书写main方法 实现转账

public class Demo02 {

public static void main(String[] args) {

//创建Service对象

AccountService as = new AccountService();

//张三 给 李四 转2块钱

as.transfer("张三", "李四", 2);

}

}

  1. 测试

执行前:

执行后:

    1. 遇到问题

如果转账的中间出现了bug,很容易导致A账户的钱减少了,但是B账户的钱没有增加。这会造成事故,不是我们期望看到的。

下面代码在Service层模拟转账过程出现问题。

效果如下:

转账前:

执行代码:

转账出错结果:

    1. 处理思路
  1. 核心思路:

让转账的两个动作:减钱,加钱  必须同时成功或者是同时失败

  1. 可选技术:

数据库的事务。

  1. 事务的概述
    1. 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全成功,要么全失败.
    2. 事务作用:保证一组操作要么全都成功,对数据库进行完整更新要么在某一个动作失败的时候让数据恢复原状,不会引起不完整的修改。

  1. MySQL事务的操作

sql语句

描述

start transaction;

开启事务

commit;

提交事务(完整更新)

rollback;

回滚事务(恢复原状)

    1. MYSQL中可以有两种方式进行事务的管理:
      1. 自动提交:MySql默认自动提交。即执行一条sql语句提交一次事务。
      2. 手动提交:先开启,再提交
    2. 方式1:手动提交(当执行手动提交的时候自动提交会暂停)

start transaction;

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

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

commit;

#或者

rollback;

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

show variables like '%commit%';

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

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

  1. &bsp;&bsp;测试利用事物实现转账1块钱 顺利完成情况

START TRANSACTION;  -- 开启事物

-- 执行一组操作

UPDATE account SET money=money-1 WHERE NAME='张三';

UPDATE account SET money=money+1 WHERE NAME='李四';

COMMIT; -- 提交事物

执行前:

执行后:

  1. &bsp;&bsp;测试利用事物实现转账1块钱 出现问题并回滚(恢复原状)

# 测试利用事物实现转账1块钱

START TRANSACTION;  -- 开启事物

-- 执行一组操作

UPDATE account SET money=money-1 WHERE NAME='张三';

 -- 下一句发生错误

UPDATE account SET money=money+1 WHERE NAME  &……&%&……¥(*&* ='李四';

ROLLBACK; -- 回滚(恢复原状)

执行前:

执行如下两句:

执行效果:

执行下面一句

这一组操作的第二个给李四加钱执行失败了 ,李四的钱并不会改变

此时一组操作没有全部成功,需要回滚来让数据恢复原状

  1. &bsp;JDBC事务操作

Connection对象的方法名

描述

conn.setAutoCommit(false)

设置关闭自动提交,(开启事务)

conn.commit()

提交事务

conn.rollback()

回滚事务

利用如下模板解决问题

//事务模板代码

public void demo01() throws SQLException{

// 获得连接

Connection conn = ...;

try {

//#1关闭自动提交事物(开始事务

conn.setAutoCommit(false);

//.... 加钱 ,减钱

//#2 手动提交事务

conn.commit();

} catch (Exception e) {

//#3 手动回滚事务

conn.rollback();

} finally{

// 释放资源

conn.close();

}

}

    1. 解决问题
  1. Dao层

把原来的两个方法进行修改,使用Service层传递过来的conn对象,并且执行完毕不要关闭链接

/*

 * 从指定的账户中取钱(减钱)

 * */

public void outMoney(String name,int money,Connection conn) throws Exception{

//2.发射器

String sql = "UPDATE account SET money=money-? WHERE NAME=?";

PreparedStatement pst = conn.prepareStatement(sql);

pst.setInt(1, money);

pst.setString(2, name);

//3.发射

int num = pst.executeUpdate();

//4.处理结果

System.out.println("本次执行的影响的行数是: num="+num);

//5.关闭资源 只关闭结果集不关闭链接

DruidUtil.closeAll(null, pst, null);

}

/*

 * 从指定的账户中存钱(加钱)

 * */

public void inMoney(String name,int money,Connection conn) throws Exception{

//2.发射器

String sql = "UPDATE account SET money=money+? WHERE NAME=?";

PreparedStatement pst = conn.prepareStatement(sql);

pst.setInt(1, money);

pst.setString(2, name);

//3.发射

int num = pst.executeUpdate();

//4.处理结果

System.out.println("本次执行的影响的行数是: num="+num);

//5.关闭资源 只关闭结果集不关闭链接

DruidUtil.closeAll(null, pst, null);

}

  1. Service层

  获取连接,关闭自动提交变成手动提交,一组动作成功则手动提交事务,一旦有异常则回滚,最后无论异常与否都要关闭连接

public void transfer(String srcName,String descName,int money){

AccountDao ad = new AccountDao();

Connection conn =null;

try {

//获取链接

conn = DruidUtil.getConn();

//把自动提交关闭,变成手动提交

conn.setAutoCommit(false);

// -钱  传递连接对象

ad.outMoney(srcName, money,conn);

//制造一个bug , 模拟转账出现问题

int a=1/0;

// +钱  传递连接对象

ad.inMoney(descName, money,conn);

//转账成功则手动提交事物

conn.commit();

System.out.println("转账完毕 ");

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

if(conn!=null){

try {

//操作失败 回滚

conn.rollback();

System.out.println("执行了回滚 ,把数据恢复原状 ");

} catch (SQLException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

}

}finally {

if(conn!=null){

try {

//关闭链接

conn.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

  1. 测试
  1. 正常情况:

把 如下代码注释上

执行前:

转账2块钱执行完毕

执行结果

  1. 异常情况:

保留如下代码

执行前:

 执行效果

执行后:数据恢复原状,问题解决

  1. 理论补充

事务特性:ACID

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

并发访问问题

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

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

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

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

严重性: 脏读 > 不可重复读 >虚读(幻读)

设置隔离级别:解决问题

  1. 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
    1. 存在:3个问题(脏读、不可重复读、虚读)。
    2. 解决:0个问题

 效率最高,引发所有读问题

 基本不设置

  1. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    1. 存放:2个问题(不可重复读、虚读)。
    2. 解决:1个问题(脏读)

如果要 效率,那么选择这个read committed

  1. repeatable read :可重复读,在一个事务中读到的数据信息始终保持一致,无论另一个事务是否提交。
    1. 存放:1个问题(虚读)。
    2. 解决:2个问题(脏读、不可重复读)

如果 要求安全,选择这个repeatable read

虚读的问题可以通过程序来规避:

  1. 事务刚开启时,可以count(*)
  2. 事务要关闭时,可以count(*)
  3. 比对,如果两次数据一致,说明没有虚读

  1. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
    1. 存放:0个问题。
    2. 解决:1个问题(脏读、不可重复读、虚读)

没有效率,安全性最高,基本不设置

  1. 安全和性能对比
    1. 安全性:serializable > repeatable read > read committed > read uncommitted
    2. 性能 : serializable < repeatable read < read committed < read uncommitted
  2. 常见数据库的默认隔离级别:
    1. MySql:repeatable read  安全,本身做的优化比较好
    2. Oracle:read committed  效率


http://www.kler.cn/a/458202.html

相关文章:

  • KNN分类算法 HNUST【数据分析技术】(2025)
  • 探索PyTorch:从入门到实践的demo全解析
  • CES Asia 2025优惠期倒计时5天,科技盛宴即将开启
  • 如何在IDEA一个窗口中导入多个项目
  • 关于 VRRP的详解
  • 爬虫代理服务要怎么挑选?
  • Spring Security3.0.2版本
  • 计算机网络技术研究方向有哪些创新点
  • 华为电源工程师面试题
  • 基于物联网的园区停车管理系统的设计与实现
  • xinput1_3.dll放在哪里?当xinput1_3.dll丢失时的应对策略:详细解决方法汇总
  • API 接口如何确保数据的安全?
  • element下拉多选项回显
  • 【Redis】:初识Redis
  • python加密算法
  • PyTorch快速入门教程【小土堆】之Transforms的使用
  • 【Rust自学】7.6. 将模块拆分为不同文件
  • Spring Boot 学习笔记
  • Hive之import和export使用详解
  • linux学习笔记(一).学习路径+学习流程+起源