技术标签: spring Java框架 java 1024程序员节 MySQL 后端 数据库
目录
事务传播的属性/种类机制分析,重点分析了 REQUIRED 和 REQUIREDS_NEW 两种事务传播属性, 其它知道即可
REQUIRES_NEW 和 REQUIRED 在处理事务的策略
1. 编程式事务:
Connection connection = JdbcUtils.getConnection();
try {
//1. 先设置事务不要自动提交
connection.setAutoCommint(false);
//2. 进行各种 crud
//多个表的修改,添加 ,删除
//3. 提交
connection.commit();
} catch (Exception e) {
//4. 回滚
conection.rollback();
}
2. 声明式事务:
Connection connection = JdbcUtils.getConnection();
try {
//1. 先设置事务不要自动提交
connection.setAutoCommit(false);
//2. 进行各种 crud
//多个表的修改,添加 ,删除
select from 商品表 => 获取价格
修改用户余额 update ... 修改库存量 update
//3. 提交
connection.commit();
} catch (Exception e) {
//4. 回滚
conection.rollback();
}
-- 演示声明式事务创建的表
CREATE TABLE `user_account`(
user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL DEFAULT '',
money DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO `user_account` VALUES(NULL,'张三', 1000);
INSERT INTO `user_account` VALUES(NULL,'李四', 2000);
CREATE TABLE `goods`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_name VARCHAR(32) NOT NULL DEFAULT '',
price DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8 ;
INSERT INTO `goods` VALUES(NULL,'小风扇', 10.00);
INSERT INTO `goods` VALUES(NULL,'小台灯', 12.00);
INSERT INTO `goods` VALUES(NULL,'可口可乐', 3.00);
CREATE TABLE `goods_amount`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_num INT UNSIGNED DEFAULT 0
)CHARSET=utf8 ;
INSERT INTO `goods_amount` VALUES(1,200);
INSERT INTO `goods_amount` VALUES(2,20);
INSERT INTO `goods_amount` VALUES(3,15);
如果我们新开一个xml文件:记得将之前配置的数据库文件都要写入,否则无法读入数据库
<!--引入外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源对象-DataSoruce-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<!--给数据源对象配置属性值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--给JdbcTemplate对象配置dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置要扫描的包-->
<context:component-scan base-package="com.hong.spring.tx.dao"/>
<context:component-scan base-package="com.hong.spring.tx.service"/>
package com.hong.spring.tx.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* Created with IntelliJ IDEA.
*
* @Author: 海绵hong
* @Date: 2022/10/25/10:14
* @Description:
*/
@Repository //将 GoodsDao-对象 注入到spring容器
public class GoodsDao {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 根据商品id,返回对应的价格
* @param id
* @return
*/
public Float queryPriceById(Integer id) {
String sql = "SELECT price From goods Where goods_id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
return price;
}
/**
* 修改用户的余额 [减少用户余额]
* @param user_id
* @param money
*/
public void updateBalance(Integer user_id, Float money) {
String sql = "UPDATE user_account SET money=money-? Where user_id=?";
jdbcTemplate.update(sql, money, user_id);
}
/**
* 修改商品库存 [减少]
* @param goods_id
* @param amount
*/
public void updateAmount(Integer goods_id, int amount){
String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";
jdbcTemplate.update(sql, amount , goods_id);
}
}
@Test
public void queryPriceByIdTest() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
Float price = goodsDao.queryPriceById(1);
System.out.println("id=1 的price=" + price);
}
@Test
public void updateBalance() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
goodsDao.updateBalance(1, 1.0F);
System.out.println("减少用户余额成功~");
}
@Test
public void updateAmount() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
goodsDao.updateAmount(1, 1);
System.out.println("减少库存成功...");
}
@Service //将 GoodsService对象注入到spring容器
public class GoodsService {
//定义属性GoodsDao
@Resource
private GoodsDao goodsDao;
//编写一个方法,完成用户购买商品的业务, 这里主要是讲解事务管理
/**
* @param userId 用户id
* @param goodsId 商品id
* @param amount 购买数量
*/
public void buyGoods(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById(userId);
//2. 减少用户的余额
goodsDao.updateBalance(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount(goodsId, amount);
System.out.println("用户购买成功~");
}
}
测试购买业务
//测试用户购买商品业务
@Test
public void buyGoodsTest() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService goodsService = ioc.getBean(GoodsService.class);
goodsService.buyGoods(1, 1, 1);
}
梳理:service中的方法传入对应的参数,在将依赖注入的dao中的方法,进行一个调用实现数据库中的改动,因为这是一个业务所以将这些封装到一个方法之中,参入参数的时候一个进行都会进行改变。实现事务的一致性,但是这样写的时候如果有异常就会发现账户余额发生了改变但是库从却没有改变,这样就不叫事务。所以接下来我们来看看spring中的事务到底是咋样的。
在spring如何实现事务那,既然是框架那么注解就是必须比的好用了,在这里我们使用@Transactional这个注解进行事务的统一管理。既然是注解那么我们就要在spring的xml文件中进行配置
<!--配置事务管理器-对象
1. DataSourceTransactionManager 这个对象是进行事务管理-debug源码
2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置启动基于注解的声明式事务管理功能-->
<tx:annotation-driven transaction-manager="transactionManager"/>
/**
* @param userId
* @param goodsId
* @param amount
* @Transactional 注解解读
* 1. 使用@Transactional 可以进行声明式事务控制
* 2. 即将标识的方法中的,对数据库的操作作为一个事务管理
* 3. @Transactional 底层使用的仍然是AOP机制
* 4. 底层是使用动态代理对象来调用buyGoodsByTx
* 5. 在执行buyGoodsByTx() 方法 先调用 事务管理器的 doBegin() , 调用 buyGoodsByTx()
* 如果执行没有发生异常,则调用 事务管理器的 doCommit(), 如果发生异常 调用事务管理器的 doRollback()
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById(userId);
//2. 减少用户的余额
goodsDao.updateBalance(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount(goodsId, amount);
System.out.println("用户购买成功~");
}
测试事务
//测试用户购买商品业务
@Test
public void buyGoodsByTxTest() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService goodsService = ioc.getBean(GoodsService.class);
goodsService.buyGoodsByTx(1, 1, 1);//这里我们调用的是进行了事务声明的方法
}
熟悉的底层:首先我们来看看DataSourceTransactionManager的底层就可以知道这个注解到底干了啥事
很清晰的可以看到我们在设置的时候一定要去定义一个数据源dataSource,只有从数据源才可以得到这个连接,只有这个连接才可以去控制我们这个事务。
拿到当前这个连接,如果这个连接时一个false,就代表默认是手动提交,如果是true就默认是自动提交,如果是true那么就会执行下面的那句,将提交结果设置为false,意思就是你不要提交了,来进行手动提交。
这个就是如果你整个在执行过程中没有出现问题那么就进行一个提交的操作
进行回滚,如果我们在执行数据的操作的时候,不管执行那一句,如果出了问题,那么他就会进入到事务管理器的doRollback这个代码之中,这样就可以控制住这个事务了。
@Transactional 底层使用的仍然是AOP机制 底层是使用动态代理对象来调用buyGoodsByTx 在执行buyGoodsByTx() 方法 先调用 事务管理器的 doBegin() , 调用 buyGoodsByTx() 如果执行没有发生异常,则调用 事务管理器的 doCommit(), 如果发生异常 调用事务管理器的 doRollback()
在执行业务代码之前先执行doBegin,就会把提交设置为false,然后就开始执行,如果在这个过程中出现了异常,那么他就会进入我们的doRollback,如果没有出现异常,就会进入到事务管理器的doCommit。就是可以这么理解:doBegin类似于我们之前的前置通知,doCommit类似于返回通知,而doRollback类似与我们的异常通知
Tx1中包含两个方法,,Tx1是一个事务,开始执行,当执行到方法1的时候,如果是默认传播机制,这个方法1就会作为一个整体事务,相当于被外面这个Tx1管理起来了,方法也是如此,知道事务结束。换言之就是被外面这个事务统一管理的。假设方法1成功了,但是方法2失败了,都会回滚
Tx1事务开始执行,当遇到方法1的时候Tx1就挂起,同时开启方法1的事务,当方法1的事务结束以后Tx1继续挂起,直到碰到方法2就继续挂起,并让方法2事务执行完毕,前提是这两个都是REQUIRES_NEW的传播机制。假设方法1成功了,但是方法2失败了,那么只会回滚方法2
继续在GoodsDao中添加一些代码,为了区分
/**
* 根据商品id,返回对应的价格
* @param id
* @return
*/
public Float queryPriceById2(Integer id) {
String sql = "SELECT price From goods Where goods_id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
return price;
}
/**
* 修改用户的余额 [减少用户余额]
* @param user_id
* @param money
*/
public void updateBalance2(Integer user_id, Float money) {
String sql = "UPDATE user_account SET money=money-? Where user_id=?";
jdbcTemplate.update(sql, money, user_id);
}
/**
* 修改商品库存 [减少]
* @param goods_id
* @param amount
*/
public void updateAmount2(Integer goods_id, int amount){
String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";
jdbcTemplate.update(sql, amount , goods_id);
}
写一个方法包含两个事务
@Transactional
public void multiBuyGoodsByTx() {
goodsService.buyGoodsByTx(1, 1, 1);
goodsService.buyGoodsByTx2(1, 1, 1);
}
设置为 REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById(userId);
//2. 减少用户的余额
goodsDao.updateBalance(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount(goodsId, amount);
System.out.println("用户购买成功~");
}
/**
* 这个方法是第二套进行商品购买的方法
*
* @param userId
* @param goodsId
* @param amount
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx2(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById2(userId);
//2. 减少用户的余额
goodsDao.updateBalance2(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount2(goodsId, amount);
System.out.println("用户购买成功~");
}
1. multiBuyGoodsByTx 这个方法 有两次购买商品操作
2. buyGoodsByTx 和 buyGoodsByTx2 都是声明式事务
3. 当前buyGoodsByTx 和 buyGoodsByTx2 使用的传播属性是默认的 REQUIRED
[这个含义前面讲过了 即会当做一个整体事务进行管理 , 比如buyGoodsByTx方法成功,但是buyGoodsByTx2() 失败,会造成 整个事务的回滚即也会回滚buyGoodsByTx]
4. 如果 buyGoodsByTx 和 buyGoodsByTx2 事务传播属性修改成 REQUIRES_NEW,
这时两个方法的事务是独立的,也就是如果 buyGoodsByTx成功 buyGoodsByTx2失败, 不会造成 buyGoodsByTx回滚.
1. 在默认情况下 声明式事务的隔离级别是 REPEATABLE_READ 2. 我们将buyGoodsByTxISOLATION的隔离级别设置为 Isolation.READ_COMMITTED ,表示只要是提交的数据,在当前事务是可以读取到最新数据
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx2(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById2(userId);
//2. 减少用户的余额
goodsDao.updateBalance2(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount2(goodsId, amount);
System.out.println("用户购买成功~");
}
说明:当我们在sqlyog中进行一个价格的变动,但是在idea的读取中没有发生变化
@Transactional(isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION() {
//查询两次商品的价格
Float price = goodsDao.queryPriceById(1);
System.out.println("第一次查询的price= " + price);
Float price2 = goodsDao.queryPriceById(1);
System.out.println("第二次查询的price= " + price2);
}
解读 1. @Transactional(timeout = 2) 2. timeout = 2 表示 buyGoodsByTxTimeout 如果执行时间超过了2秒, 该事务就进行回滚. 3. 如果你没有设置 timeout, 默认是 -1,表示使用事务的默认超时时间,或者不支持
@Transactional(timeout = 2)
public void buyGoodsByTxTimeout(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById2(userId);
//2. 减少用户的余额
goodsDao.updateBalance2(userId, price * amount);
//模拟超时
System.out.println("=====超时开始4s=====");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=====超时结束4s=====");
//3. 减少库存量
goodsDao.updateAmount2(goodsId, amount);
System.out.println("用户购买成功~");
}
文章浏览阅读1.6k次。在含有电阻、电感和电容的交流电路中,电路两端电压与其电流一般是不同相的,若调节电路参数或电源频率使电流与电源电压同相,电路呈电阻性,称这时电路的工作状态为谐振。谐振现象是正弦交流电路的一种特定现象,它在电子和通讯工程中得到广泛应用,但在电力系统中,发生谐振有可能破坏系统的正常工作。谐振一般分串联谐振和并联谐振。顾名思义,串联谐振就是在串联电路中发生的谐振。并联谐振就是在并联电路中发生的谐振。串联谐..._并联谐振 电流计算
文章浏览阅读5.7k次,点赞4次,收藏19次。如何使用Windows server 2022 创建ad域与加入_windows server 2022配置域控
文章浏览阅读7.1k次。魔兽世界怀旧服10月已经到来,目前怀旧服由于位面的逐渐取消,导致现在排队严重,让很多玩家还想知道现在各大服务器的人口普查数据,下面就来为大家详细的介绍一下最新10月份的人口普查阵营比例。12月19日更新:【魔兽世界怀旧服12月最新转服前人口普查 联盟部落阵营比例数据】魔兽世界怀旧服人口普查插件已经被修复,所以这个就是最终版,此外还有附带一些目前新服务器的情况。大家也就凑合着看看之前记录的最新数据,...
文章浏览阅读855次。目录Base64编码SSL 与 OpenSSLmacOS 下 RSA 证书生成过程Base64编码Base64编码简介一般情况下,如果用记事本直接打开 .exe、.jpg、.pdf、… 等格式的文件时,会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符。所以,如果要让像记事本这样的文本处理软件能处理和显示二进制数据,就需要一个二进制数据到字符串的转换方法。Base64 就是一种最常见的二进制编码方法。Base64 是一种用 64 个字符来表示任意二进制数据的方法。Base64 是一种__airths的博客-csdn博客.pdf
文章浏览阅读1.3w次,点赞114次,收藏319次。threejs 是运行在浏览器中的 3D 引擎,是JavaScript编写的WebGL第三方库。提供了非常多的3D显示功能。开发者可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象。可以在它的主页上看到许多精彩的演示。不过,这款引擎还处在比较不成熟的开发阶段,其不够丰富的 API 以及匮乏的文档增加了初学者的学习难度(尤其是文档的匮乏)。在讲解 threejs 的时候,我们通过一个基本的简单的案例,来实现一个小的效果,然后把常用的 API、工具、功能稍微说一下哈!_three.js vue
文章浏览阅读1.4w次,点赞19次,收藏116次。最近学习过程中 常碰到让用户登录注册这种 今天就用vue实现简单的登录注册功能:注册时要用到localstorage来存储信息 登录时将表单信息和localstorage中的对比判断代码如下: <!-- 登陆页面 --> <van-nav-bar title="密码登陆" style="background:#2f8fea;color:#fff"> <template #left> <van-icon name="ar_vue2实现好看的登录注册
文章浏览阅读1.2k次。文件名大小更新时间ROSTCM6.exedict/1.datdict/2.datdict/3.datdict/4.datdict/5.datdict/6.datdict/8.datdict/9.datdict/F.datdict/new.datdict/s.datdict/SSCItitle.txtdict/Z.datsample/模拟群(437343630).txtuser/Feature.tx..._rostcm6.exe
文章浏览阅读1.8w次,点赞46次,收藏371次。前后端参数传递总结(@RequestParam @RequestBody@PathVariable @RequestHeader )@RequestParam@RequestBody合理的创建标题,有助于目录的生成如何改变文本的样式@RequestParam@RequestParam 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String–> 简单类型的转换操作由ConversionService配置的转换器来完成_pathvariable前端传参
文章浏览阅读2.9w次,点赞29次,收藏113次。C语言 strstr函数的用法及模拟实现strstr函数一、strstr函数的用法二、模拟实现strstr函数的功能一、strstr函数的用法1.strstr函数原型:char* strstr(const char* str1,const char* str2)2.功能:strstr()是一个参数为两个字符指针类型,返回值是char*类型的函数,它用于找到子串(str2)在一个字符串(str1)中第一次出现的位置。这里因为传进来的地址指向的内容不会在发生改变,所以我们在两个形参(char*)前加上c_strstr
文章浏览阅读293次。--_使用page标准动作可以实现动态包含
文章浏览阅读742次,点赞5次,收藏8次。ROS打包错误: /usr/bin/ld: cannot find -lpthreads 的解决_performing test cmake_have_libc_pthread - failed
文章浏览阅读1.7k次。“本文介绍的论文全面概述了深度神经网络的压缩方法,主要可分为参数修剪与共享、低秩分解、迁移/压缩卷积滤波器和知识精炼,本论文对每一类方法的性能、相关应用、优势和缺陷等进行独到的分析。” 大型神经网络具有大量的层级与结点,因此考虑如何减少它们所需要的内存与计算量就显得极为重要,特别是对于在线学习和增量学习等实时应用。此外,近来智能可穿戴设备的流行也为研究员提供了在资源(内存、CPU、能..._深度网络模型压缩