当前位置: 首页 > news >正文

Spring事务详解

一、Spring对事务的支持

1.事务概述

  • 什么是事务

    • 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。

    • 多条DML要么同时成功,要么同时失败,这叫做事务。

    • 事务:Transaction(tx)

  • 事务的四个处理过程:

    • 第一步:开启事务 (start transaction)

    • 第二步:执行核心业务代码

    • 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)

    • 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)

  • 事务的四个特性:

    • A 原子性:事务是最小的工作单元,不可再分。

    • C 一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。

    • I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。

    • D 持久性:持久性是事务结束的标志。

2.引入事务场景

以银行账户转账为例学习事务。两个账户act-001和act-002。act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。

(一个减成功,一个加成功, 这两条update语句必须同时成功,或同时失败。)

连接数据库的技术采用Spring框架的JdbcTemplate。 采用三层架构搭建:

模块名:spring6-013-tx-bank(依赖如下)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.powernode</groupId><artifactId>spring6-013-tx-bank</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><!--仓库--><repositories><!--spring里程碑版本的仓库--><repository><id>repository.spring.milestone</id><name>Spring Milestone Repository</name><url>https://repo.spring.io/milestone</url></repository></repositories><!--依赖--><dependencies><!--spring context--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.0-M2</version></dependency><!--spring jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.0-M2</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--德鲁伊连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.13</version></dependency><!--@Resource注解--><dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>

第一步:准备数据库表

第二步:创建包结构

第三步:准备POJO类

package com.bank.pojo;/*** 银行账户类* @className Account* @since 1.0**/
public class Account {private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"actno='" + actno + '\'' +", balance=" + balance +'}';}public Account() {}public Account(String actno, Double balance) {this.actno = actno;this.balance = balance;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}
}

第四步:编写持久层

AccountDao接口

package com.bank.dao;import com.bank.pojo.Account;/*** 专门负责账户信息的CRUD操作。* DAO中只执行SQL语句,没有任何业务逻辑。* 也就是说DAO不和业务挂钩。* @className AccountDao* @since 1.0**/
public interface AccountDao {/*** 根据账号查询余额* @param actno* @return*/Account selectByActno(String actno);/*** 更新账户* @param act* @return*/int update(Account act);}

AccountDaoImpl类

package com.bank.dao.impl;import com.bank.pojo.Account;
import com.bank.dao.AccountDao;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @className AccountDaoImpl* @since 1.0**/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {@Resource(name = "jdbcTemplate")private JdbcTemplate jdbcTemplate;@Overridepublic Account selectByActno(String actno) {String sql = "select actno, balance from t_act where actno = ?";Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);return account;}@Overridepublic int update(Account act) {String sql = "update t_act set balance = ? where actno = ?";int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());return count;}
}

第五步:编写业务层

AccountService 接口

/*** 业务接口* 事务就是在这个接口下控制的。* @className AccountService* @since 1.0**/
public interface AccountService {/*** 转账业务方法* @param fromActno 从这个账户转出* @param toActno 转入这个账户* @param money 转账金额*/void transfer(String fromActno, String toActno, double money);}

/*** @className AccountServiceImpl* @since 1.0**/
@Service("accountService")
public class AccountServiceImpl implements AccountService {@Resource(name = "accountDao")private AccountDao accountDao;@Overridepublic void transfer(String fromActno, String toActno, double money) {// 查询账户余额是否充足Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new RuntimeException("账户余额不足");}// 余额充足,开始转账Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);int count = accountDao.update(fromAct);count += accountDao.update(toAct);if (count != 2) {throw new RuntimeException("转账失败,请联系银行");}}
}

第六步:编写Spring配置文件

<?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:context="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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><!--组件扫描--><context:component-scan base-package="com.bank"/><!--配置数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jdbc"/><property name="username" value="root"/><property name="password" value="abc123"/></bean><!--配置JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"/></bean></beans>

第七步:编写表示层(测试程序)

   @Testpublic void testSpringTx() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");AccountService accountService = applicationContext.getBean("accountService", AccountService.class);try {accountService.transfer("act001", "act002", 10000);System.out.println("转账成功");} catch (Exception e) {e.printStackTrace();}}

模拟异常

@Service("accountService")
public class AccountServiceImpl implements AccountService {@Resource(name = "accountDao")private AccountDao accountDao;@Overridepublic void transfer(String fromActno, String toActno, double money) {// 查询账户余额是否充足Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new RuntimeException("账户余额不足");}// 余额充足,开始转账Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);int count = accountDao.update(fromAct);// 模拟异常String s = null;s.toString();count += accountDao.update(toAct);if (count != 2) {throw new RuntimeException("转账失败,请联系银行");}}
}

执行结果:

数据库表中数据:

丢了1万。

3.Spring对事务的支持

⑴.Spring实现事务的两种方式

  • 编程式事务

    • 通过编写代码的方式来实现事务的管理。

  • 声明式事务

    • 基于注解方式

    • 基于XML配置方式

⑵.Spring事务管理API

Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套API,API的核心接口如下:

PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:

  • DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。

  • JtaTransactionManager:支持分布式事务管理。

如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)

⑶.声明式事务之注解实现方式

