【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用

【JavaEE】Spring 事务(1)

在这里插入图片描述

文章目录

  • 【JavaEE】Spring 事务(1)
    • 1. 为什么要使用事务
    • 2. Spring中事务的实现
      • 2.1 事务针对哪些操作
      • 2.2 MySQL 事务使用
      • 2.3 Spring 编程式事务(手动挡)
      • 2.4 Spring 声明式事务(自动挡)
      • 2.5 小疑问(@Transactional注解原理)
        • 2.5.1 @Transactional注解原理
        • 2.5.2 为什么必须被五大类注解修饰
        • 2.5.3 为什么@Transactional不支持static方法
    • 3. 实践
      • 3.1 创建项目
      • 3.2 编写代码
      • 3.3 测试
      • 3.4 注意事项
        • 3.4.1 事务不会自动回滚解决方案1:重新抛出
        • 3.4.2 事务不会自动回滚解决方案2:手动回滚

【JavaEE】Spring 事务(1)

1. 为什么要使用事务

比如跟钱相关的两个操作:

第一步操作:小马卡里 - 100元

第二步操作:老马卡里 + 100元

这就是一个事务,捆在一起的一组行为,就是事务

白色背景中捆在一起的两个蜡烛图片下载 - 觅知网

而它能保证的是,这个行为的原子性,一致性,隔离性,持久性:

  1. 两个操作都成功
  2. 两个操作都失败

要么一起成功,要么一起失败

但是,如果没有事务呢,则两个操作逻辑上是分开的:

  • 第一个操作成功,第二个操作失败,则小马直接亏了100!

2. Spring中事务的实现

Spring中的事务操作主要分为两类:

  1. 编程式事务(原生方式去写代码操作事务)
  2. 声明式事务(利用注解,“约定规则”去自动开启和提交事务)

2.1 事务针对哪些操作

事务一般针对的是

  1. 持久化相关的操作,如数据库操作、文件系统操作等

正如刚才的那样,两个用户的转账操作

  1. 保证数据完整性的操作,如消息队列等

通过使用事务,可以在消息队列中提供可靠的消息传递机制,减少消息丢失或重复处理的可能性,同时确保系统在出现故障情况下能够正确恢复

事务的概念适用于需要保证一系列操作的原子性和一致性的任何场景

  • 而其中,被持久化的数据,被传播的数据…等操作,都具有 “持久性影响” 的作用,所以要通过事务来控制其影响不要太糟糕
  • 而一些操作,比如打印,都打印到控制台了,不会回滚的,也没有必要回滚,例如查看执行日志…
    • 至于其他的不可见的操作,又没有持久化,是没有影响力的,程序出异常后,这些数据也销毁了~

2.2 MySQL 事务使用

--- 开启事务
start transaction;
--- transaction就是事务的意思


--- 提交事务
commit;


--- 回滚事务
rollback;

三个重要的操作:

  1. 开启事务
  2. 提交事务
  3. 回滚事务

2.3 Spring 编程式事务(手动挡)

与MySQL操作事务类似:

  1. 开启事务(获取一个事务/创建一个事务并获取)
  2. 提交事务
  3. 回滚事务

SpringBoot 内置了两个对象:

  1. DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或 回滚事务的
  2. TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus

实现代码如下:

@RestController
public class UserController {
    @Resource
    private UserService userService;
    // JDBC 事务管理器
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;
    // ------------定义事务属性------------
    @Resource
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/sava")
    public Object save(User user) {
        // ------------开启事务------------
        TransactionStatus transactionStatus = dataSourceTransactionManager
            .getTransaction(transactionDefinition);
        // ------------插⼊数据库------------
        int result = userService.save(user);
        // ------------提交事务------------
        dataSourceTransactionManager.commit(transactionStatus);
        // // ------------回滚事务------------
        // dataSourceTransactionManager.rollback(transactionStatus);
        return result;
    }
}

反正就是,麻烦,难记,不简洁,容易出错(难写)

2.4 Spring 声明式事务(自动挡)

声明式事务的实现很简单

  • 只需要在需要的类或者方法上添加 @Transactional 注解 就可以实现了

无需手动开启/提交/回滚事务:

  1. 进入方法,自动开启事务
  2. 方法执行完会,自动提交事务
  3. 如果中途发生了没有处理的异常,自动回滚事务

具体规则/作用范围是:

  • 加在类上,内部的所有非静态public方法都相当于加了 @Transactional 注解
  • 加在非静态public方法上,这个方法就是一个事务
  • 所在的类,必须被五大类注解修饰,这跟其事务的实现有关
    • 而且有了五大类注解,Spring开发才能进行呀~

