【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.mzph.cn/news/891117.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dede-cms关于shell漏洞

一.文件式管理器 1.新建文件 新建一个php文件&#xff0c;内容写个php脚本语言 访问&#xff0c;可以运行 2.文件上传 上传一个php文件&#xff0c;内容同样写一个php代码 访问&#xff0c;运行成功 二.模块-广告管理 来到模块-广告管理——>增加一个新广告 在这里试一下…

k-Means聚类算法 HNUST【数据分析技术】(2025)

1.理论知识 K-means算法&#xff0c;又称为k均值算法。K-means算法中的k表示的是聚类为k个簇&#xff0c;means代表取每一个聚类中数据值的均值作为该簇的中心&#xff0c;或者称为质心&#xff0c;即用每一个的类的质心对该簇进行描述。K-Means算法接受参数K&#xff1b;然后将…

Opencv之对图片的处理和运算

Opencv实现对图片的处理和修改 目录 Opencv实现对图片的处理和修改灰度图读取灰度图转换灰度图 RBG图单通道图方法一方法二 单通道图显色合并单通道图 图片截取图片打码图片组合缩放格式1格式2 图像运算图像ma[m:n,x:y]b[m1:n1,x1:y1] add加权运算 灰度图 读取灰度图 imread(‘…

【算法思想04】二分查找

文章目录 1. 基本思想与实现1.1 基本思想1.2 值m的计算方式1.3 查找失败时的返回值1.4 代码实现1.4.1 循环1.4.2 递归 2. 性能分析2.1 时间复杂度2.2 与顺序查找的效率比较 3. 应用3.1 前提3.2 变体3.2.1 最基本的二分查找3.2.2 寻找左侧边界的二分查找3.2.3 寻找右侧边界的二分…

【brainpan靶场渗透】

文章目录 一、基础信息 二、信息收集 三、反弹shell 四、提权 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机 IP&#xff1a;192.168.20.155 二、信息收集 似乎开放了9999&#xff0c;10000端口&#xff0c;访问页面没有太多内容&#xff0c;扫描一下目录 dirs…

matlab reshape permute

1.reshape 将向量按照顺序重新构建 矩阵&#xff0c;新矩阵 先排完第一列&#xff0c; 再第二列… 2.permute 将向量 维度变换

comctl32.dll没有被指定在window运行怎么解决?

一、文件丢失问题&#xff1a;comctl32.dll没有被指定在Windows上运行怎么解决&#xff1f; comctl32.dll是Windows操作系统中的一个重要组件&#xff0c;它负责提供用户界面元素&#xff0c;如按钮、对话框和列表视图等。当系统提示“comctl32.dll没有被指定在Windows上运行”…

Qt下使用AES进行字符串加密解密

文章目录 前言一、获取QAESEncryption库二、加密与解密实现三、示例完整代码四、下载链接总结 前言 引用&#xff1a;AES&#xff08;Advanced Encryption Standard&#xff09;是一种对称加密算法&#xff0c;被广泛用于数据加密&#xff0c;提供128、192、256位三种密钥长度&…

docker 安装minio

docker pull minio/minio #启动 mkdir -p /root/minio/config mkdir -p /root/minio/datadocker run -d \--name minio \-p 9002:9000 \-p 9001:9001 \--restartalways \-v /root/minio/data:/data \-v /root/minio/config:/root/.minio \-e "MINIO_ACCESS_KEYminioadmin…

Linux系统下安装配置 Nginx 超详细图文教程

一、下载Nginx安装包 nginx官网&#xff1a;nginx: download[这里是图片001]http://nginx.org/en/download.html 找到我们所需要版本&#xff0c;把鼠标移动到上面&#xff0c;右键打开链接进行下载 或者如果Linux联网&#xff0c;直接在Linux服务上使用wget命令把Nginx安装包…

爬虫与反爬虫实现全流程

我选取的网页爬取的是ppt nba版 需要的工具:pycharm,浏览器 爬虫需要观察它的网页信息,然后开始首先爬取它的html,可以看到有人气,标题,日期,咨询 可以看到用get方法 import requests url"https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2…

最新版Edge浏览器加载ActiveX控件技术——alWebPlugin中间件V2.0.28-迎春版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX控件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持Chrome、Firefo…

OSPFv2协议状态切换(状态机)基本原理-RFC2328

个人认为&#xff0c;理解报文就理解了协议。通过报文中的字段可以理解协议在交互过程中相关传递的信息&#xff0c;更加便于理解协议。 自动换行 OSPFv2&#xff1a; 关于 OSPFv2 协议基本原理&#xff0c;可参考RFC2328-OSPF Version 2。 其他相关资料可参考&#xff1a; …

【最新】沃德协会管理系统源码+uniapp前端+环境教程

一.系统介绍 一款基于FastAdminThinkPHPUniapp开发的商协会系统&#xff0c;新一代数字化商协会运营管理系统&#xff0c;以“智慧化会员体系、智敏化内容运营、智能化活动构建”三大板块为基点&#xff0c;实施功能全场景覆盖&#xff0c;一站式解决商协会需求壁垒&#xff0…

【LeetCode: 83. 删除排序链表中的重复元素 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

算法练习——模拟题

前言&#xff1a;模拟题的特点在于没有什么固定的技巧&#xff0c;完全考验自己的代码能力&#xff0c;因此有助于提升自己的代码水平。如果说一定有什么技巧的话&#xff0c;那就是有的模拟题能够通过找规律来简化算法。 一&#xff1a;替换所有问号 题目要求&#xff1a; 解…

Idea创建JDK17的maven项目失败

Idea创建JDK17的maven项目失败 Error occurred during initialization of VM Could not find agent library instrument on the library path, with error: Can’t find dependent libraries Possible solution: Check your maven runner VM options. Open Maven Runner setti…

VSCode设置Playwright教程

1.安装扩展 打开VS Code&#xff0c;在扩展—>搜索"Playwright Test for VSCode"&#xff0c;点击安装 按快捷键CommandShiftP&#xff0c;输入install playwright&#xff0c;点击安装Playwright 安装成功会有如下提示 2.调试脚本 打开tests/example.spec.ts文…

HTML新特性|01 音频视频

音频 1、Audio (音频) HTML5提供了播放音频文件的标准 2、control(控制器) control 属性供添加播放、暂停和音量控件 3、标签: <audio> 定义声音 <source> 规定多媒体资源,可以是多个<!DOCTYPE html> <html lang"en"> <head><…

【深度学习】卷积网络代码实战ResNet

ResNet (Residual Network) 是由微软研究院的何凯明等人在2015年提出的一种深度卷积神经网络结构。ResNet的设计目标是解决深层网络训练中的梯度消失和梯度爆炸问题&#xff0c;进一步提高网络的表现。下面是一个ResNet模型实现&#xff0c;使用PyTorch框架来展示如何实现基本的…