  • 第一步:在spring配置文件中配置事务管理器。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
  • 第二步:在spring配置文件中引入tx命名空间。

<?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:context="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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  • 第三步:在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。

<tx:annotation-driven transaction-manager="transactionManager"/>
  • 第四步:在service类上或方法上添加@Transactional注解

在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。

@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {@Resource(name = "accountDao")private AccountDao accountDao;// 控制事务,因为在这个方法中要完成所有的转账业务。@Overridepublic void transfer(String fromActno, String toActno, double money) {// 第一步:开启事务// 第二步:执行核心业务逻辑// 查询转出账户的余额是否充足Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new RuntimeException("余额不足!!!");}// 余额充足Account toAct = accountDao.selectByActno(toActno);// 将内存中两个对象的余额先修改。fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 数据库更新int count = accountDao.update(fromAct);// 模拟异常String s = null;s.toString();count += accountDao.update(toAct);if(count != 2) {throw new RuntimeException("转账失败,联系银行!");}}}

当前数据库表中的数据:

执行测试程序:

虽然出现异常了,再次查看数据库表中数据:

通过测试,发现数据没有变化,事务起作用了。

⑷.事务属性

①事务属性包括哪些

事务中的重点属性:

  • 事务传播行为

  • 事务隔离级别

  • 事务超时

  • 只读事务

  • 设置出现哪些异常回滚事务

  • 设置出现哪些异常不回滚事务

②事务传播行为

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

事务传播行为在spring框架中被定义为枚举类型:

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】

  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】

  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】

  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】

  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】

  • NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】

  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

在代码中设置事务的传播行为:

@Transactional(propagation = Propagation.REQUIRED)

可以编写程序测试一下传播行为:

Dao

package com.example.dao;import com.example.entity.Account;// 账户数据访问对象接口,定义了账户相关的数据库操作方法
public interface AccountDao {// 根据账户号码查询账户信息Account findByAccountNumber(String accountNumber);// 更新账户余额void updateBalance(Account account);
}
package com.example.dao;import com.example.entity.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;// 账户数据访问对象实现类,实现了 AccountDao 接口
public class AccountDaoImpl implements AccountDao {private JdbcTemplate jdbcTemplate;// 设置 JdbcTemplatepublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}// 实现根据账户号码查询账户信息的方法@Overridepublic Account findByAccountNumber(String accountNumber) {String sql = "SELECT * FROM accounts WHERE account_number = ?";return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), accountNumber);}// 实现更新账户余额的方法@Overridepublic void updateBalance(Account account) {String sql = "UPDATE accounts SET balance = ? WHERE account_number = ?";jdbcTemplate.update(sql, account.getBalance(), account.getAccountNumber());}
}

service

package com.example.service;// 转账服务接口,定义了转账相关的业务逻辑方法
public interface TransferService {// 从一个账户向另一个账户转账void transfer(String fromAccountNumber, String toAccountNumber, double amount);
}
package com.example.service;import com.example.dao.AccountDao;
import com.example.entity.Account;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;// 转账服务实现类,实现了 TransferService 接口
public class TransferServiceImpl implements TransferService {private AccountDao accountDao;// 设置账户数据访问对象public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}// 实现转账方法,使用 Propagation.REQUIRED 传播行为@Override@Transactional(propagation = Propagation.REQUIRED)public void transfer(String fromAccountNumber, String toAccountNumber, double amount) {// 查询转出账户信息Account fromAccount = accountDao.findByAccountNumber(fromAccountNumber);// 查询转入账户信息Account toAccount = accountDao.findByAccountNumber(toAccountNumber);// 检查转出账户余额是否充足if (fromAccount.getBalance() < amount) {throw new RuntimeException("余额不足,无法转账");}// 减少转出账户余额fromAccount.setBalance(fromAccount.getBalance() - amount);// 增加转入账户余额toAccount.setBalance(toAccount.getBalance() + amount);// 更新转出账户余额到数据库accountDao.updateBalance(fromAccount);// 模拟异常,用于测试事务回滚if (true) {throw new RuntimeException("模拟转账过程中出现异常");}// 更新转入账户余额到数据库accountDao.updateBalance(toAccount);}
}

entity

package com.example.entity;import java.io.Serializable;// 账户实体类,用于表示银行账户信息
public class Account implements Serializable {private static final long serialVersionUID = 1L;private Integer id;private String accountNumber;private double balance;// 无参构造函数public Account() {}// 带参构造函数,用于初始化账户信息public Account(String accountNumber, double balance) {this.accountNumber = accountNumber;this.balance = balance;}// 获取账户 IDpublic Integer getId() {return id;}// 设置账户 IDpublic void setId(Integer id) {this.id = id;}// 获取账户号码public String getAccountNumber() {return accountNumber;}// 设置账户号码public void setAccountNumber(String accountNumber) {this.accountNumber = accountNumber;}// 获取账户余额public double getBalance() {return balance;}// 设置账户余额public void setBalance(double balance) {this.balance = balance;}// 重写 toString 方法,方便打印账户信息@Overridepublic String toString() {return "Account{" +"id=" + id +", accountNumber='" + accountNumber + '\'' +", balance=" + balance +'}';}
}

applicationContext.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:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 数据源配置,使用 DriverManagerDataSource 连接数据库 --><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><!-- 数据库驱动类 --><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><!-- 数据库连接地址,需要替换为实际的数据库地址 --><property name="url" value="jdbc:mysql://localhost:3306/jdbc"/><!-- 数据库用户名,需要替换为实际的用户名 --><property name="username" value="root"/><!-- 数据库密码,需要替换为实际的密码 --><property name="password" value="abc123"/></bean><!-- JdbcTemplate 配置,用于执行 SQL 语句 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!-- 注入数据源 --><property name="dataSource" ref="dataSource"/></bean><!-- 事务管理器配置,使用 DataSourceTransactionManager 管理事务 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 注入数据源 --><property name="dataSource" ref="dataSource"/></bean><!-- 开启注解驱动的事务管理 --><tx:annotation-driven transaction-manager="transactionManager"/><!-- DAO 层配置,创建账户数据访问对象 --><bean id="accountDao" class="com.example.dao.AccountDaoImpl"><!-- 注入 JdbcTemplate --><property name="jdbcTemplate" ref="jdbcTemplate"/></bean><!-- 服务层配置,创建转账服务对象 --><bean id="transferService" class="com.example.service.TransferServiceImpl"><!-- 注入账户数据访问对象 --><property name="accountDao" ref="accountDao"/></bean>
</beans>

Test

package com.bank.test;import com.example.service.TransferService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;// 测试类,用于测试转账服务
public class TestTransferService {public static void main(String[] args) {// 加载 Spring 配置文件ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 从 Spring 容器中获取转账服务对象TransferService transferService = context.getBean("transferService", TransferService.class);try {// 执行转账操作transferService.transfer("123456", "654321", 100.0);System.out.println("转账成功");} catch (RuntimeException e) {System.out.println("转账失败: " + e.getMessage());}}
}

此案例验证了 Propagation.REQUIRED 传播行为在银行转账场景中的应用。当执行 transfer 方法时,如果没有外部事务,会创建一个新的事务;若在操作过程中出现异常,整个事务会回滚,从而保证转账双方账户余额的一致性。

一定要集成Log4j2日志框架,在日志信息中可以看到更加详细的信息。

③事务隔离级别

事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。 数据库中读取数据存在的三大问题:(三大读问题)

  • 脏读:读取到没有提交到数据库的数据,叫做脏读。

  • 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。

  • 幻读:读到的数据是假的。

事务隔离级别包括四个级别:

  • 读未提交:READ_UNCOMMITTED

    • 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。

  • 读提交:READ_COMMITTED

    • 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。

  • 可重复读:REPEATABLE_READ

    • 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。

  • 序列化:SERIALIZABLE

    • 解决了幻读问题,事务排队执行。不支持并发。

大家可以通过一个表格来记忆:

隔离级别脏读不可重复读幻读
读未提交
读提交
可重复读
序列化

在Spring代码中如何设置隔离级别? 隔离级别在spring中以枚举类型存在:

image.png

@Transactional(isolation = Isolation.READ_COMMITTED)

测试事务隔离级别:READ_UNCOMMITTED 和 READ_COMMITTED

怎么测试:一个service负责插入,一个service负责查询。负责插入的service要模拟延迟。

一个未提交,另一个已经读到:READ_UNCOMMITTED

Dao

package com.bank.dao;import com.bank.pojo.Account;/*** 专门负责账户信息的CRUD操作。* DAO中只执行SQL语句,没有任何业务逻辑。* 也就是说DAO不和业务挂钩。* @className AccountDao* @since 1.0**/
public interface AccountDao {/*** 根据账号查询账户信息* @param actno* @return*/Account selectByActno(String actno);/*** 更新账户信息。* @param act* @return*/int update(Account act);/*** 保存账户信息* @param act* @return*/int insert(Account act);}
package com.bank.dao.impl;import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @className AccountDaoImpl* @since 1.0**/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {@Resource(name = "jdbcTemplate")private JdbcTemplate jdbcTemplate;@Overridepublic Account selectByActno(String actno) {String sql = "select actno, balance from t_act where actno = ?";Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);return account;}@Overridepublic int update(Account act) {String sql = "update t_act set balance = ? where actno = ?";int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());return count;}@Overridepublic int insert(Account act) {String sql = "insert into t_act values(?,?)";return jdbcTemplate.update(sql, act.getActno(), act.getBalance());}}

pojo

package com.bank.pojo;/*** 银行账户类* @className Account* @since 1.0**/
public class Account {private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"actno='" + actno + '\'' +", balance=" + balance +'}';}public Account() {}public Account(String actno, Double balance) {this.actno = actno;this.balance = balance;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}
}

