Spring 声明式事务 万字详解(通俗易懂)
目录
Δ前言
一、声明式事务快速入门
1.为什么需要声明式事务?
2.定义:
3.应用实例:
二、声明式事务的传播机制
1.引出问题:
2.传播机制分类:
3.应用实例:
三、声明式事务的隔离机制
1.四种隔离级别:
2.应用实例:
3.事务超时回滚:
Δ总结
Δ前言
- 第七节内容,up主要和大家分享一下Spring 声明式事务相关的内容;包括声明式事务的快速入门,事务传播机制和隔离机制。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着敲一遍才真正有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!
一、声明式事务快速入门
1.为什么需要声明式事务?
传统的编程式事务(Programmatic Transaction)需要开发者手动编写代码来控制事务的开启、提交、回滚。
传统编程式事务,事务逻辑与业务代码耦合性高,且每个事务方法需重复编写 try-catch 和事务模板代码,造成代码冗余;修改事务逻辑需逐个方法调整,易出错,维护成本高。
2.定义:
声明式事务(Declarative Transaction)是 Spring 框架提供的一种 通过配置(而非代码)管理事务 的机制。开发者无需在业务代码中手动编写事务开启、提交或回滚逻辑,而是通过注解或 XML 定义事务规则,由 Spring 自动处理事务的完整生命周期。
与Spring 声明式事务相关的核心类,就在spring-tx-xxx.jar包下,如下图所示:
声明式事务的底层通过 动态代理 + AOP 拦截 实现,结合事务管理器和线程资源绑定,将事务逻辑与业务代码解耦。其核心思想是 “约定优于配置”,开发者只需关注业务逻辑,事务管理由框架自动完成。
3.应用实例:
需求:要求完成用户购买商品的业务逻辑处理,并正确地处理用户余额、商品库存的更新。(以下案例仅用于演示声明式事务,并无实际意义)
分析:
① 既然要求正确处理用户余额和商品库存的更新,所以数据库中肯定要有 用户账目表 和 商品库存表,而要想处理用户余额,必须得知道用户所购买的商品的价格,所以还需要一张 商品表,那么一共需要三张表。
② 假设以Commodity表示商品,我们需要在 CommodityDAOImpl 中组织SQL语句,CommodityDAOImpl中至少需要实现三种数据库操作——查询商品的价格、更新用户余额、更新商品库存。而在 CommodityServiceImpl 中我们要完成业务逻辑的处理,其实就是把DAO层定义的若干个方法进行灵活地组合,从而实现特定的业务逻辑(此处为购买商品的业务逻辑)。
实现:
首先,来创建 `user_account`, `commodities` , `inventory_report`三张表,SQL代码如下:
# 创建用户账目表
CREATE TABLE `user_account` (`user_id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,`user_name` VARCHAR(64) NOT NULL DEFAULT '',`user_balance` DOUBLE NOT NULL DEFAULT 0.0
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;INSERT INTO `user_account` VALUES(NULL, 'Cyan', 500),(NULL, 'Five', 2000);# 创建商品表
CREATE TABLE `commodities` (`c_id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,`c_name` VARCHAR(64) NOT NULL DEFAULT '',`c_price` DOUBLE NOT NULL DEFAULT 0.0
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;INSERT INTO `commodities`(`c_name`, `c_price`)VALUES('mini washing machine', 300.0),('lamp', 35.0),('keyboard', 150.0);# 创建商品库存表
CREATE TABLE`inventory_report` (`ir_id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,`ir_amount` INT UNSIGNED DEFAULT 0
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;INSERT INTO `inventory_report`VALUES(NULL, 3), (NULL, 11), (NULL, 9);
创建后的三张表,如下图所示:(注意看清楚当前商品的库存和用户的余额)

接着,我们来编写CommodityDAOImpl类 和 CommodityServiceImpl类,前者负责实现对上面三张表的操作,后者负责将DAO中的方法灵活组合,以实现购买商品的业务逻辑处理。
CommodityDAOImpl类代码如下:
package com.cyan.spring.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @author : Cyan_RA9* @version : 22.0*/
@Repository(value = "commodityDAO")
public class CommodityDAOImpl {//使用 @Autowired 进行自动装配@Autowired@Qualifier(value = "jdbcTemplate01")private JdbcTemplate jdbcTemplate;//根据 商品id 查询 商品价格public Float queryPriceById(Integer c_id) {String sql = "SELECT `c_price` FROM `commodities` WHERE `c_id` = ?";Float c_price = jdbcTemplate.queryForObject(sql, Float.class, c_id);return c_price;}//用户购买商品后,需要减少用户账目中的余额public void decreaseBalance(Integer user_id, Float c_price) {String sql = "UPDATE `user_account` " + "\n" +"SET `user_balance` = `user_balance` - ? " + "\n" +"Where `user_id` = ?";jdbcTemplate.update(sql, c_price, user_id);}//用户购买商品后,需要减少对应的商品库存量public void decreaseAmount(Integer c_id, int amount){String sql = "UPDATEX `inventory_report` " + "\n" +"SET `ir_amount` = `ir_amount` - ? " + "\n" +"Where `ir_id` = ?";jdbcTemplate.update(sql, amount , c_id);}
}
CommodityServiceImpl类,代码如下:
package com.cyan.spring.service;import com.cyan.spring.dao.CommodityDAOImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author : Cyan_RA9* @version : 22.0*/
@Service
public class CommodityServiceImpl {@Autowiredprivate CommodityDAOImpl commodityDAO;/*** 该方法用于实现购买商品。* @param user_id:用户id* @param c_id:商品id* @param amount:购买数量*/public void purchase(int user_id, int c_id, int amount) {//1.查询到商品价格Float c_price = commodityDAO.queryPriceById(c_id);//2.购买商品后,更新对应用户的账户余额commodityDAO.decreaseBalance(user_id, c_price * amount);//3.购买商品后,更新对应的商品库存commodityDAO.decreaseAmount(c_id, amount);}
}
别忘了最关键的一步,我们还没有配置xml!up以beans_tx.xml为例,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:contex="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 配置扫面注解的包 --><contex:component-scan base-package="com.cyan.spring.dao"/><contex:component-scan base-package="com.cyan.spring.service"/><!-- 配置启动基于注解的声明式事务 --><!-- 注意一定要选择 springframework.org/schema/tx 下的tx命名空间 --><tx:annotation-driven transaction-manager="dataSourceTransactionManager01"/><!-- 配置properties文件 --><contex:property-placeholder location="classpath:druid.properties"/><!-- 配置数据源对象 --><bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource01"><property name="driverClassName" value="${driverClassName}"/><property name="url" value="${url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!-- 配置JdbcTemplate对象 --><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate01"><property name="dataSource" ref="dataSource01"/></bean><!-- 配置事务管理器对象 --><!--1.DataSourceTransactionManager实例,用于进行事务管理。2.一定要配置数据源属性,并且配置的数据源属性要和使用的jdbcTemplate配置的数据源一致。--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"id="dataSourceTransactionManager01"><constructor-arg name="dataSource" ref="dataSource01"/></bean>
</beans>
最后,up以TxTest类为测试类,令用户Five尝试购买两台迷你洗衣机,代码如下:
package com.cyan.spring.test;import com.cyan.spring.service.CommodityServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author : Cyan_RA9* @version : 22.0*/
public class TxTest {@Testpublic void testPurchase() {//1.获得容器对象ApplicationContext ioc = new ClassPathXmlApplicationContext("beans_tx.xml");//2.根据容器对象获得CommodityServiceImpl对象CommodityServiceImpl commodityServiceImpl = (CommodityServiceImpl) ioc.getBean("commodityServiceImpl");//3.根据获得的CommodityServiceImpl 调用业务层方法/*id = 2的用户(即Five用户,初始余额是2000块),购买了 2件 id = 1的商品(即迷你洗衣机)*/commodityServiceImpl.purchase(2, 1, 2);}
}
运行结果如下:

可能这时候就要有p小将(personable小将,指风度翩翩的人!)要说理了——啥玩意儿啊?写牛魔一大堆结果跑都跑不起来?👴要看的声明式事务搁哪儿呢?
p小将你先别急,注意看报错信息,是“bad SQL grammar”,意思就是我们的SQL语句语法错误,很快我们找到,是CommodityDAOImpl类中的 decreaseAmount方法 出错了,如下图所示:

其实up这里是 故意写错 的,那为什么要故意写错呢?——就是为了看看没有事务支持的后果。请注意,我们这时候是没有开启“声明式事务”的(因为压根就没有用声明式事务的注解),那么我们来看一下数据库中 商品库存表 和 用户账目表 的数据变化情况,如下图所示:(左边是用户帐目表,右边是商品库存表)

可以看到,在没有 事务 的支持下,这种错误造成的后果就是——商品发不出去(库存没有减少),而用户的余额被白白扣掉了!
下面我们引入声明式事务!其实只要在 purchase(int, int, int) 方法上面加上一个 @Transactional 即可,如下图所示:

这时候如果我们再次运行TxTest测试类,会发现数据库中的数据没有变化,如下GIF图演示:

可以看到,有了声明式事务的加持后,即便用于更新商品库存的SQL语句出错,但是由于事务本身的 原子性,事务中用于操作数据库的一组DML(Data Manipulation Language) 要么全部成功,要么全部失败(全部失败指的是无效),也就不会造成数据更新不完整的后果。
好的,up现在将之前 故意写错 的SQL语句更改过来,如下图所示:

然后我们重新进行测试(现在可是已经配置了声明式事务噢!),测试方法代码如下:
@Testpublic void testPurchase() {//1.获得容器对象ApplicationContext ioc = new ClassPathXmlApplicationContext("beans_tx.xml");//2.根据容器对象获得CommodityServiceImpl对象CommodityServiceImpl commodityServiceImpl = (CommodityServiceImpl) ioc.getBean("commodityServiceImpl");//3.根据获得的CommodityServiceImpl 调用业务层方法/*user_id = 1的用户(即Cyan用户,初始余额是500块),购买了 2件 c_id = 2的商品(即lamp,台灯)*/commodityServiceImpl.purchase(1, 2, 2);}
运行结果:

可以看到,在事务支持下,正确的数据库操作成功执行!
二、声明式事务的传播机制
1.引出问题:
如果一个事务中包含了其他的事务(例如,一个启动了声明式事务的方法中调用了其他方法,而被调用的方法本身也配置了声明式事务),那么该如何控制事务呢?
2.传播机制分类:
如下表所示:(尤其注意前两种方式)
| 传播属性 | 描述 |
|---|---|
| REQUIRED [默认] | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 |
| REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行。 如果有事务正在运行,应该将它挂起 |
| SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行。 否则它可以不运行在事务中 |
| NOT_SUPPORTED | 当前的方法不应该运行在事务中。 如果有运行的事务,将它挂起 |
| MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
| NEVER | 当前的方法不应该运行在事务中。 如果有运行的事务,就抛出异常 |
| NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。 否则,就启动一个新的事务,并在它自己的事务内运行 |
@Transactional 中的propagation元素,默认传播机制是REQUIRED,如下图所示:

我们下面来看一个演示案例。
3.应用实例:
需求:不同用户购买商品,要调用不同的方法。例如Cyan用户购买商品要调用f1()方法,Five用户购买商品要调用f2()方法。
在上文“声明式事务快速入门”中,我们在 CommodityDAOImpl类 定义了三个方法——queryPriceById, decreaseBalance 和 decreaseAmount,现在我们在这个类新增一个方法,叫做decreaseAmountWrongVersion(见名知意,这个方法无法正确执行),代码如下:
//一个故意写错的 用于更新商品库存量 的方法public void decreaseAmountWrongVersion(Integer c_id, int amount){String sql = "UPDATEXXXXX `inventory_report` " + "\n" +"SET `ir_amount` = `ir_amount` - ? " + "\n" +"Where `ir_id` = ?";jdbcTemplate.update(sql, amount , c_id);}
同样地,我们在 CommodityServiceImpl类 中,也新增一个 “故意写错” 的方法,代码如下:
/*** 这个方法是错误的,无法购买成功。*/@Transactionalpublic void purchaseWrongVersion(int user_id, int c_id, int amount) {Float c_price = commodityDAO.queryPriceById(c_id);commodityDAO.decreaseBalance(user_id, c_price * amount);//PS: decreaseAmountWrongVersion 这个方法是错误滴!!!commodityDAO.decreaseAmountWrongVersion(c_id, amount);}
这样在CommodityServiceImpl类中,就有两个用于购买的方法了,且这两个方法都用了 @Transactional 修饰,如下图所示:

好的,接下来我们还需要一个再来建 TxTestServiceImpl类,在这个类中我们要编写一个同样使用 @Transactional 修饰的方法,并在该方法中调用上图中的两个方法。TxTestServiceImpl类代码如下:
package com.cyan.spring.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author : Cyan_RA9* @version : 22.0*/
@Service
public class TxTestServiceImpl {@Autowired@Qualifier(value = "commodityServiceImpl")CommodityServiceImpl commodityService;/*** 注意,外层方法 doublePurchase 本身,也使用了 @Transactional 修饰。* 即调用者 doublePurchase 本身,亦是一个事务。*/@Transactionalpublic void doublePurchase() {// user_id = 1 的用户(Cyan)购买 1件 c_id = 2(lamp)的商品commodityService.purchase(1, 2, 1);// user_id = 2 的用户(Five)购买 2件 c_id = 3(keyboard)的商品commodityService.purchaseWrongVersion(2, 3, 2);}
}
注意,由于 @Transactional 启用的声明式事务,默认传播机制是REQUIRED,因此虽然 doublePurchase()方法 中调用的两个内层方法也都开启了声明式事务,但是都要算在外层方法的事务中,也就是仅仅有一个事务在进行,只要其中有一条DML失效,那么整个事务的操作全部作废。So,现在的这个 doublePurchase() 方法是一定执行失败的!
好嘞,这时候为了验证事务传播机制,最后就需要一个测试方法啦~。代码如下:
@Testpublic void testAffairPropagation() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans_tx.xml");TxTestServiceImpl txTestService = ioc.getBean(TxTestServiceImpl.class);txTestService.doublePurchase();}
先不急着运行,我们先来瞄一眼数据库中商品库存表 和 用户帐目表的情况,如下图所示:(左边是商品库存表,右边是用户帐目表)

运行测试方式,并查看数据库中商品库存表 和 用户帐目表的数据变化情况,如下GIF图所示:

可以看到,商品库存和用户余额都没有减少!这就是默认传播属性REQUIRED的机制。
还没完!下面我们把内层两个事务的 propagation属性 全部换成REQUIRED_NEW,如下图所示:

那根据上面表格中对REQUIRED_NEW的解释,此时内外层的事务是相互独立的呀!所以不难猜到,成功的事务将正确提交且无法被回滚,而失败的事务仍然会立即回滚。下面我们重新运行测试方法,如下图所示:

这个时候我们再去查看数据库中的商品库存表 和 用户帐目表的情况,如下图所示:(左边是商品库存表,右边是用户帐目表)

由此可知——
① 内层成功的事务在执行完毕后,会立即提交其独立事务,且已提交的事务无法回滚,即使外层事务后续失败。
② 内层失败的事务在抛出异常后,其独立事务会立即回滚,且异常会向上传播到外层事务,导致外层事务也回滚(除非外层捕获并处理异常)。
③ 外层事务回滚仅影响其自身的操作(如果有),不影响已提交的 内层的成功执行的 事务,即外层事务的回滚不会撤销已提交的内层的 REQUIRES_NEW 事务,因为它们是相互独立的。
三、声明式事务的隔离机制
1.四种隔离级别:
up之前在 “MySQL——事务机制与隔离机制”一文中,已经讲过了MySQL的四种隔离级别 和 脏读、幻读、不可重复读的定义,此处不再赘述,但up还是用表格来带大家简单回顾一下四种隔离级别,如下表所示:
| MySQL隔离级别(4种) | 脏读 | 幻读 | 不可重复读 | 加锁读 |
|---|---|---|---|---|
| Read Uncommitted:读未提交 | √ | √ | √ | No |
| Read committed:读已提交 | × | √ | √ | No |
| Repeatable Read:可重复读(默认) | × | × | × | No |
| Serializable:串行化(隔离级别最高) | × | × | × | Yes |
2.应用实例:
需求:连续两次查询同一商品的价格,但在查询第一次商品后,尝试更改事务的隔离机制,并测试两次查询的结果是否一致。
实现:我们在 CommodityServiceImpl类 中新增一个方法用于实现连续两次查询,queryTwice方法代码如下:(设置断点)
@Transactionalpublic void queryTwice(int c_id) {Float priceV1 = commodityDAO.queryPriceById(c_id);System.out.println("第一次查询得到的价格为:" + priceV1);Float priceV2 = commodityDAO.queryPriceById(c_id);System.out.println("第二次查询得到的价格为:" + priceV2);}
并且,我们要在第二次的查询语句那一行,设置一个断点,因为我们要通过Debug来进行测试。如下图所示:

注意,此时我们并没有为 @Transactional 设置隔离级别,那么它就是默认的Repeatable Read,在这种隔离级别下,事务可以读到启动事务时刻数据库中的数据,并且不会被其他事务所进行的DML操作所影响。
好嘞,最后还需要一个测试方法来验证我们的结论,代码如下:
@Testpublic void testIsolationMechanisms() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans_tx.xml");CommodityServiceImpl commodityService = ioc.getBean(CommodityServiceImpl.class);commodityService.queryTwice(3);}
我们对这个方法进行Debug,如下图所示:

可以看到,第一次查询的价格是150,并且程序现在卡在了断点处。
趁它卡住,我们悄咪咪去Navicat中修改id为3的商品的价格,如下图所示:

注意,由于up在修改语句时并没有开启新的事务,所以默认是直接提交的。接下来我们让程序直接跳转到下一个断点,如下图所示:

嗯?明明keyboard的价格已经被修改成了15,但是第二次查询却还是原来修改之前的150,这就是默认隔离机制——Repeatable Read!
好,接下来up将事务的隔离机制改为Read Committed,如下图所示:

然后进行同样的操作,先Debug,令程序卡在第一个断点,如下图所示:

然后去Navicat中悄咪咪修改价格,如下图所示:

让程序运行到下一个断点,如下图所示:

喏,还真不一样了,这就是 Read Commmitted 啦!
3.事务超时回滚:
事务超时回滚,是指如果一个事务执行的时间超过了某个时间限制,就让该事务回滚;我们可以通过 @Transactional 的timeout元素来设置超时时间。下面来看一个演示案例:
需求:测试事务超时回滚。
实现:在 CommodityServiceImpl类 中新定义一个 purchaseEX方法,代码如下:
@Transactional(timeout = 3)/*timeout = 3 表示如果事务的执行时间超过了3秒,就进行回滚。若没有设置timeout元素,默认就是-1,表示使用数据库默认的事务超时时间,或者不支持超时回滚。*/public void purchaseEX(int user_id, int c_id, int amount) {//1.查询到商品价格Float c_price = commodityDAO.queryPriceById(c_id);//2.购买商品后,更新对应用户的账户余额commodityDAO.decreaseBalance(user_id, c_price * amount);System.out.println("================ 4s > 3s =================");try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("================ 超时了,回滚! =================");//3.购买商品后,更新对应的商品库存commodityDAO.decreaseAmount(c_id, amount);System.out.println("👴购买成功🌶!");}
然后还是在测试类新建一个测试方法,代码如下:
@Testpublic void testTimeout() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans_tx.xml");CommodityServiceImpl commodityService = ioc.getBean(CommodityServiceImpl.class);/*id为2的用户(Five) 购买1件 id为1的商品(mini washing machine)*/commodityService.purchaseEX(2, 1, 1);}
在运行测试方法之前呢,我们先来瞄一眼数据库中商品库存表 和 用户帐目表的情况,如下图所示:(左边是商品库存表,右边是用户帐目表)

运行结果如下:

数据库有变化吗?如下图所示:

压根儿没变!看来事务确实超时回滚了!
为了确认事务超时回滚机制,我们把用于线程休眠的代码注释掉,然后重新运行测试方法,并查看数据库中商品库存表 和 用户帐目表的数据变化情况,如下GIF图所示:

可以看到,在事务未超时的情况下,数据都被正确地更新!
Δ总结
- 🆗,以上就是Spring系列博文 第七小节 的全部内容了。
- 总结一下,我们先是用一个详细的入门实例,了解了声明式事务的概念和使用,很明显地,我们可以感受到,声明式事务相比传统编程式事务最大的优点就是省事儿,不用写那么多重复的代码了,加两个注解统统搞定。之后,我们又一起学习了事务的传播机制和隔离机制,其实部分内容up在 MySQL系列 也分享过了,大家有兴趣可以去看看噢~。
- 另外,之后up还会带大家深入分析Spring的底层源码,并且还会和大家一起 手动实现Spring底层机制,敬请期待~❀。
- 下一节内容——暂时无了哈哈,Spring系列暂时告一段落,大家拜拜,感谢阅读!
System.out.println("END---------------------------------------------------------");
相关文章:
Spring 声明式事务 万字详解(通俗易懂)
目录 Δ前言 一、声明式事务快速入门 1.为什么需要声明式事务? 2.定义: 3.应用实例: 二、声明式事务的传播机制 1.引出问题: 2.传播机制分类: 3.应用实例: 三、声明式事务的隔离机制 1.四种隔离级别&…...
MySQL 当中的锁
MySQL 当中的锁 文章目录 MySQL 当中的锁MySQL 中有哪些主要类型的锁?请简要说明MySQL 的全局锁有什么用?MySQL 的表级锁有哪些?作用是什么?元数据锁(MetaData Lock,MDL)意向锁(Inte…...
fyrox 2D和3D游戏的制作
目录 fyrox介绍 1. 核心特性 1.1 高性能渲染 1.2 跨平台支持 1.3 物理引擎集成 1.4 脚本系统 1.5 场景管理 2. 架构设计 2.1 渲染器 2.2 资源管理器 2.3 输入系统 2.4 音频引擎 2.5 网络模块 3. 使用场景 3.1 2D游戏 3.2 3D游戏 3.3 模拟与教育应用 4. 在游戏…...
[Linux]基础IO
基础IO C文件IO相关操作磁盘文件与内存文件inode(index node)硬链接与软连接硬链接软连接总结 动静态库静态库动态库总结 C文件IO相关操作 当前路径:进程运行的时候,所处的路径叫做当前路径 打开文件的时候,一定是进…...
力扣刷题-热题100题-第27题(c++、python)
21. 合并两个有序链表 - 力扣(LeetCode)https://leetcode.cn/problems/merge-two-sorted-lists/description/?envTypestudy-plan-v2&envIdtop-100-liked 常规法 创建一个新链表,遍历list1与list2,将新链表指向list1与list2…...
Vue3 其它API Teleport 传送门
Vue3 其它API Teleport 传送门 在定义一个模态框时,父组件的filter属性会影响子组件的position属性,导致模态框定位错误使用Teleport解决这个问题把模态框代码传送到body标签下...
windows下安装sublime
sublime4 alpha 4098 版本 下载 可以根据待破解的版本选择下载 https://www.sublimetext.com/dev crack alpha4098 的licence 在----- BEGIN LICENSE ----- TwitterInc 200 User License EA7E-890007 1D77F72E 390CDD93 4DCBA022 FAF60790 61AA12C0 A37081C5 D0316412 4584D…...
golang 的strconv包常用方法
目录 1. 字符串与整数的转换 2. 字符串与浮点数的转换 3. 布尔值的转换 4. 字符串的转义 5. 补充:rune 类型的使用 方法功能详解 代码示例: 1. 字符串与整数的转换 方法名称功能描述示例Atoi将字符串转换为十进制整数。strconv.Atoi("123&q…...
ComplexE的代码注释
目录 dataloader.pymodel.pyrun.py 先安装软件,配置环境,搞了一周。再看代码写注释搞了一周。中间隔了一周。再安装环境跑代码又一周。最后结果是没结果。自己电脑内存带不动。还不想配电脑,又不会用GPU服务器。哭死哭死。心态崩了。直接发吧…...
vector<int> 的用法
vector<int> 是 C 标准模板库(STL)中的一个容器,用于存储动态大小的整数序列。以下是它的主要用法: 基本操作 1. 创建和初始化 #include <vector> using namespace std;vector<int> v1; // 空vector vector<int>…...
Java高级JVM知识点记录,内存结构,垃圾回收,类文件结构,类加载器
JVM是Java高级部分,深入理解程序的运行及原理,面试中也问的比较多。 JVM是Java程序运行的虚拟机环境,实现了“一次编写,到处运行”。它负责将字节码解释或编译为机器码,管理内存和资源,并提供运行时环境&a…...
名言警句1
1、嫉妒是欲望的衍生,而欲望则是成长的驱动,说到底每个人都是为了成长,为了不居人后,在成长的过程中,我们可以让欲望枝繁叶茂,但不能让嫉妒开花结果 2、人生无法奢求更多,我们有健康的身体&…...
【STL】queue
q u e u e queue queue 是一种容器适配器,设计为先进先出( F i r s t I n F i r s t O u t , F I F O First\ In\ First\ Out,\ FIFO First In First Out, FIFO)的数据结构,有两个出口,将元素推入队列的操作称为 p u …...
QXmpp入门
QXmpp 是一个基于 Qt 的 XMPP (Jabber) 协议实现库,用于开发即时通讯(IM)、聊天应用和实时协作系统。它支持客户端和服务端开发,提供完整的 XMPP 核心功能扩展。 1. 核心功能 XMPP 核心协议支持 支持 RFC 6120 (XMPP Core) 和 RFC 6121 (XMPP IM) 基础功能:认证、在线状态…...
使用cursor为代码添加注释
1. add-comments.md英文 You are tasked with adding comments to a piece of code to make it more understandable for AI systems or human developers. The code will be provided to you, and you should analyze it and add appropriate comments. To add comments to …...
20250330-傅里叶级数专题之离散时间傅里叶变换(4/6)
4. 傅里叶级数专题之离散时间傅里叶变换 20250328-傅里叶级数专题之数学基础(0/6)-CSDN博客20250330-傅里叶级数专题之傅里叶级数(1/6)-CSDN博客20250330-傅里叶级数专题之傅里叶变换(2/6)-CSDN博客20250330-傅里叶级数专题之离散傅里叶级数(3/6)-CSDN博客20250330-傅里叶级数专…...
漏洞挖掘---迅饶科技X2Modbus网关-GetUser信息泄露漏洞
一、迅饶科技 X2Modbus 网关 迅饶科技 X2Modbus 网关是功能强大的协议转换利器。“X” 代表多种不同通信协议,能将近 200 种协议同时转为 Modbus RTU 和 TCP 服务器 。支持 PC、手机端等访问监控,可解决组态软件连接不常见控制设备难题,广泛…...
Java进阶——位运算
位运算直接操作二进制位,在处理底层数据、加密算法、图像处理等领域具有高效性能和效率。本文将深入探讨Java中的位运算。 本文目录 一、位运算简介1. 与运算2. 或运算异或运算取反运算左移运算右移运算无符号右移运算 二、位运算的实际应用1. 权限管理2. 交换两个变…...
golang strings包常用方法
方法名称功能描述示例strings.Join将字符串切片中的元素连接成一个字符串,使用指定的分隔符。strings.Join([]string{"hello", "world"}, " ")strings.HasPrefix检查字符串是否以指定的前缀开头。strings.HasPrefix("hello"…...
网络安全之前端学习(css篇2)
那么今天我们继续来学习css,预计这一章跟完后,下一章就是终章。然后就会开始js的学习。那么话不多说,我们开始吧。 字体属性 之前讲到了css可以改变字体属性,那么这里来详细讲一讲。 1.1字体颜色 之前讲到了对于字体改变颜色食…...
PS底纹教程
1.ctrlshiftU 去色 2.新建纯色层 颜色中性灰;转换为智能对象 3.纯色层打开滤镜(滤镜库); 素描下找到半调图案,数值调成大小5对比1; 再新建一层,素描下找到撕边,对比拉到1&#x…...
NX/UG二次开发—CAM获取加工操作的最低Z深度值的方法
网上已经有些大佬给出了解决方案,但是基本有两种,一种内部函数,另外一种就是导出程序的刀轨文件找坐标计算。使用内部函数进行操作,可以自己学习,不做解释。下面只是针对第二种进行说明,参考胡君老师的教程…...
解决pyinstaller GUI打包时无法打包图片问题
当我们的python GuI在开发时。经常会用到图片作为背景,但是在打包后再启动GUI后却发现:原先调试时好端端的背景图片竟然不翼而飞或者直接报错。这说明图片没有被pyinstaller一起打包…… 要解决这个问题很简单,就是更改图片的存储方式。 tk…...
蓝桥杯真题------R格式(高精度乘法,高精度加法)
对于高精度乘法和加法的同学可以学学这几个题 高精度乘法 高精度加法 文章目录 题意分析部分解全解 后言 题意 给出一个整数和一个浮点数,求2的整数次幂和这个浮点数相乘的结果最后四舍五入。、 分析 我们可以发现,n的范围是1000,2的1000次方非常大&am…...
Nginx — Nginx安装证书模块(配置HTTPS和TCPS)
一、安装和编译证书模块 [rootmaster nginx]# wget https://nginx.org/download/nginx-1.25.3.tar.gz [rootmaster nginx]# tar -zxvf nginx-1.25.3.tar.gz [rootmaster nginx]# cd nginx-1.25.3 [rootmaster nginx]# ./configure --prefix/usr/local/nginx --with-http_stub_…...
回调后门基础
回调后门概述 回调后门(Reverse Shell)是一种常见的攻击方式,攻击者通过受害主机主动连接到远程服务器(攻击者控制的机器),从而获得远程控制权限。 工作原理 受害者主机 运行一个恶意代码,尝…...
深度学习 Deep Learning 第13章 线性因子模型
深度学习 Deep Learning 第13章 线性因子模型 内容概要 本章深入探讨了线性因子模型,这是一类基于潜在变量的概率模型,用于描述数据的生成过程。这些模型通过简单的线性解码器和噪声项捕捉数据的复杂结构,广泛应用于信号分离、特征提取和数…...
【个人笔记】用户注册登录思路及实现 springboot+mybatis+redis
基本思路 获取验证码接口 验证码操作用了com.pig4cloud.plugin的captcha-core这个库。 AccountControl的"/checkCode"接口代码,通过ArithmeticCaptcha生成一张验证码图片,通过text()函数得到验证码的答案保存到变量code,然后把图…...
华为OD机试2025A卷 - 正则表达式替换(Java Python JS C++ C )
最新华为OD机试 真题目录:点击查看目录 华为OD面试真题精选:点击立即查看 题目描述 为了便于业务互交,约定一个对输入的字符串中的下划线做统一替换。 具体要求如下: 输入字符串,将其中包含的每一个下划线“_”,使用特殊字符串(^|$|[,+])替换,并输出替换后的结果…...
WPS宏开发手册——Excel常用Api
目录 系列文章4、Excel常用Api4.1、判断是否是目标工作excel4.2、获取源工作表和目标工作表的引用4.3、获取单元格的值4.4、设置单元格的值4.5、合并单元格4.6、获取源范围4.7、获取源范围行数4.8、通过源来获取单元格的值4.9、设置单元格的背景颜色4.10、设置单元格的文字颜色…...