代码实现:

@Service
@Transactional
public class Test {
    @Autowired
    private Mapper mapper;
    
    public int save(User user) {
        mapper.save(user);
    }
}

@RequestMapping("/save")
@Transactional
public Object save(User user) {
 int result = userService.save(user);
 return result;
}

跟往常的注解版不使用注解版的代码一样:

  1. 不使用注解版: 灵活,能实现很多功能,但是麻烦,使用困难,甚至正常人压根没法写,例如事务传播机制的代码实现起来就比较复杂
  2. 使用注解版: 使用规则约束,实现特定功能,但是方便,使用简单,且足以面对正常开发环境,不关心一些极端的不正常开发
    • 对于注解的使用,就是:遵循约定,坐享其成,明白逻辑(作用),合理使用(逻辑分析合理)

编程式就相当于车的手动挡,声明式就相当于车的自动挡,那么现实咱们买不起偏贵的自动挡车,而我们现在可以无条件舒适地使用自动挡,那咋不用嘞🤣🤣🤣

2.5 小疑问(@Transactional注解原理)

2.5.1 @Transactional注解原理

在这里插入图片描述

  • 这个行为,可能你也意识到了,其实就是AOP,对@Transactional注解下的代码,进行统一的处理
    • 当然,对于不同的事务/复杂事务,处理可能不同~
  • 这个在执行日志中也能看到,可以平时观察观察~

@Transactional 实现思路图:

在这里插入图片描述

@Transactionl执行思路图:

在这里插入图片描述

默认就是这么一个事务管理器执行这样的逻辑

  • 而如果配置了多个事务管理器,则需要通过参数value/transactionManager去指定

2.5.2 为什么必须被五大类注解修饰

其实就是因为

@Transactional注解是基于Spring AOP的,而Spring AOP则通过JDK的或者CGLib的动态代理来实现AOP

对于使用@Transactional注解来实现事务管理,确实是通过动态代理来实现的

  • 当你在一个类或方法上添加了@Transactional注解时,Spring会通过动态代理在运行时为该类或方法创建一个代理对象。这个代理对象会拦截调用,并在适当的时机开启、提交或回滚事务

由于动态代理的实现方式,确实需要满足一些条件才能使@Transactional注解生效

  • 具体来说,被注解的类或方法必须是Spring容器中的bean,而Spring容器会自动为标注了@Service@Controller@Repository@Component@Configuration等注解的类创建bean实例。这也是为什么我之前提到了五大类注解

2.5.3 为什么@Transactional不支持static方法

其实就是因为

无论JDK还是CGlib都无法对静态方法提供代理。原因在于静态方法是类级别的,调用需要知道类信息,而类信息在编译器就已经知道了,并 不支持在运行期的动态绑定

3. 实践

3.1 创建项目

为了方便,我就直接使用之前mybatis项目里写过的代码了

  • 因为我们目前侧重学习的点是在事务的实现!

model.UserInfo:

@Component
@Data
public class UserInfo {
    private int id;

    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;


    public UserInfo(String username, String password, Integer state) {
        this.username = username;
        this.password = password;
        this.state = state;
    }
    public UserInfo() {

    }
}

mapper.UserMapper:

@Mapper
//跟五大类注解@Repository,say 拜拜
public interface UserMapper {
    List<UserInfo> getAll(); //获得所有用户信息

    UserInfo getUserById(Integer id);
    //通过id查找用户

    UserInfo getUserByUsername(@Param("username") String username);
    //通过username查找用户

    List<UserInfo> getAll2(@Param("option") String option);

    UserInfo login(@Param("username") String username, @Param("password") String password);


    int update(UserInfo userInfo);

    //删除状态为state的用户
    int delete(@Param("state") Integer state);

    //增加用户
    int insert(UserInfo userInfo);

    List<UserInfo> getAllLikeSome(@Param("likeString") String likeString);


    //用户注册提交信息
//    int add(UserInfo userInfo);
    int add(String username, String password, Integer state, Integer id);

    int add2(UserInfo userInfo);

    List<UserInfo> select1(UserInfo userInfo);

    int update2(UserInfo userInfo);

    int deleteByIDs(List<Integer> list);

    int insertByUsers(List<UserInfo> list, List<UserInfo> list2);

}

mybatis.UserInfoMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">


    <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
    </resultMap>
    <select id="getAll" resultMap="BaseMap">
        select * from userinfo
    </select>