service

package com.bank.service.impl;import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;/*** @className IsolationService1* @since 1.0**/
@Service("i1")
public class IsolationService1 {@Resource(name = "accountDao")private AccountDao accountDao;// 1号// 负责查询// 当前事务可以读取到别的事务没有提交的数据。//@Transactional(isolation = Isolation.READ_UNCOMMITTED)// 对方事务提交之后的数据我才能读取到。@Transactional(isolation = Isolation.READ_UNCOMMITTED)public void getByActno(String actno) {Account account = accountDao.selectByActno(actno);System.out.println("查询到的账户信息:" + account);}}

/*** @className IsolationService2* @since 1.0**/
@Service("i2")
public class IsolationService2 {@Resource(name = "accountDao")private AccountDao accountDao;// 2号// 负责insert@Transactional(isolation = Isolation.READ_UNCOMMITTED)public void save(Account act) throws IOException {// 睡眠一会try {Thread.sleep(1000 *10);} catch (InterruptedException e) {e.printStackTrace();}accountDao.insert(act);}}

Test

    @Testpublic void testIsolation1() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");IsolationService1 i1 = applicationContext.getBean("i1", IsolationService1.class);i1.getByActno("act003");}@Testpublic void testIsolation2() throws IOException {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");IsolationService2 i2 = applicationContext.getBean("i2", IsolationService2.class);Account act = new Account("act003", 1000.0);i2.save(act);}
