【JDBC】转账案例

news2025/1/2 20:54:30
  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.coloradmin.cn/o/2268176.html

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

相关文章

HTML——14. 超链接四种状态

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>超链接</title></head><body><a href"https://ai.m.taobao.com" target"_blank">淘宝</a><br /><a href"…

微信V3支付报错 平台证书及平台证书序列号

1.平台证书及平台证书序列号设置错误报错&#xff1a; 错误1&#xff1a; Verify the response’s data with: timestamp1735184656, noncea5806b8cabc923299f8db1a174f3a4d0, signatureFZ5FgD/jtt4J99GKssKWKA/0buBSOAbWcu6H52l2UqqaJKvrsNxvodB569ZFz5G3fbassOQcSh5BFq6hvE…

MusicFree - 免费播放全网歌曲!无广告开源网络音乐聚合播放器 (安卓电脑版)

大家平常听歌可能都会在 QQ 音乐、网易云音乐、酷狗、喜马拉雅等不同平台来回切换&#xff0c;体验其实很烦。曾经推荐过不少“聚合”音乐应用&#xff0c;比如 洛雪音乐助手、Listen1 等等。 最近又有一个新选择了&#xff01;MusicFree 是一款免费开源清爽无广告的音乐播放器…

C++的第一个程序

前言 在学习c之前&#xff0c;你一定还记得c语言的第一个程序 当时刚刚开始进行语言学习 因此告诉到&#xff0c;仅仅需要记住就可以 #include <stdio.h>int main(){printf("Hello World");return 0; }而对于c中的第一个程序&#xff0c;似乎有所变化 C的…

代码随想录算法【Day1】

Day1 1.掌握二分法边界值判断&#xff0c;是根据写法来的; 2.删除数组元素的双指针和暴力解法; 3.灵活使用双指针方法 704 二分法 以前对于边界的问题非常纠结&#xff0c;到底是<还是<&#xff0c;以及是mid还是mid-1。 通过视频讲解&#xff0c;得知二分法的两种…

探索CSDN博客数据:使用Python爬虫技术

探索CSDN博客数据&#xff1a;使用Python爬虫技术 在数字化的浪潮中&#xff0c;数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台&#xff0c;汇聚了海量的技术博客与文章&#xff0c;成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…

实战案例——ZooKeeper集群部署(新手教程超详细)

案例目标 了解ZooKeeper分布式应用程序协调服务使用3台机器搭建ZooKeeper集群使用ZooKeeper集群 案例分析 规划节点 ZooKeeper集群节点规划 Ip 主机名 节点 192.168.110.10 zookeeper1 集群节点 192.168.110.20 zookeeper2 集群节点 192.168.110.30 zookeeper3 …

如果你的网站是h5网站,如何将h5网站变成小程序-除开完整重做方法如何快速h5转小程序-h5网站转小程序的办法-优雅草央千澈

如果你的网站是h5网站&#xff0c;如何将h5网站变成小程序-除开完整重做方法如何快速h5转小程序-h5网站转小程序的办法-优雅草央千澈 h5如何转小程序 如果当年你们开发网站是用的h5但是没有开发小程序&#xff0c;也没有使用uniapp这样的混开框架&#xff0c;但是目前根据业务需…

阿里云redis内存优化——PCP数据清理

在阿里云安装了一个redis节点&#xff0c;今天使用时忽然想着点击了一下分析内存。好家伙&#xff0c;居然崩出了一个30多M的块出来。问题是我本地安装的redis没有这个啊&#xff0c;怎么奇怪冒出这个来了。 本着把系统用干榨尽的态度&#xff0c;研究了下这个问题的来源。网上…

学系C++:循环练习案例

一&#xff0c;猜数字 案例描述&#xff1a;系统随机生成一个1到100之间的数字&#xff0c;玩家进行猜测&#xff0c;如果猜错&#xff0c;提示玩家数字过大或过小&#xff0c;如果猜对恭喜玩家胜利&#xff0c;并且退出游戏。 #include <iostream> using namespace st…

六大基础深度神经网络之CNN