<!--    <select id="getAll" resultType="com.example.demo.model.UserInfo">-->
<!--        select id, username as name, password, photo,-->
<!--            createtime, updatetime, state from userinfo-->
<!--    </select>-->
    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where id = #{id}
    </select>
    <select id="getUserByUsername" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username = ${username}
    </select>

    <select id="getAll2" resultType="com.example.demo.model.UserInfo">
        select * from userinfo order by id ${option}
    </select>

    <select id="login" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username = '${username}'
        and password = '${password}'
    </select>

    <update id="update">
        update userinfo set state = #{state} where username = #{username}
    </update>

    <delete id="delete">
        delete from userinfo where state = #{state}
    </delete>

    <insert id="insert" useGeneratedKeys="true"
            keyColumn="id" keyProperty="id">
<!--    自增主键 id 不能为null也没有默认值,如果id不设置或者设置为null,都会导致自增    -->
        insert into userinfo (username, password) values (#{username}, #{password});
    </insert>

    <select id="getAllLikeSome" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username like concat('%', #{likeString}, '%')
    </select>

    <insert id="add">
        insert into userinfo (
            <if test="id != 0">
                id,
            </if>
            <if test="username != null">
                username,
            </if>
            <if test="password != null">
                password,
            </if>
            <if test="state != null">
                state
            </if>
        ) values (
            <if test="id != 0">
                #{id},
            </if>
            <if test="username != null">
                #{username},
            </if>
            <if test="password != null">
                #{password},
            </if>
            <if test="state != null">
                #{state}
            </if>
        )
    </insert>

    <insert id="add2">
        insert into userinfo
        <trim prefix="(" suffix=")"
            suffixOverrides=",">
            <if test="id != 0">
                id,
            </if>
            <if test="username != null">
                username,
            </if>
            <if test="password != null">
                password,
            </if>
            <if test="state != null">
                state
            </if>
        </trim>
         values
        <trim prefix="(" suffix=")"
              suffixOverrides=",">
            <if test="id != 0">
                #{id},
            </if>
            <if test="username != null">
                #{username},
            </if>
            <if test="password != null">
                #{password},
            </if>
            <if test="state != null">
                #{state}
            </if>
        </trim>
    </insert>

    <select id="select1" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
        <where>
            <if test="id != 0">
                id = #{id}
            </if>
            <if test="username != null">
                or username = #{username}
            </if>
            <if test="password != null">
                or password = #{password}
            </if>
            <if test="state != null">
                or state = #{state}
            </if>
        </where>
<!--        <trim prefix="where" prefixOverrides="and">-->
<!--            <trim prefixOverrides="or">-->
<!--                <if test="id != 0">-->
<!--                    id = #{id}-->
<!--                </if>-->
<!--                <if test="username != null">-->
<!--                    or username = #{username}-->
<!--                </if>-->
<!--                <if test="password != null">-->
<!--                    or password = #{password}-->
<!--                </if>-->
<!--                <if test="state != null">-->
<!--                    or state = #{state}-->
<!--                </if>-->
<!--            </trim>-->
<!--        </trim>-->


    </select>

    <update id="update2">
        update userinfo
        <set>
            <if test="username != null">
                username = #{username},
            </if>
            <if test="password != null">
                password = #{password},
            </if>
            <if test="state != null">
                state = #{state}
            </if>
        </set>
        where id = #{id}
    </update>

    <delete id="deleteByIDs">
        delete from userinfo where id in
        <foreach collection="list" open="(" close=")" item="x" separator=",">
            #{x}
        </foreach>
    </delete>

    <insert id="insertByUsers">
        insert into userinfo(username, password, state) values
        <foreach collection="list" item="x" open="(" close=")" separator="),(">
            #{x.username}, #{x.password}, #{x.state}
        </foreach>
        ,
        <foreach collection="list2" item="x" open="(" close=")" separator="),(">
            #{x.username}, #{x.password}, #{x.state}
        </foreach>
    </insert>
</mapper>

application.properties:

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?characterEncoding=utf8
# MyBatis 基于jdbc实现~ 底层用的就是jdbc:mysql协议,这个地址是本地数据库的地址,test_db就是我们的那个数据库
spring.datasource.username=root
# 用户名,默认固定是root
spring.datasource.password=mmsszsd666
# 密码,是数据库的密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis配置信息
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml

#  执行时打印SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#由于其默认情况下的日志类型为Debug,重要程度不高,所以我们需要设置我们对应的目录下的日志级别
logging.level.com.example.demo.controller=debug

#将数据库中的下换线转换成驼峰,比如 user_name -> userName
mybatis-plus.configuration.map-underscore-to-camel-case=true

目录结构:

在这里插入图片描述

3.2 编写代码

同样的controller接受请求,service调用方法~

在这里插入图片描述

在这里插入图片描述

加@Transactional:

在这里插入图片描述

3.3 测试

在这里插入图片描述

访问路由前:

delete from userinfo;

现在userinfo一条数据都没有了~

效果:

浏览器:

在这里插入图片描述

控制台:

在这里插入图片描述

数据库:

在这里插入图片描述

  • 符合预期:还是空的
    • 因为发生了因为@Transactional捕获到了异常,发生回滚

在这里插入图片描述

去这段代码后,效果:

在这里插入图片描述

在这里插入图片描述

3.4 注意事项

@Transactional 在异常被 try{}catch(){} 捕获的情况下,不会进行事务自动回滚,这也很好理解,因为 try{}catch(){} 后,后面的代码可以继续运行,这个异常是被我们写的 try{}catch(){} 抢走处理了,注解是捕获不到的~

代码:

在这里插入图片描述

效果:

浏览器:

在这里插入图片描述

控制台:

在这里插入图片描述

数据库:

在这里插入图片描述

说明没有回滚

3.4.1 事务不会自动回滚解决方案1:重新抛出

在这里插入图片描述

效果:

在这里插入图片描述

在这里插入图片描述

无新增数据,代表回滚成功

但是这不太美观,“优雅”,过于暴力

3.4.2 事务不会自动回滚解决方案2:手动回滚

TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法setRollbackOnly就可以实现将当前事务的回滚了

  • 跟切面有关=>aop

在这里插入图片描述

效果:

在这里插入图片描述

在这里插入图片描述

无新增数据,代表回滚成功

这种方式就比较“优雅”了~


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

代码:事务/src/main · 游离态/马拉圈2023年8月 - 码云 - 开源中国 (gitee.com)


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/99811.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

视频汇聚/视频云存储/视频监控管理平台EasyCVR接入海康SDK协议后无法播放该如何解决?

开源EasyDarwin视频监控/安防监控/视频汇聚EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…

java八股文面试[多线程]——线程池拒绝策略

四种线程池拒绝策略&#xff08;handler&#xff09; 当线程池的线程数达到最大线程数时&#xff0c;需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口&#xff0c;并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过…

矢量图片转换 Vector Magic for mac

Vector Magic会帮你进行自动识别和分析&#xff0c;转换过程中用户可选择相应的转换级别&#xff0c;从而达到自已所需的效果。 只需上传即可在线自动将 JPG、PNG、BMP 和 GIF 位图图像转换为真正的 SVG、Eps 和 PDF 矢量图像。真正的全彩描摹&#xff0c;无需安装软件&#xf…

智慧工厂解决方案:推动制造业转型升级的新引擎

随着信息技术的迅猛发展和制造业竞争的加剧&#xff0c;智慧工厂成为了推动制造业转型升级的重要引擎。智慧工厂解决方案通过整合物联网、人工智能、大数据分析等先进技术&#xff0c;实现生产过程的智能化、自动化和高效化&#xff0c;为企业提供了更加灵活、智能的生产模式和…

Linux之web服务器

目录 www简介 常见Web服务程序介绍 服务器主机 主要数据 浏览器 网址及HTTP简介 URL http请求方法 状态码 MIME&#xff08;Multipurpose Internet Mail Extension&#xff09; www服务器的类型 静态网站 动态网站 Apache服务的搭建 Apache的安装 准备工作 htt…

Android RecyclerView 之 列表宫格布局的切换

前言 RecyclerView 的使用我就不再多说&#xff0c;接下来的几篇文章主要说一下 RecyclerView 的实用小功能&#xff0c;包括 列表宫格的切换&#xff0c;吸顶效果&#xff0c;多布局效果等&#xff0c;今天这篇文章就来实现一下列表宫格的切换&#xff0c;效果如下 一、数据来…

大数据之Maven

一、Maven的作用 作用一&#xff1a;下载对应的jar包 避免jar包重复下载配置&#xff0c;保证多个工程共用一份jar包。Maven有一个本地仓库&#xff0c;可以通过pom.xml文件来记录jar所在的位置。Maven会自动从远程仓库下载jar包&#xff0c;并且会下载所依赖的其他jar包&…

uniapp项目实践总结(六)自定义顶部导航栏

本篇主要讲述如何自定义顶部导航栏,有时候默认导航栏不足以满足我们的需求,这时候就需要自定义导航栏来解决这个问题。 目录 默认导航修改配置自定义顶部默认导航 自带的默认顶部导航设置的内容有限,不容易扩展修改,因此如果有更加个性化的需求,则需要自定义顶部导航。 …

QT基础使用:组件和代码关联(信号和槽)

自动关联 ui文件在设计环境下&#xff0c;能看到的组件可以使用鼠标右键选择“转到槽”就是开始组件和动作关联。 在自动关联这个过程中软件自动动作的部分 需要对前面头文件进行保存&#xff0c;才能使得声明的函数能够使用。为了方便&#xff0c;自动关联时先对所有文件…

Windows如何部署Redis

一、简介 Redis (Remote Dictionary Server) 是一个由意大利人 Salvatore Sanfilippo 开发的 key-value 存储系统&#xff0c;具有极高的读写性能&#xff0c;读的速度可达 110000 次/s&#xff0c;写的速度可达 81000 次/s 。 二、下载 访问 https://github.com/tporadows…

IDEA集成Git相关操作知识(pull、push、clone)

一&#xff1a;集成git 1&#xff1a;初始化git&#xff08;新版本默认初始化&#xff09; 老版本若没有&#xff0c;点击VCS&#xff0c;选中import into Version Controller中的Create git Repository(创建git仓库)&#xff0c;同理即可出现git符号。 也可查看源文件夹有没有…

Apifox-比postman更优秀的接口自动化测试平台

一、Apifox介绍 Apifox 是 API 文档、API 调试、API Mock、API 自动化测试一体化协作平台&#xff0c;定位 Postman Swagger Mock JMeter。通过一套系统、一份数据&#xff0c;解决多个系统之间的数据同步问题。只要定义好 API 文档&#xff0c;API 调试、API 数据 Mock、AP…

【Cortex-M3权威指南】学习笔记4 - 异常

目录 实现 CM3流水线CM3 详细框图CM3 总线接口总线连接模板 异常异常类型优先级定义优先级组 向量表中断输入于挂起NMI中断挂起 Fault 类异常总线 faults存储器管理 faults用法 faults SVC 与 PendSV 实现 CM3 流水线 CM3 处理器使用 3 级流水线&#xff0c;分别是&#xff1a;…

ERROR(IMPSP-365) innovus加endcap失败问题解析

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 ERROR(IMPSP-365)&#xff1a;Design has inst with SITE (xx_site)&#xff0c;but the floorplan has no rows defined for this site.Any location for such instance will …

jsch网页版ssh

使用依赖 implementation com.jcraft:jsch:0.1.55Server端代码 import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.TimeUnit; import o…

【状态估计】基于UKF法、AUKF法、EUKF法电力系统三相状态估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

在k8s中用label控制Pod部署到指定的node上

案例-标注k8s-node1是配置了SSD的节点 kubectl label node k8s-node1 disktypessd 查看标记 测试 将pod部署到disktypessd的节点上&#xff08;这里设置了k8s-node1为ssd&#xff09; 部署后查看结果-副本全都运行在了k8s-node1上—符合预期 删除标记 kubectl label node k8…

Linux 进程基础概念-进程状态、进程构成、进程控制

Linux 进程 参考&#xff1a; 「linux操作系统」进程的切换与控制到底有啥关系&#xff1f; - 知乎 (zhihu.com)&#xff0c;Linux进程解析_deep_explore的博客-CSDN博客&#xff0c;腾讯面试&#xff1a;进程的那些数据结构 - 知乎 (zhihu.com)&#xff0c;如何在Linux下的进…

算法通过村第四关-栈黄金笔记|表达式问题

文章目录 前言1. 计算器问题2. 逆波兰表达式问题 总结 前言 提示&#xff1a;快乐的人没有过去&#xff0c;不快乐的人除了过去一无所有。 --理查德弗兰纳根《深入北方的小路》 栈的进阶来了&#xff0c;还记得栈的使用场景吗&#xff1f;表达式和符号&#xff0c;这不就来了 1…

【LeetCode-中等题】437. 路径总和 III

文章目录 题目方法一&#xff1a;迭代层序 每层节点dfs 维护一个count变量 题目 方法一&#xff1a;迭代层序 每层节点dfs 维护一个count变量 思路&#xff1a; 层序遍历每一个节点遍历一个节点就对这个节点进行dfsdfs的同时&#xff0c;维护一个count变量&#xff0c;并且…