④事务超时

代码如下:

@Transactional(timeout = 10)

以上代码表示设置事务的超时时间为10秒。

表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

默认值-1,表示没有时间限制。

这里有个坑,事务的超时时间指的是哪段时间?

在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。

@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {accountDao.insert(act);// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}
}
@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}accountDao.insert(act);
}

当然,如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。

⑤只读事务

代码如下:

@Transactional(readOnly = true)

将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。

该特性的作用是:启动spring的优化策略。提高select语句执行效率。

如果该事务中确实没有增删改操作,建议设置为只读事务。

⑥设置哪些异常回滚事务

代码如下:

@Transactional(rollbackFor = RuntimeException.class)
⑦设置哪些异常不回滚事务

代码如下:

@Transactional(noRollbackFor = NullPointerException.class)

表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。

⑸.事务的全注解式开发

编写一个类来代替配置文件,代码如下:

package com.bank;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;/*** @className Spring6Config* @since 1.0**/
@Configuration // 代替spring.xml配置文件,在这个类当中完成配置。
@ComponentScan("com.bank") // 组件扫描
@EnableTransactionManagement // 开启事务注解
public class Spring6Config {// Spring框架,看到这个@Bean注解后,会调用这个被标注的方法,这个方法的返回值是一个java对象,这个java对象会自动纳入IoC容器管理。// 返回的对象就是Spring容器当中的一个Bean了。// 并且这个bean的名字是:dataSource@Bean(name = "dataSource")public DruidDataSource getDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc");dataSource.setUsername("root");dataSource.setPassword("abc123");return dataSource;}@Bean(name = "jdbcTemplate")public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //Spring在调用这个方法的时候会自动给我们传递过来一个dataSource对象。JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Bean(name = "txManager")public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager txManager = new DataSourceTransactionManager();txManager.setDataSource(dataSource);return txManager;}}

⑹.声明式事务之XML实现方式

配置步骤:

  • 第一步:配置事务管理器

  • 第二步:配置通知

  • 第三步:配置切面

记得添加aspectj的依赖:

<!--aspectj依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.0-M2</version>
</dependency>

Spring配置文件如下:

记得添加aop的命名空间。

<?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:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--组件扫描--><context:component-scan base-package="com.bank"/><!--配置数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jdbc"/><property name="username" value="root"/><property name="password" value="abc123"/></bean><!--配置JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"/></bean><!--配置事务管理器--><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--配置通知,具体的增强代码。--><!--注意:在通知当中要关联事务管理器。--><tx:advice id="txAdvice" transaction-manager="txManager"><!--配置通知的相关属性--><tx:attributes><!--之前所讲的所有的事务属性都可以在以下标签中配置。--><tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="query*" read-only="true"/><tx:method name="select*" read-only="true"/><tx:method name="find*" read-only="true"/><tx:method name="get*" read-only="true"/></tx:attributes></tx:advice><!--配置切面--><aop:config><!--切点--><aop:pointcut id="txPointcut" expression="execution(* com.bank.service..*(..))"/><!--切面 = 通知 + 切点--><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/></aop:config></beans>

dao

package com.bank.dao;import com.bank.pojo.Account;/*** 专门负责账户信息的CRUD操作。* DAO中只执行SQL语句,没有任何业务逻辑。* 也就是说DAO不和业务挂钩。* @className AccountDao* @since 1.0**/
public interface AccountDao {/*** 根据账号查询账户信息* @param actno* @return*/Account selectByActno(String actno);/*** 更新账户信息。* @param act* @return*/int update(Account act);}
package com.bank.dao.impl;import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @className AccountDaoImpl* @since 1.0**/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {@Resource(name = "jdbcTemplate")private JdbcTemplate jdbcTemplate;@Overridepublic Account selectByActno(String actno) {String sql = "select actno, balance from t_act where actno = ?";Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);return account;}@Overridepublic int update(Account act) {String sql = "update t_act set balance = ? where actno = ?";int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());return count;}}

pojo

package com.bank.pojo;/*** 银行账户类* @className Account* @since 1.0**/
public class Account {private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"actno='" + actno + '\'' +", balance=" + balance +'}';}public Account() {}public Account(String actno, Double balance) {this.actno = actno;this.balance = balance;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}
}

service

