Spring 声明式事务
Spring 声明式事务
- 1.Spring 事务管理概述
- 1.1 事务管理的重要性
- 1.2 Spring事务管理的两种方式
- 1.2.1 编程式事务管理
- 1.2.2 声明式事务管理
- 1.3 为什么选择声明式事务管理
- 2. 声明式事务管理
- 2.1 基本用法
- 2.2 常用属性
- 2.2.1 propagation(传播行为)
- 2.2.2 isolation(隔离级别)
- 2.2.3 readOnly(只读事务)
- 2.2.4 timeout(超时时间)
- 2.2.5 rollbackFor(回滚异常)
- 3. Spring事务失效
- 4. 案例
- 4.1 前期准备
- 4.1.1 依赖引入
- 4.1.2 数据库建表语句
- 4.1.3 实体类以及相关代码
- 4.2 转账案例
- 4.2.1 测试成功
- 4.2.2 测试回滚
- 4.2.3 测试受检异常
- 4.2.4 测试 rollBackFor 属性
- 4.2.5 测试隔离级别
Spring 提供了两个事务管理方式一种是编程式(很少用),一种是声明式事务。声明式事务管理将事务管理的代码从业务逻辑中分离出来,使得代码更清晰、可维护。使得开发者可以通过配置而不是编写大量的代码来管理事务。
使用这里我们只介绍声明式事务
1.Spring 事务管理概述
1.1 事务管理的重要性
在应用程序中,事务管理是确保数据操作的一致性、隔离性、持久性和原子性的关键机制。当多个数据库操作必须作为一个不可分割的单元执行时,事务管理变得至关重要。对于复杂的业务逻辑,事务能够确保在并发和异常情况下,数据库始终保持一致性。
1.2 Spring事务管理的两种方式
Spring框架提供了两种主要的事务管理方式,分别是编程式事务管理和声明式事务管理。
1.2.1 编程式事务管理
编程式事务管理要求开发者通过编写代码来管理事务的开始、提交和回滚。虽然具有灵活性,但容易导致代码冗余和可读性差。
try {// 开始事务transactionManager.beginTransaction();// 执行业务逻辑// 提交事务transactionManager.commit();
} catch (Exception e) {// 发生异常,回滚事务transactionManager.rollback();throw e;
}
1.2.2 声明式事务管理
相比之下,声明式事务管理通过配置文件或注解的方式实现事务控制,将事务逻辑从业务代码中分离出来。这种方式更加简洁、可维护,并提供更好的可读性。
@Transactional
public void performBusinessLogic() {// 业务逻辑
}
1.3 为什么选择声明式事务管理
选择声明式事务管理有以下优势:
- 简洁性: 通过注解或XML配置,开发者无需编写冗长的事务管理代码,使代码更加简洁清晰。
- 可维护性: 事务逻辑与业务逻辑分离,易于维护和理解。
- 可读性: 使用注解或XML配置,事务逻辑与业务逻辑在代码中更易于辨认,提高代码的可读性。
- 一致性: 通过统一的配置方式,整个应用程序可以保持一致的事务管理策略,减少错误和不一致性。
- 集成性: 声明式事务更好地与Spring的其他特性(如AOP)集成,提供更全面的解决方案。
综合而言,声明式事务管理是Spring中推荐的事务管理方式,它能够提高代码的可维护性、可读性,并与其他Spring特性协同工作,使得开发者能够更专注于业务逻辑的实现而不是事务的管理。
2. 声明式事务管理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
Spring 根据类或者方法上是否有@transactional
注解来判断是否开启事务。
2.1 基本用法
在类上使用
@Transactional
public class TestService {// 类中所有方法都将使用默认的事务配置
}
在方法上使用
public class TestService {@Transactionalpublic void method1() {// 这个方法将使用默认的事务配置}@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)public void method2() {// 这个方法将使用指定的事务配置}
}
2.2 常用属性
2.2.1 propagation(传播行为)
@Transactional
注解的 propagation
属性用于定义事务的传播行为,它指定在方法被调用时,当前方法的事务如何与现有的事务进行交互。
@Transactional(propagation = Propagation.*)
public void method1() {// ...
}
定义事务的传播行为,包括 :
REQUIRED
:默认,如果当前存在事务,则加入该事务,如果不存在事务,则新建一个事务。REQUIRES_NEW
:无论当前是否存在事务,都会创建一个新的事务,如果存在事务,则将其挂起。SUPPORTS
:如果当前存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行。MANDATORY
:该传播行为要求当前方法必须在一个事务中执行,否则将抛出异常。NOT_SUPPORTED
:以非事务的方式执行,如果当前存在事务,则将其挂起。NEVER
:以非事务的方式执行,如果当前存在事务,则抛出异常。NESTED
:如果当前存在事务,则创建一个嵌套事务,并在嵌套事务内执行。嵌套事务是外部事务的一部分,但有独立的提交和回滚。
2.2.2 isolation(隔离级别)
@Transactional
注解中的 isolation
属性用于指定事务的隔离级别。隔离级别定义了多个事务并发执行时,彼此之间的可见性和影响的程度。
@Transactional(isolation = Isolation.*)
public void method1() {// ...
}
Spring 支持以下五个隔离级别:
-
DEFAULT
:默认,使用底层数据库的默认隔离级别。通常为数据库的默认配置,比如 MySQL 默认的是REPEATABLE_READ
,而 Oracle 默认的是READ_COMMITTED
。 -
READ_UNCOMMITTED
(读未提交): 允许一个事务读取另一个事务未提交的数据。这是最低的隔离级别,可能导致脏读、不可重复读和幻读的问题。 -
READ_COMMITTED
(读已提交):保证一个事务提交后才能被其他事务读取。这是大多数数据库的默认隔离级别,可以避免脏读,但仍可能存在不可重复读和幻读的问题。 -
REPEATABLE_READ
(可重复读): 对相同字段的多次读取结果是一致的,除非自己进行了数据更新。避免了不可重复读的问题,但仍可能存在幻读的问题。 -
SERIALIZABLE
(串行化): 最高的隔离级别,确保每个事务都完全看不到其他事务的操作,包括读取和写入。可以避免脏读、不可重复读和幻读的问题,但也降低了并发性能。
2.2.3 readOnly(只读事务)
标识事务是否为只读,可以提高事务的性能。
@Transactional(readOnly = true)
public void method1() {// ...
}
2.2.4 timeout(超时时间)
指定事务的超时时间,单位为秒。
@Transactional(timeout = 60)
public void method1() {// ...
}
2.2.5 rollbackFor(回滚异常)
指定哪些异常触发事务回滚。@Transactional
注解默认只对运行时异常进行事务回滚,对检查时异常不回滚。
@Transactional(rollbackFor = {SQLException.class, MyCustomException.class})
public void method1() {// ...
}
3. Spring事务失效
哪些情况下会导致Spring事务失效,对应的原因是什么?
-
1.方法内的自调用:Spring事务是基于AOP的,只要使用代理对象调用某个方法时,Spring事务才能生效,而在一个方法中调用使用this.xxxO调用方法时,this并不是代理对象,所以会导致事务失效。
-
解决办法1:将需要在同一事务中执行的方法抽取到一个独立的Bean中,通过依赖注入的方式调用该Bean。确保方法调用经过代理对象,从而激活事务。
-
解决办法2:在类内部通过依赖注入的方式,将当前类注入到自己中,然后通过注入的对象调用方法。这样确保调用经过代理对象,从而使事务生效。
-
解决办法3:使用
AopContext.currentProxy()
获取当前代理对象,通过这个代理对象调用方法。结合@EnableAspectJAutoProxy(exposeProxy=true)
注解开启对当前代理对象的暴露,确保事务能够正确地被激活。
-
-
2.方法是private的:Spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来失效,子类是代理类,父类是被代理类,如果父类中的某个方法是private的,那么子类就没有办法重写它,也就没有办法额外增加Spring事务的逻辑。
-
3.方法是final的:原因和private是了样的,也是由于子类不能重写父类中的final的方法
-
4.单独的线程调用方法:当Mybatis或JdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象,如果开启事务的线程和执行SQL的线程是同一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿到不到数据库连接对象,这样,Mybatis或JdbcTemplate就会自己去新建一个数据库连接用来执行SQL,此数据库连接的autocommit为true,那么执行完SQL就会提交,后续再抛异常也就不能再回滚之前已经提交了的SQL了。
-
5.没加@Configuration注解:如果用SpringBoot基本没有这个问题,但是如果用的Spring,那么可能会有这个问题,这个问题的原因其实也是由于Mybatis或JdbcTemplate会从ThreadLocal中去获取数据库连接,但是ThreadLocal中存储的是一个MAP,MAP的key为DataSource对象,value为连接对象,而如果我们没有在AppConfig上添加@Configuration注解的话,会导致MAP中存的DataSource对象和Mybatis和JdbcTemplate中的DataSource对象不相等,从而也拿不到数据库连接,导致自己去创建数据库连接了。
-
6.异常被吃掉:如果Spring事务没有捕获到异常,那么也就不会回滚了,默认情况下Spring会捕获RuntimeException和Error。
-
7.类没有被Spring管理
-
8.数据库不支持事务
4. 案例
4.1 前期准备
4.1.1 依赖引入
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- 使用Plus 简化开发 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.17</version></dependency></dependencies>
4.1.2 数据库建表语句
CREATE TABLE `users` (`id` int NOT NULL AUTO_INCREMENT,`user_name` varchar(18) NOT NULL,`balance` decimal(10, 2) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
)
4.1.3 实体类以及相关代码
User
@Data
public class User {private int id;private String userName;private Double balance;
}
UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
UserService
public interface UserService extends IService<User> {
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
application.yml
Spring:datasource:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 120125hzy.type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus: # MyBatis Plus配置configuration: map-underscore-to-camel-case: true # 驼峰下划线转换
logging: # 控制台打印 SQLlevel:com:example:mapper: debug
4.2 转账案例
在Spring中,你可以使用@Transactional
注解来实现声明式事务。这个注解可以应用于类级别或方法级别,具体取决于你想要控制事务的粒度。
接口编写
/*** 测试转账*/String Transfer(Integer fromId, Integer toId, Double money);
生成单元测试,每次测试前两人余额都调整为 10000.00
@SpringBootTest
class UserServiceImplTest {@Autowiredprivate UserService userService;@Testvoid transfer() {String ans = userService.Transfer(1,2,600.0);assert ans.equals("转账成功");}
}
4.2.1 测试成功
@Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}
测试通过
数据库也成功修改
4.2.2 测试回滚
@Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 手动抛出异常,测试事务回滚if (1 == 1) throw new RuntimeException("转账异常,事务回滚");// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}
测试未通过
数据库也没有改变
4.2.3 测试受检异常
@Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) throws SQLException {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 手动抛出异常,测试事务回滚if (1 == 1) throw new RuntimeException("转账异常,事务回滚");// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new SQLException("转账失败: " + e.getMessage());}}
测试未通过
事务未回滚
4.2.4 测试 rollBackFor 属性
@Transactional(rollbackFor = SQLException.class)
这次成功回滚了。
4.2.5 测试隔离级别
添加一个接口
/*** 测试付款*/String payment(Integer id,Double money);
@Override@Transactional()public String payment(Integer id, Double money) {try {User user = getById(id);if (user == null) {throw new RuntimeException("支付账户不存在");}// 更新转出账户余额user.setBalance(user.getBalance()-money);updateById(user);Thread.sleep(5000);if (1 == 1) throw new RuntimeException("转账异常,事务回滚");log.info("支付成功");}catch (RuntimeException e){throw new RuntimeException("支付失败");}catch (Exception e){}return "支付成功";}
修改 Transfer()
@Override@Transactional(isolation = Isolation.READ_UNCOMMITTED)public String Transfer(Integer fromId, Integer toId, Double money){try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}log.info("id: {}的余额:{}",fromUser.getId(),fromUser.getBalance());// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}
测试函数
@Testvoid test() throws InterruptedException {Thread t1 = new Thread(()->{userService.payment(1,500.0);});t1.start();Thread.sleep(2000);userService.Transfer(1,2,600.0);}
转账接口读取到了支付接口修改的数据,最终转账成功。而支付接口支付失败,但是张三还是少了1100
相关文章:

Spring 声明式事务
Spring 声明式事务 1.Spring 事务管理概述1.1 事务管理的重要性1.2 Spring事务管理的两种方式1.2.1 编程式事务管理1.2.2 声明式事务管理 1.3 为什么选择声明式事务管理 2. 声明式事务管理2.1 基本用法2.2 常用属性2.2.1 propagation(传播行为)2.2.2 iso…...

通达OA inc/package/down.php接口存在未授权访问漏洞
声明 本文仅用于技术交流,请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 一. 产品简介 通达OA(Office Anywhere网络智能办公系统&am…...

数据库原理: 笛卡儿积
笛卡儿积(Cartesian Product)是集合论中的一个概念,也在数据库中的查询操作中经常使用。笛卡儿积是指两个集合(或更多集合)之间所有可能的组合。如果有两个集合A和B,它们的笛卡儿积记作A B,表示…...

docker安装配置prometheus+node_export+grafana
简介 Prometheus是一套开源的监控预警时间序列数据库的组合,Prometheus本身不具备收集监控数据功能,通过获取不同的export收集的数据,存储到时序数据库中。Grafana是一个跨平台的开源的分析和可视化工具,将采集过来的数据实现可视…...

【JavaScript】JS——Map数据类型
【JavaScript】JS——Map数据类型 什么是Map?特性Map与Object的比较 map的创建map的属性map相关方法map的遍历 什么是Map? 存储键值对的对象。 能够记住键的原始插入顺序任何值(对象或原始值)都可以作为键或值。 特性 Map中的一个键只能出现一次&am…...

【【FPGA的 MicroBlaze 的 介绍与使用 】】
FPGA的 MicroBlaze 的 介绍与使用 可编程片上系统(SOPC)的设计 在进行系统设计时,倘若系统非常复杂,采用传统 FPGA 单独用 Verilog/VHDL 语言进行开发的方式,工作量无疑是巨大的,这时调用 MicroBlaze 软核…...

PyQt pdf格式保存
参考文章 pyqt5:利用QFileDialog从本地选择图片\文本文档显示到label、保存图片\label文本到本地(附代码)_pyqt5中qfiledialog.getopenfileurl-CSDN博客 txt文件的打开与保存 def openTextFile(self): # 选择文本文件上传fd,fp QFileDialog.getOpen…...

微前端介绍
目录 微前端概念 微前端特性 场景演示 微前端方案 iframe 方案 qiankun 方案 micro-app 方案 EMP 方案 无界微前端 方案 无界方案 成本低 速度快 原生隔离 功能强大 总结 前言:微前端已经是一个非常成熟的领域了,但开发者不管采用哪个现…...

工业机器视觉megauging(向光有光)使用说明书(一,轻量级的visionpro)
机器视觉megauging(未名之光,向光有光)程序软件资源已经发布,欢迎下载尝新 8:11 2023/12/2 首先,既然觉得可以发表了,就发表。 其次,我这个人没写过什么软件使用说明书,既然走到这路…...

Java——面试:String 和 StringBuffer 的区别?
相同点: String 和 StringBuffer,它们可以储存和操作字符串, 即包含多个字符的字符数据。 String 和 StringBuffer 的区别有以下几点: 1.String 类提供了数值不可改变的字符串。而 StringBuffer 类提供的字符串进行修改。 当你知…...

图扑软件受邀出席高交会-全球清洁能源创新博览会
“相聚鹏城深圳,共享能源盛宴” 第二十五届中国国际高新技术成果交易会(简称“高交会”)于 11 月 15-18 日在深圳盛大开幕。高交会由商务部、科学技术部、工业和信息化部、国家发展改革委、农业农村部、国家知识产权局、中国科学院、中国工程院和深圳市人民政府共同…...

vue项目下npm或yarn下安装echarts多个版本
最近在大屏展示的时候,用到了百度的echarts图表库,看完效果图后,又浏览了一下echarts官网案例,大同小异。但是搬砖过程中发现实际效果和demo相差甚远,一番折腾发现,项目中安装的是echarts4.x版本࿰…...

在内网开发中使用Nginx代理来访问钉钉新版服务端API
如果你在内网开发中使用Nginx代理来访问钉钉新版服务端API,你可以在Nginx配置文件中进行相应的配置。 以下是一个简单的示例Nginx配置,用于将对指定URL的请求代理到钉钉服务端API: server { listen 80; server_name your_server_domain; l…...

机器学习算法如何进行特征重要性评估
特征重要性评估是机器学习中一种常用的方法,用于确定输入特征对模型预测的贡献程度。以下是几种常见的机器学习算法进行特征重要性评估的方法: 1 决策树算法(如随机森林和梯度提升树):决策树算法可以通过计算每个特征…...

运行启动vue项目报报错node: --openssl-legacy-provider is not allowed in NODE_OPTIONS解决
报错的问题就是package.json中的Scripts下的dev 解决方法就是要不升级你的应用代码,支持 新版本的node.js 要不就是删除SET NODE_OPTIONS--openssl-legacy-provider &&代码,如下代码即可正常运行起来...

网工学习5 交换机端口相关配置
交换机的接口属性默认支待一般网络环境,一般情况下是不需要对其接口进行设置的。在某些情况下需 要对其端口属性进行配置时,配置的对象主要有接口隔离、速率、双工等信息。 5.1 接口隔离设置 > 配置接口 GE0/0/1 和 GE0/0/2 的接口隔离功能…...

使用Pytorch从零开始实现CLIP
生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I,Transformer II [3] 变分自编码器 [4] 生成对抗网络,高级生成对抗网络 I,高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…...

Java网络编程 *TCP与UDP协议*
网络编程 什么是计算机网络? 把分布在不同地理区域的具有独立功能的计算机,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统 简单来说就是把不同地区的计算机通过设备连接起来,实现不同地区之前的数据传输 网络编程是干什么的? 网络…...

校园外卖小程序源码系统 附带完整的搭建教程
随着大学生消费水平的提高,对于外卖服务的需求也在不断增加。很多学生都面临着课业繁重、时间紧张等问题,无法亲自到餐厅就餐。因此,开发一款适合校园外卖市场的应用软件,将为广大学生提供极大的便利。 以下是部分代码示例&#…...

TiDB专题---1、TiDB简介和特性
什么是TiDB TiDB 是一个分布式 NewSQL 数据库,它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。 TiDB 是 PingCAP 公司自主设计、研发…...

如何二次封装一个Vue3组件库?
为什么要二次封装组件库 目前开源的Vue3组件库有很多,包括Element Plus、Ant Design Vue、Naive UI、Vuetify、Varlet等等。 在大部分场景中,我们直接使用现有组件库中的组件即可实现功能。如果遇到部分组件的特殊配置或者特殊逻辑,或者当前…...

2024年网络安全比赛--系统渗透测试(超详细)
一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.在渗透机中对服务器主机进行信息收集,将服务器开启的端口号作为 Flag 值提交; 2.在渗透机中对服务器主机进行渗透,在服务器主机中获取服务器主机名称ÿ…...

高效的单行python脚本
#-- coding: utf-8 -- “”" Created on Wed Dec 6 13:42:00 2023 author: czliu “”" 1. 平方列表推导 #使用列表推导法计算从 1 到 10 的数字平方 squares [x**2 for x in range(1, 11)] print(squares)2.求偶数 #可以使用列表推导式从列表中筛选偶数。还可以…...

如何通过内网穿透实现无公网IP也能远程访问内网的宝塔面板
文章目录 一、使用官网一键安装命令安装宝塔二、简单配置宝塔,内网穿透三、使用固定公网地址访问宝塔 宝塔面板作为建站运维工具,适合新手,简单好用。当我们在家里/公司搭建了宝塔,没有公网IP,但是想要在外也可以访问内…...

【广州华锐互动】VR沉浸式体验铝厂安全事故让伤害教育更加深刻
随着科技的不断发展,虚拟现实(VR)技术已经逐渐渗透到各个领域,为我们的生活带来了前所未有的便捷和体验。在安全生产领域,VR技术的应用也日益受到重视。 VR公司广州华锐互动就开发了多款VR安全事故体验系统,…...

CFLAGS、CXXFLAGS、FFLAGS、FCFLAGS、LDFLAGS、LD_LIBRARY_PATH区别
这些环境变量在编译和链接过程中扮演着重要的角色。下面是对每个环境变量的详细说明及示例: CFLAGS:用于设置C编译器的编译选项。 示例:将优化级别设置为最高,启用所有警告信息,并指定目标体系结构为x86-64。 export C…...

阿里云租赁费用_阿里云服务器多配置报价表
阿里云服务器租用费用,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、轻量应用服务器2核2G3M带宽轻量服务器一年87元,2核4G4M带宽轻量服务器一年165元12个月,ECS云服务器e系列2核2G配置99元一年、2核4G配置365元一年、2核8G配置522元一年…...

网络层(1)——概述
一、概述 网络层毫无疑问是最复杂的一层,涉及到大量的协议与结构的内容。在如今主流的设计中,大家都会把网络层分成两个部分:数据平面、控制平面。其中数据平面指的是网络层中每台路由器的功能,它决定了到达路由器端口输入链路之一…...

计算机网络——网络层
目录 一、网络层的作用 二、网络层的协议 (一)ARP地址解析协议 (二)ICMP国际控制报文协议 (三)IGMP网际组织管理协议 三、ip地址 (一)ip地址的概念 (二ÿ…...

Antd search input无中框
发现input.search, 搜索图标的左侧有个竖线,不是很好看 把它改掉, 新建一个自己的CSS .custom-search-input{.ant-input-affix-wrapper{border-right: none !important;}.ant-input-group-addon{.ant-btn{border-left: none !important;}}}应用 <S…...