左侧是传统卷积网络输入的是一列像素点&#xff0c;右侧是卷积神经网络&#xff0c;输入的是具有长宽通道数的原始图像 下图为整体架构。卷积层可以认为提取特征&#xff0c;池化层是压缩特征。全连接层是把图像展平然后计算10个类别的概率值 给出一张图像不同区域的特征不同&a…

SemiDrive E3 MCAL 开发系列(6)– Icu 模块的使用

一、 概述 本文将会介绍 SemiDrive E3 MCAL Icu 模块的简介以及基本配置&#xff0c;其中还会涉及到 Xtrg 模块的配置。此外会结合实际操作的介绍&#xff0c;帮助新手快速了解并掌握这个模块的使用&#xff0c;文中的 MCAL 是基于 PTG3.0 的版本&#xff0c;开发板是官方的 …

嵌入式入门Day35

网络编程 Day2 套接字socket基于TCP通信的流程服务器端客户端TCP通信API 基于UDP通信的流程服务器端客户端 作业 套接字socket socket套接字本质是一个特殊的文件&#xff0c;在原始的Linux中&#xff0c;它和管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;信号等…

【Redis】:初识Redis

1.1 盛赞 Redis Redis 是⼀种基于键值对&#xff08;key-value&#xff09;的 NoSQL 数据库&#xff0c;与很多键值对数据库不同的是&#xff0c;Redis 中的值可以是由 string&#xff08;字符串&#xff09;、hash&#xff08;哈希&#xff09;、list&#xff08;列表&#xf…

MATLAB 车牌自动识别系统设计 图像分割与图像增强方法 车牌识别

一 车牌自动识别系统总体设计 基于matlab的车牌识别系统&#xff0c;第一种方法采用图像分割与图像增强的方法&#xff0c;采集的车牌后将图像传入程序中&#xff0c;对图像进行处理后将车牌号提取出来&#xff0c;然后与数据库的样本进行对比后输出结果。 本课题拟采用的思路&…

Windows下C++使用SQLite

1、安装 进入SQLite Download Page页面&#xff0c;下载sqlite-dll-win-x86-*.zip、sqlite-amalgamation-*.zip、sqlite-tools-win-x64-*.zip三个包&#xff0c;这三个包里分别包含dll文件和def文件、头文件、exe工具。 使用vs命令行工具生成.lib文件&#xff1a;进入dll和def文…

module ‘django.db.models‘ has no attribute ‘FieldDoesNotExist‘

module ‘django.db.models’ has no attribute ‘FieldDoesNotExist’ xadmin报错 原因 django与xadmin版本不匹配。 django==3.2.7 xadmin-django==3.0.2解决方案 在xadmin/view/edit.py的388行改为 from django.core import exceptions if self.request_method ==

3.微服务灰度发布落地实践(组件灰度增强)

文章目录 前言调用链示图dubbo服务之间的的调链cloud 服务之间的调用链 网关servlet容器: 标签续传1.定义插件2.实现灰度增强拦截 线程池: 标签续传1.拦截Runnable或Callable,接口增强实现标签续传;Callable 插件定义Runnable 插件定义拦载Callabl或Runnable构造(可共用)拦载ru…

UE5 丧尸类杂兵的简单AI

A、思路 1、关卡初始化时&#xff0c;自动产生随机巡逻点&#xff0c;小兵到达后&#xff0c;去另一个随机巡逻点。 2、加入视力&#xff0c;发现主角后&#xff0c;不再巡逻&#xff0c;而开始追击主角并攻击。条件循环。 3、加入听力。主角的奔跑与射击会产生噪音&#xf…

数据库管理-第275期 Oracle 23ai:画了两张架构图(20241225)

数据库管理275期 2024-12-25 数据库管理-第275期 Oracle 23ai&#xff1a;画了两张架构图&#xff08;20241225&#xff09;1 系统管理分片2 用户定义分片总结 数据库管理-第275期 Oracle 23ai&#xff1a;画了两张架构图&#xff08;20241225&#xff09; 作者&#xff1a;胖…