package com.bank.service;/*** 业务接口* 事务就是在这个接口下控制的。* @className AccountService* @since 1.0**/
public interface AccountService {/*** 转账业务方法* @param fromActno 从这个账户转出* @param toActno 转入这个账户* @param money 转账金额*/void transfer(String fromActno, String toActno, double money);}
package com.bank.service.impl;import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;/*** @className AccountServiceImpl* @since 1.0**/
@Service("accountService")
public class AccountServiceImpl implements AccountService {@Resource(name = "accountDao")private AccountDao accountDao;// 控制事务,因为在这个方法中要完成所有的转账业务。@Overridepublic void transfer(String fromActno, String toActno, double money) {// 第一步:开启事务// 第二步:执行核心业务逻辑// 查询转出账户的余额是否充足Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new RuntimeException("余额不足!!!");}// 余额充足Account toAct = accountDao.selectByActno(toActno);// 将内存中两个对象的余额先修改。fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 数据库更新int count = accountDao.update(fromAct);// 模拟异常/*String s = null;s.toString();*/count += accountDao.update(toAct);if(count != 2) {throw new RuntimeException("转账失败,联系银行!");}// 第三步:如果执行业务流程过程中,没有异常。提交事务// 第四步:如果执行业务流程过程中,有异常,回滚事务。}}

Test

   @Testpublic void testNoAnnotation(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");AccountService accountService = applicationContext.getBean("accountService", AccountService.class);try {accountService.transfer("act001", "act002", 10000.0);System.out.println("转账成功");} catch (Exception e) {e.printStackTrace();}}

相关文章:

Spring事务详解

一、Spring对事务的支持 1.事务概述 什么是事务 在一个业务流程当中&#xff0c;通常需要多条DML&#xff08;insert delete update&#xff09;语句共同联合才能完成&#xff0c;这多条DML语句必须同时成功&#xff0c;或者同时失败&#xff0c;这样才能保证数据的安全。 多…...

单片机FreeRTOSTickless低功耗模式应用示例

Tickless低功耗模式在很多需要延长电池寿命或减少能耗的场景中非常有用&#xff0c;特别是在那些大部分时间处于空闲状态的系统中。 以下是一些使用Tickless模式的场景和例子&#xff1a; 1.传感器节点在物联网&#xff08;IoT&#xff09;中&#xff0c;许多传感器节点需要长…...

2025.4.9总结

今天周三&#xff0c;晚上默认不加班&#xff0c;每到闲暇的时候&#xff0c;总会瞎想。 如今想想&#xff0c;是要多提升提升自身的软实力了。硬实力&#xff0c;是你的专业技能&#xff0c;是你吃饭的东西&#xff0c;而软实力则体现在人际交往&#xff0c;表达能力等方面。…...

Ceph异地数据同步之-Cephfs异地同步复制

#作者&#xff1a;闫乾苓 文章目录 1.核心原理2.部署步骤3.cephfs同步测试4.查看cephfs文件同步状态5.优化cephfs文件系统同步的时间间隔 1.核心原理 Cephfs异地同步基于CephFS-mirror&#xff0c;其工作原理是基于CephFS的快照功能和cephfs-mirror工具的异步复制机制。它通过…...

大数据专业学习路线

大数据专业学习路线 目录 基础知识核心技术进阶技能实战项目职业发展学习资源学习计划常见问题 1. 基础知识 1.1 编程语言 Python&#xff1a;大数据分析的基础语言 基础语法和数据类型函数和模块面向对象编程文件操作和异常处理常用库&#xff1a;NumPy, Pandas, Matplot…...

每日文献(十)——Part two

今天从第四部分 级联RCNN开始介绍。 目录 四、级联RCNN 4.1 级联边界框回归 4.2 级联检测 五、实验结果 5.1 实现细节 5.1.1 基准工作 5.2 质量不匹配 5.3 与迭代bbox和积分损失的比较 5.4 消融实验 5.5 与最先进的方法对比 5.6 泛化能力 5.7 PASCAL VOC数据集结果…...

8.3.1 MenuStrip(菜单)控件

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的 MenuStrip控件提供了程序窗体的主菜单&#xff0c;即显示于窗体顶端部分的菜单。 MenuStrip常用属性&#xff1a; ImageScalingSize…...

仿真每日一练 | ABAQUS子程序DLOAD

ABAQUS中用户子程序DLOAD可用于定义分布载荷幅值随坐标、时间、单元编号、积分点编号等的变化&#xff0c;该功能主要应用于定义复杂的载荷工况&#xff0c;今天给大家举一个简单的例子介绍其使用方式&#xff1a; 图1 模型认识 回顾一下ABAQUS的有限元分析流程&#xff1a; 图…...

Kubernetes(k8s)-备份Etcd介绍

作者介绍&#xff1a;简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Docker基本情况&#xff0c;目前在规模较大的容器集群基本都是Kubernetes&#xff0c;但是K…...

[leetcode]求最大公约数和最小公倍数(gcd和lcm算法)

求最大公约数和最小公倍数 Coding : 使用C的库 #include<iostream> #include<algorithm> using namespace std; int main() { int a, b; cout << "cin a and b of gcd : "; cin >> a >> b; int res __gcd(a, b);…...

B-tree 的原理源码分析及应用场景等

B-tree&#xff08;B树&#xff09;是一种自平衡的多路搜索树&#xff0c;广泛用于文件系统、数据库索引、键值存储系统等对大规模数据的高效插入、查找和删除有高要求的场景。相比于二叉搜索树&#xff08;BST&#xff09;&#xff0c;B-tree 可以减少磁盘I/O次数&#xff0c;…...

MySQL 中的聚簇索引和非聚簇索引有什么区别?

MySQL 中的聚簇索引和非聚簇索引有什么区别&#xff1f; 1. 从不同存储引擎去考虑 在MySIAM存储引擎中&#xff0c;索引和数据是分开存储的&#xff0c;包括主键索引在内的所有索引都是“非聚簇”的&#xff0c;每个索引的叶子节点存储的是数据记录的物理地址&#xff08;指针…...

重构居家养老安全网:从 “被动响应” 到 “主动守护”

随着全球老龄化加剧&#xff0c;居家养老安全成为社会关注的核心议题。 传统养老模式依赖人工巡检或单一传感器&#xff0c;存在响应滞后、隐私泄露、场景覆盖不足等问题。 由此智绅科技应运而生&#xff0c;七彩喜智慧养老系统构筑居家养老安全网。 而物联网&#xff08;Io…...

从静态绑定驱动模型到现代设备模型 —— 一次驱动架构的进化之旅

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 从静态绑定驱动模型到现代设备模型 在 Linux 内核的发展历程中&#xff0c;设备驱动结构经历了从"硬编码 手动注册"的早期实现方式&#xff0c;到"设备模型统一管理&qu…...

MySQL学习笔记十五

第十七章组合查询 17.1组合查询 MySQL允许执行多个查询&#xff08;多条SELECT语句&#xff09;&#xff0c;并将结果作为单个查询结果集返回。这些组合查询通常称为并&#xff08;union&#xff09;或复合查询&#xff08;compound query&#xff09;。 以下几种情况需要使…...

NLP基础知识 与 词向量的转化方法 发展

目录 1.NLP 基础知识点 为什么需要自然语言处理? 自然语言处理有哪些分类? 自然语言处理有哪些实际应用? 为什么需要自然语言处理? 自然语言处理有哪些分类? 自然语言处理有哪些实际应用? 自然语言处理的技术/工作原理是什么? 2.NLP文本转化为词向量的方法 2…...

VectorBT量化入门系列:第四章 高级策略开发与优化

VectorBT量化入门系列&#xff1a;第四章 高级策略开发与优化 本教程专为中高级开发者设计&#xff0c;系统讲解VectorBT技术在量化交易中的应用。通过结合Tushare数据源和TA-Lib技术指标&#xff0c;深度探索策略开发、回测优化与风险评估的核心方法。从数据获取到策略部署&am…...

JVM虚拟机篇(七):JVM垃圾回收器全面解析与G1深度探秘及四种引用详解

JVM垃圾回收器全面解析与G1深度探秘及四种引用详解 JVM虚拟机&#xff08;七&#xff09;&#xff1a;JVM垃圾回收器全面解析与G1深度探秘及四种引用详解一、JVM有哪些垃圾回收器1. Serial回收器2. ParNew回收器3. Parallel Scavenge回收器4. Serial Old回收器5. Parallel Old回…...

【蓝桥杯】15届JAVA研究生组F回文字符串

一、思路 1.这题去年考的时候想的是使用全排列进行尝试&#xff0c;实际不用这么麻烦&#xff0c;只用找到第一个和最后一个非特殊字符串的位置&#xff0c;然后分别向内检查是否对称&#xff0c;向外检查是否对称直到左指针小于0(可以通过添加使其对称) 2.至于如何找到第一个…...

TDengine 语言连接器(Python )

简介 taospy 是 TDengine 数据库面向 Python 语言提供的官方连接器&#xff0c;连接器对外提供对数据库写入、查询、订阅等多种访问接口。 安装连接器命令如下&#xff1a; # 原生连接和 REST 连接 pip3 install taospy# WebSocket 连接&#xff0c;可选装 pip3 install tao…...

Android compose源码浅析——Modifier

Modifier浅析 Modifier的使用foldOutfoldInanyall总结Modifier的使用 先来一段代码1: @Preview(showBackground = true) @Composable fun GreetingPreview() {ComposeTestTheme {Box(modifier = Modifier.size(DpSize(Dp(100f),Dp(100f))).padding(Dp(10f)).background(Colo…...

基于机器视觉的多孔零件边缘缺陷检测(源码C++、opencv、凸包、凸缺陷检测)

&#x1f451;主页&#xff1a;吾名招财 &#x1f453;简介&#xff1a;工科学硕&#xff0c;研究方向机器视觉&#xff0c;爱好较广泛… ​&#x1f4ab;签名&#xff1a;面朝大海&#xff0c;春暖花开&#xff01; 基于机器视觉的多孔零件边缘缺陷检测&#xff08;源码C、ope…...

JAVAWeb_Servlet:前置准备与理论简易介绍

要写JAVA_Web&#xff1a;首先就得建个项目——如何在Eclipse新建一个Web项目-CSDN博客 然后我们考虑具体的代码细节&#xff08;接下来就是我们的前置准备&#xff09; 一、导包&#xff1a; 在 Eclipse 中&#xff0c;如果需要快速导入缺失的包&#xff08;例如&#xff0…...

反射 tcp

反射 临时越过权限 获取成员变量1并进行修改 成员方法 TCP客户端...

UML综合实验四

1. 计算机包含内存(RAM)、CPU等硬件设备&#xff0c;根据下面的“产品等级结构-产品族”示意图&#xff0c;使用抽象工厂模式实现计算机设备创建过程并绘制相应的类图。 2. 电脑组装工厂可以将CPU、内存、硬盘、主机、显示器等硬件设备组装在一起构成一台完整的电脑&#xff0c…...

<《AI大模型应知应会100篇》第8篇:大模型的知识获取方式及其局限性

第8篇&#xff1a;大模型的知识获取方式及其局限性 摘要 大模型&#xff08;如GPT、BERT、Qwen、DeepSeek等&#xff09;凭借其卓越的自然语言处理能力&#xff0c;已经成为人工智能领域的明星。然而&#xff0c;这些模型“知道”什么&#xff1f;它们如何获取知识&#xff1f…...

【回眸】Linux 内核 (十六) 之 多线程编程 下

前言 前面介绍了互斥锁&#xff0c;本篇博文介绍死锁及其他多线程遇到的情况。 什么情况会造成死锁 死锁指的是两个或两个以上的运算单元&#xff08;进程、线程或协程&#xff09;&#xff0c;互相持有对方所需的资源&#xff0c;导致它们都无法向前推进&#xff0c;从而导…...

学习笔记083——Java Stream API

文章目录 1、过滤数据 filter()2、转换元素 map()3、排序 sorted()3.1、自定义排序规则 4、去重 distinct()5、限制元素数量 limit()6、收集结果 collect()6.1、收集为List6.2、收集为Set6.3、转为Map6.4、基本用法&#xff08;注意键冲突会抛异常&#xff09;6.5、处理键冲突&…...

逍遥模拟器ARM过检测技术全解析

逍遥模拟器ARM框架安装magisk和修改设备型号隐藏应用隐藏root过检测 逍遥模拟器ARMmagisk改设备型号隐藏应用隐藏root 引言 逍遥模拟器以其出色的性能和丰富的功能&#xff0c;深受广大用户喜爱&#xff0c;让用户能在电脑上轻松运行各类安卓应用和游戏。然而&#xff0c;为保…...

Easysearch VS Opensearch 数据写入与存储性能对比

本文记录 Easysearch 和 Opensearch 数据写入和数据存储方面的性能对比。 准备 压测工具&#xff1a;INFINI Loadgen 对比版本&#xff1a; Easysearch 1.11.1&#xff08;lucene 8.11.4&#xff09;Opensearch 2.19.1&#xff08;lucene 9.12.1&#xff09; 节点 JVM 配置…...

C++中STL学习(一)——向量、栈、堆、集合

#include “bits/stdc.h” using namespace std; int main() { // -------------------- 1、向量vector&#xff1a;可以替换数组&#xff0c;不需要事先指定长度 // ------------------------- vector arr; // 构建int数组 vector arr1(100); // 构建初始长度100的int向量 ve…...

柑橘病虫害图像分类数据集OrangeFruitDataset-8600

文章目录 1. 前言2. 数据类别介绍3. 数据集地址 1. 前言 柑橘&#xff0c;作为水果界的 “宠儿”&#xff0c;不仅以其酸甜可口的味道深受大众喜爱&#xff0c;更是在全球水果产业中占据着举足轻重的地位。无论是早餐中的一杯橙汁&#xff0c;还是下午茶里的柑橘甜点&#xff…...

leetcode刷题-单调栈

代码随想录单调栈|739. 每日温度、496.下一个更大元素 I、503.下一个更大元素II、42. 接雨水、84.柱状图中最大的矩形 739. 每日温度496.下一个更大元素 I503.下一个更大元素II42. 接雨水 -- 面试常考题84.柱状图中最大的矩形 739. 每日温度 leetcode题目链接 代码随想录文档讲…...

【设计模式】访问者模式

**简介 假设你有一个购物车&#xff08;对象结构&#xff09;&#xff0c;里面有多种商品&#xff08;元素&#xff09;&#xff0c;如苹果、牛奶、书籍。每个商品的计价规则不同&#xff1a; 水果按重量计价牛奶按数量计价书籍按固定价格计价 现在需要实现两种功能&#xff1…...

【ISP】ISP pipeline(AI)

ISP Pipeline 全流程概览 ISP&#xff08;Image Signal Processing&#xff0c;图像信号处理&#xff09;流程通常从原始 Bayer 数据出发&#xff0c;经过一系列模块处理&#xff0c;逐步完成图像校正和增强&#xff0c;最终生成用于显示或编码的标准图像。常见处理模块包括&a…...

【设计模式】模板模式

简介 假设你要冲泡咖啡和茶&#xff0c;两者的流程相似但部分步骤不同&#xff1a; 烧水&#xff08;公共步骤&#xff09;加入主材料&#xff08;咖啡粉/茶叶&#xff09;添加调料&#xff08;糖/牛奶&#xff09;→ 可选步骤倒进杯子&#xff08;公共步骤&#xff09; 模板…...

GDB调试程序的基本命令和用法(Qt程序为例)

1. 引言 GDB&#xff08;GNU Debugger&#xff09;是一个强大的命令行调试工具&#xff0c;它可以帮助开发者在程序运行时查找和修复错误。当调试Qt程序时&#xff0c;GDB同样适用&#xff0c;并且能够帮助开发者定位诸如数组越界挂死等复杂问题。 2. 基本命令 2.1 启动GDB …...

vue3腾讯云直播 前端推流

1、在index.html文件中引入&#xff08;在body体中&#xff09; <script src"https://video.sdk.qcloudecdn.com/web/TXLivePusher-2.1.1.min.js" charset"utf-8"></script> 2、vue文件中&#xff0c;添加video推流&#xff08;我用的推流地…...

DP_AUX辅助通道介绍

DisplayPort&#xff08;简称DP&#xff09;是一个由PC及芯片制造商联盟开发&#xff0c;视频电子标准协会&#xff08;VESA&#xff09;标准化的数字式视频接口标准。该接口免认证、免授权金&#xff0c;主要用于视频源与显示器等设备的连接&#xff0c;并也支持携带音频、USB…...

【微机及接口技术】- 第九章 串行通信与串行接口(下)

文章目录 第二节 串行通信协议一、异步串行通信协议二、同步串行通信协议 第三节 串行接口标准RS-232C一、RS-232C信号线定义二、电气特性 第四节 可编程串行接口芯片8251A一、基本性能二、内部结构三、外部引脚功能1. 同CPU的连接信号2. MODEM控制信号&#xff08;4个&#xf…...

人形机器人制造—3D打印推动微型化与轻量化设计

在人形机器人仿生架构的构建中&#xff0c;多模态传感器集群与仿生关节矩阵的拓扑融合&#xff0c;正催生第三代具身智能的力学革命。通过分布式触觉薄膜、双目视觉惯性测量单元&#xff08;200Hz采样率&#xff09;与肌电模拟传感器的三重耦合&#xff0c;机器人获得了超越人类…...

前端性能优化高频面试题解析与实战指南(2025版)

一、前端性能优化核心面试题汇总 1. 浏览器加载优化相关问题 Q1&#xff1a;浏览器从输入URL到页面渲染的完整流程中&#xff0c;有哪些关键性能节点&#xff1f; 核心流程&#xff1a;DNS解析 → TCP连接&#xff08;TLS握手&#xff09;→ HTTP请求 → 资源下载 → 解析HT…...

【教程】xrdp修改远程桌面环境为xfce4

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 xfce4 vs GNOME对比 配置教程 1. 安装 xfce4 桌面环境 2. 安装 xrdp 3. 配置 xrdp 使用 xfce4 4. 重启 xrdp 服务 5. 配置防火墙&#xff…...

递增子序列

递增子序列 难点&#xff1a; 结果集如何加&#xff1a;每次进入递归都判断是否sub中的个数>2&#xff1b;不允许对数组排序&#xff0c;如何在每层去重&#xff1a;不可以再用nums[i] nums[i-1]&#xff08;没有意义&#xff0c;重复的元素不一定挨着&#xff09;&#x…...

Linux磁盘管理双雄:lsblk与df深度解析

在Linux系统管理的日常工作里&#xff0c;磁盘管理占据着极为重要的地位&#xff0c;这里重点介绍lsblk和df这两个命令。 一、lsblk命令&#xff1a;呈现磁盘物理架构 lsblk是用于罗列块设备信息的实用命令&#xff0c;它以直观的树状结构呈现系统中的块设备&#xff0c;帮助…...

C#里设计Modbus-RTU(Remote Terminal Unit)协议

Modbus-RTU(Remote Terminal Unit)是一种串行通信协议,广泛用于工业自动化领域,支持主从式(Master-Slave)通信架构。它是Modbus协议的两种传输模式之一(另一种是ASCII模式),具有高效、简洁、可靠性强的特点,常用于RS-485或RS-232物理层通信。 核心特性 物理层 通常基…...

spark学习内容总结

Spark运行架构总结 一、核心结构 Spark框架的核心是一个计算引擎&#xff0c;整体采用标准的master-slave结构。其中&#xff0c;Driver作为master&#xff0c;负责管理整个集群中的作业任务调度&#xff1b;Executor作为slave&#xff0c;负责实际执行任务。 二、核心组件 ‌…...

MySQL多表查询、事务与索引的实践与应用

摘要&#xff1a;本文围绕MySQL数据库操作展开&#xff0c;通过构建部门与员工管理、餐饮业务相关的数据库表&#xff0c;并填充测试数据&#xff0c;系统地阐述了多表查询的多种方式&#xff0c;包括内连接、外连接和不同类型的子查询&#xff0c;同时介绍了事务的处理以及索引…...

MySQL【8.0.41版】安装详细教程--无需手动配置环境

一、MySQL 介绍 1. 概述 MySQL 是一个开源的关系型数据库管理系统&#xff0c;由瑞典公司 MySQL AB 开发&#xff0c;现属于 Oracle 旗下。它基于 SQL&#xff08;结构化查询语言&#xff09;进行数据管理&#xff0c;支持多用户、多线程操作&#xff0c;广泛应用于 Web 应用、…...

FRP练手:hello,world实现

方案一&#xff1a;使用 Flask&#xff08;推荐&#xff09; from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return "你好啊世界"if __name__ __main__:# 监听所有网络接口&#xff08;0.0.0.0&#xff09;&#xff0c;端口 3344app.…...