后端:事务

文章目录

    • 1. 事务
    • 2. Spring 单独配置DataSource
    • 3. 利用JdbcTemplate操作数据库
    • 4. 利用JdbcTemplate查询数据
    • 5. Spring 声明式事务
    • 6. 事务的隔离级别
      • 6.1 脏读
      • 6.2 不可重复读
      • 6.3 幻读
      • 6.4 不可重复读和幻读的区别
      • 6.5 三种方案的比较
    • 7. 事务的传播特性
    • 8. 设置事务 只读(readOnly)
    • 9. 超时属性(timeout)
    • 10. 异常属性
    • 11. 事务失效原因

1. 事务

一组关联的数据操作;要么全部成功,要么全部失败。
事务的四大特性:原子性、一致性、隔离性、持久性,即ACID。

  • 原子性:指一组业务操作下,要么全部成功,要么全部失败;
  • 一致性:在业务操作下,前后的数据保持一致;
  • 隔离性:并发操作下,事务之间要相互隔离;
  • 持久性:数据一旦保存,那么就是持久存在的。

2. Spring 单独配置DataSource

在Spring Boot项目中不需要单独配置DataSource,只需要在配置文件添加对应的数据库连接配置即可,在Spring项目中需要单独配置。
已经在application.properties文件做了相关数据库连接配置,为了在单元测试类中避免Spring Boot自动配置DataSource的影响,在单元测试类下新建spring文件夹,在这个文件夹下面新建相关类,用以演示Spring 单独配置DataSource,如下:
在这里插入图片描述
只要不要和启动类同在一个目录下,Spring Boot项目中DataSource就不会自动配置的,配置文件中的配置如下:

# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/mytest1
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

定义DataSource这个Bean的配置类代码如下:

package com.example.spring_database_1.spring;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class MyConfiguration {

    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String usr;
    @Value("${spring.datasource.password}")
    private String pwd;
    @Value("${spring.datasource.driver-class-name}")
    private String driver;

    @Bean
    public DataSource dataSource(){

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(usr);
        dataSource.setPassword(pwd);

        return dataSource;
    }
}

单元测试类:

package com.example.spring_database_1.spring;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;

import javax.sql.DataSource;
import java.sql.SQLException;

@SpringBootTest(classes =SpringDataSourceTest.class)
@ComponentScan
public class SpringDataSourceTest {

    @Test
    public void test1(@Autowired DataSource dataSource) throws SQLException {
        System.out.println(dataSource.getConnection());
    }
}

运行结果:
@SpringBootTest(classes =SpringDataSourceTest.class)表示当前这个类为单元测试类,并且还是一个启动类,不会和Spring Boot那个启动类产生关联,@ComponentScan表示自动扫描,没有指定的话默认是扫描当前包下的所有。
在这里插入图片描述

3. 利用JdbcTemplate操作数据库

在Spring项目中需要配置JdbcTemplate这个Bean,参考代码如下:

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
    return new JdbcTemplate(dataSource);
}

这个和上面那个DataSource Bean放在同一个配置文件下,单元测试类如下,在mysql数据库中插入一条数据。

@Test
public void test2(@Autowired JdbcTemplate jdbcTemplate){

    int rows = jdbcTemplate.update("insert into user values(null,?)", "张三");
    System.out.println(rows);
}

在Spring Boot项目中,上述不需要单独配置,Spring Boot会自动进行配置的。

4. 利用JdbcTemplate查询数据

查询单个数据:

@Test
public void test3(@Autowired JdbcTemplate jdbcTemplate){
    String sql = "select * from user where u_id = ?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
    System.out.println(user);
}

运行结果:
在这里插入图片描述
实体用户类为:

package com.example.spring_database_1.entity;

public class User {
    private Integer u_id;
    private String u_name;

    public Integer getU_id() {
        return u_id;
    }

    public void setU_id(Integer u_id) {
        this.u_id = u_id;
    }

    public String getU_name() {
        return u_name;
    }

    public void setU_name(String u_name) {
        this.u_name = u_name;
    }

    @Override
    public String toString() {
        return "User{" +
                "u_id=" + u_id +
                ", u_name='" + u_name + '\'' +
                '}';
    }
}

查询多个数据,可以改写上述代码为:

@Test
public void test3(@Autowired JdbcTemplate jdbcTemplate){
    String sql = "select * from user";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    for (User user : users) {
        System.out.println(user);
    }
}

在这里插入图片描述

5. Spring 声明式事务

需要在一个配置类下定义事务管理的Bean,并且需要开启事务(使用注解 @EnableTransactionManagement),另外就是需要添加注解==@Transactional==(可以放在方法、类上)
配置类如下:

package com.example.spring_database_1.spring;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;


@Configuration
@EnableTransactionManagement
public class MyConfiguration {

    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String usr;
    @Value("${spring.datasource.password}")
    private String pwd;
    @Value("${spring.datasource.driver-class-name}")
    private String driver;


    @Bean
    public DataSource dataSource(){

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(usr);
        dataSource.setPassword(pwd);
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

这是上述所有的Bean的配置。
示例方法为(添加了注解@Transactional):

package com.example.spring_database_1.spring.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public Integer insert(){

        int row = jdbcTemplate.update("insert into user values(null,?)", "张三");
        int a = 1 / 0;

        return row;
    }

}

单元测试类为:

package com.example.spring_database_1.spring;


import com.example.spring_database_1.spring.dao.UserDao;
import com.example.spring_database_1.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;

@SpringBootTest(classes =SpringDataSourceTest.class )
@ComponentScan
public class SpringDataSourceTest {

    @Autowired
    private UserDao ud;
    @Test
    public void test2(){

        int rows = ud.insert();
    }
}

运行结果当然也是会报错的,但是数据库中不会插入数据。
在这里插入图片描述
如果是Spring Boot项目,要达到上述效果,只需要在对应的类或者方法上添加注解 @Transactional 即可。
关于@Transactional注解的使用:

  • 放在类上面,表示当前这个类下的所有方法都会开启事务(这种通常不会使用,因为开启事务也是会占用一定资源的);
  • 放在方法上面,表示当前这个方法开启事务(放在业务逻辑类下的方法上面);

6. 事务的隔离级别

通过设置事务的隔离级别来解决并发过程中产生的一些问题。在并发情况下,对同一个数据(变量、对象)进行读写操作才产生的问题(脏读、不可重复读、幻读)。

6.1 脏读

现在有两个事务,事务2以微弱的优势首先进行修改操作,把一个值修改为100,此时事务1执行查询操作,得到值为100,但是事务2在执行过程中出现了异常,进行了事务回滚,此时的值没有得到修改,依旧是200,也就是说此时事务1得到值100是一个脏数据。
一个事务,读取了一个事务中没有提交的数据,会在本事务中产生数据不一致的问题。
解决方案

@Transactional(isolation = Isolation.READ_COMMITTED)

这个不用设置,数据库默认都会保证都已提交

6.2 不可重复读

现在有两个事务,事务1以微弱的优势首先进行查询操作,得到结果为100,此时事务2执行修改操作,把对应的值修改为200,之后事务1又进行了一次查询操作,此时得到的值为200。
一个事务中,多次读取相同的数据,但是读取的结果不一样,从而造成在本事务中所读取的数据不一致的问题。
解决方案:

@Transactional(isolation = Isolation.REPEATABLE_READ)

相当于加上一个行锁

6.3 幻读

现在有两个事务,事务1首先执行求和操作,对正常表里的一些数据进行求和操作,得到的值为100,而事务2执行了插入数据操作,插入了100,此时事务1又进行了一次求和操作,得到的值为200。
一个事务中,多次对数据进行整表数据读取,但是结果不一样,从而导致在本事务中产生数据不一致的问题。
解决方案:

@Transactional(isolation = Isolation.SERIALIZABLE)

相当于对整表上锁

6.4 不可重复读和幻读的区别

前者,只需要锁行;后者,需要锁表

6.5 三种方案的比较

方案脏读不可重复都幻读
READ_COMMITTED不会可能出现可能出现
REPEATABLE_READ不会不会可能出现
SERIALIZABLE不会不会不会

并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运行效率:SERIALIZABLE<REPEATABLE_READ<READ_COMMITTED

数据库默认情况下设置了隔离级别:

在这里插入图片描述
上述是Mysql数据的隔离级别,如果是Oracle,它的隔离级别为READ_COMMITTED。

对于脏读,通过设置都已提交(行锁,读不会加锁);对于不可重复读,需要设置重复读(行锁,读和写都会上锁);对于幻读,通过设置串行化(表锁)。

7. 事务的传播特性

常用的 spring 的事务传播行为:

事务传播行为类型外部不存在事务(外层事务)外层存在事务(嵌套事务)使用方式
REQUIRED(默认)开启新的事务融合到外部事务中@Transactional(propagation = Propagation.REQUIRED),适合增删改查,常用
SUPPORTS不开启新的事务融合到外部事务中@Transactional(propagation = Propagation.SUPPORTS),适合查询
REQUIRES_NEW开启新的事务不用外部事务,创建新的事务中@Transactional(propagation = Propagation.REQUIRES_NEW),适合内部事务和外部事务不存在业务关联,如日志,常用
NEVER不开启新的事务抛出异常@Transactional(propagation = Propagation.NEVER),不常用

8. 设置事务 只读(readOnly)

readOnly:只会设置在查询的业务方法中
connection.setReadOnly(true),通知数据库,当前数据库操作是只读,数据库就会当作只读做相应优化。

  1. readOnly并不是所有数据库都支持,不同的数据库下会有不同的结果;
  2. 设置readOnly后,connection都会被赋予readOnly,效果取决于数据库的实现。
package com.example.spring_database_1.spring.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(readOnly = true)
    public Integer insert(){

        return jdbcTemplate.update("insert into user values(null,?)", "张三");
    }
}

运行结果(设置为readOnly=true之后,在下面执行除查询之外的操作,所以报错。):
在这里插入图片描述

9. 超时属性(timeout)

指定事务等待的最长时间(秒)
当前事务访问数据时,有可能访问的数据库被别的数据进行加锁处理,那么此时事务就必须等待,如果等待时间过长给用户造成的体验感差。

package com.example.spring_database_1.spring.dao;

import com.example.spring_database_1.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(timeout = 3)
    public Integer insert(){

        return jdbcTemplate.update("insert into user values(null,?)", "张三");
    }


    @Transactional(isolation = Isolation.SERIALIZABLE)
    public List<User> query(){

        List<User> users = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return users;
    }

}
package com.example.spring_database_1.spring;


import com.example.spring_database_1.spring.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;


@SpringBootTest(classes =SpringDataSourceTest.class )
@ComponentScan
public class SpringDataSourceTest {


    @Test
    public void test1(@Autowired UserDao ud) throws InterruptedException {

        Thread thread = new Thread(()->{
            System.out.println("查询。。。");
            ud.query();
        });

        thread.start();

        Thread.sleep(1000);

        Thread thread2 = new Thread(()->{
            System.out.println("1。。。");
            ud.insert();
            System.out.println("2。。。");
        });

        thread2.start();

        thread.join();
        thread2.join();
    }

}

运行结果:
在这里插入图片描述
查询操作那里使用了事务的隔离级别串行化,此时需要等待查询操作执行完毕,才会执行插入操作,但插入操作那里设置超时时间为3秒,超过3秒之后,此时报错。

10. 异常属性

设置当前事务出现的那些异常就进行回滚或者提交。
默认对于RuntimeException及其子类,采用的是回滚的策略。
默认对于非RuntimeException就不会回滚。

  1. 设置哪些异常不回滚(notRollbackFor)
  2. 设置那些异常回滚(rollbackFor)
@Transactional
public void rollbackFor() throws AlreadyBoundException {

     insert();
     throw new AlreadyBoundException("xxx异常");

 }
@Test
public void test2(@Autowired UserDao ud) throws AlreadyBoundException {
    ud.rollbackFor();
}

此时并没有回滚,数据库里边插入了一条数据,如下:
在这里插入图片描述
如果想所有异常都会回滚,可以设置如下:

@Transactional(rollbackFor = Exception.class)
public void rollbackFor() throws AlreadyBoundException {
    insert();
    throw new AlreadyBoundException("xxx异常");

}

11. 事务失效原因

事务实现原理:动态代理
在这里插入图片描述

失效原因

  • 保证事务类配置为一个Bean,@Component、@Service。。。;
  • 事务的方法不能是private;
  • 自己把异常捕捉了,并且没抛出去;
  • 动态代理层面失效原因:
    • 要让aop、事务生效,一定要通过动态代理的对象调用目标方法,不能通过普通对象去调用;
    • 直接调用本类的方法,没有通过动态代理的对象调用目方法,解决方案;
      • 将本类Bean自动装配进来(会产生循环依赖,Spring Boot中需要单独开启循环依赖支持,直接在配置文件中配置即可);
      • ((xxx类)AopContext.currentProxy()).xxx方法(获取当前类)
        1. 前提需要 @EnableAspectJAutoProxy(exposeProxy = true);
        2. 添加aop的依赖;
        3. 把本类的方法移动到其他类的Bean中,然后再把其他类自动装配进来;

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

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

相关文章

vue element-ui的el-image 和 el-table冲突层级冲突问题问题preview-teleported

问题: 解决代码:preview-teleported <el-image style"width: 50px; height: 50px" :src"props.row.url" :zoom-rate"1.2" :max-scale"7":min-scale"0.2" :preview-src-list"[props.row.url]" :initial-index&…

vue3 开发利器——unplugin-auto-import

这玩意儿是干啥的&#xff1f; 还记得 Vue 3 的组合式 API 语法吗&#xff1f;如果有印象&#xff0c;那你肯定对以下代码有着刻入 DNA 般的熟悉&#xff1a; 刚开始写觉得没什么&#xff0c;但是后来渐渐发现&#xff0c;这玩意儿几乎每个页面都有啊&#xff01; 每次都要写…

FreeSWITCH 简单图形化界面34 - 网络环境安全的情况下,进行任意SIP注册

FreeSWITCH 简单图形化界面34 -网络环境安全的情况下&#xff0c;进行任意SIP注册 测试环境1、前言2、参数3、实践一下 测试环境 http://myfs.f3322.net:8020/ 用户名&#xff1a;admin&#xff0c;密码&#xff1a;admin FreeSWITCH界面安装参考&#xff1a;https://blog.cs…

基于Matlab深度学习的CT影像识别系统研究与实现

通过使用AlexNet、GoogLeNet和VGGNet等预训练模型&#xff0c;并结合迁移学习技术&#xff0c;对CT影像进行特征提取和分类。系统在公开数据集上进行了训练和测试&#xff0c;结果表明&#xff0c;该方法能够有效区分COVID-19和非COVID-19的CT影像&#xff0c;具有较高的准确率…

如何使用postman做接口测试?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 常用的接口测试工具主要有以下几种&#xff1a; Postman: 简单方便的接口调试工具&#xff0c;便于分享和协作。具有接口调试&#xff0c;接口集管理&#…

数据分析的尽头是web APP?

数据分析的尽头是web APP&#xff1f; 在做了一些数据分析的项目&#xff0c;也制作了一些数据分析相关的web APP之后&#xff0c;总结自己的一些想法和大家分享。 1.web APP是呈现数据分析结果的另外一种形式。 数据分析常见的结果是数据分析报告&#xff0c;可以是PPT或者…

学习笔记037——Java中【Synchronized锁】

文章目录 1、修饰方法1.1、静态方法&#xff0c;锁定的是类1.2、非静态方法&#xff0c;锁定的是方法的调用者&#xff08;对象&#xff09; 2、修饰代码块&#xff0c;锁定的是传入的对象2.1、没有锁之前&#xff1a;2.2、有锁后&#xff1a; 实现线程同步&#xff0c;让多个线…

开源加密库mbedtls及其Windows编译库

目录 1 项目简介 2 功能特性 3 性能优势 4 平台兼容性 5 应用场景 6 特点 7 Windows编译 8 编译静态库及其测试示例下载 1 项目简介 Mbed TLS是一个由ARM Maintained的开源项目&#xff0c;它提供了一个轻量级的加密库&#xff0c;适用于嵌入式系统和物联网设备。这个项…

QTableWidget使用代理绘制分行显示

在这里插入代码片# 创建主窗口类&#xff1a; 使用 QTableWidget 作为核心控件。 设置表头及行列信息。 自定义代理&#xff1a; 继承 QStyledItemDelegate&#xff0c;实现代理模式。 重写 paint 和 sizeHint 方法&#xff0c;支持多行文本绘制。 设置行高以适应多行显示。 …

Python学习35天

# 定义父类 class Computer: CPUNone MemoryNone diskNone def __init__(self,CPU,Memory,disk): self.disk disk self.Memory Memory self.CPU CPU def get_details(self): return f"CPU:{self.CPU}\tdisk:{self.disk}\t…

企业OA管理系统:Spring Boot技术深度解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Android 图形系统之一:概览

Android 图形系统是一套完整的架构&#xff0c;用于管理从应用绘制到显示屏幕的整个流程。它涉及多个层次和组件&#xff0c;从应用程序到硬件&#xff0c;确保每一帧都能准确、高效地呈现到用户的设备屏幕上。 1. Android 图形系统的架构 Android 图形系统的架构可以分为以下…

深度理解进程的概念(Linux)

目录 一、冯诺依曼体系 二、操作系统(OS) 设计操作系统的目的 核心功能 系统调用 三、进程的概念与基本操作 简介 查看进程 通过系统调用获取进程标识符 通过系统调用创建进程——fork() 四、进程的状态 操作系统中的运行、阻塞和挂起 理解linux内核链表 Linux的进…

系统思考—共同看见

在一家零售企业的项目中&#xff0c;团队频繁讨论客户流失的严重性&#xff0c;但每次讨论的结果都无法明确找出问题的根源。大家都知道客户流失了&#xff0c;但究竟是什么原因导致的&#xff0c;始终没有一致的答案。市场部认为是客户体验差&#xff0c;客服部门觉得是响应慢…

从0开始学PHP面向对象内容之常用设计模式(组合,外观,代理)

二、结构型设计模式 4、组合模式&#xff08;Composite&#xff09; 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它将对象组合成树形结构以表示”部分–整体“的层次结构。通过组合模式&#xff0c;客户端可以以一致的方式处理单个对…

Linux 线程互斥

目录 0.前言 1.相关概念 2.互斥量&#xff08;mutex&#xff09; 2.1 代码引入 2.2为什么需要互斥量 2.3互斥量的接口 2.3.1 初始化互斥量 2.3.2 销毁互斥量 2.3.3 互斥量加锁和解锁 2.4改写代码 3.互斥量的封装 4.小结 &#xff08;图像由AI生成&#xff09; 0.前言 在多线…

前端实用知识-用express搭建本地服务器

目录 一、为什么会有这篇文章&#xff1f; 二、使用前的准备-如环境、工具 三、如何使用&#xff1f;-express常用知识点 四、代码演示-配合截图&#xff0c;简单易懂 一、为什么会有这篇文章&#xff1f; 在日常前端开发中&#xff0c;我们离不开数据&#xff0c;可能是用…

用nextjs开发时遇到的问题

这几天已经基本把node后端的接口全部写完了&#xff0c;在前端开发时考虑时博客视频类型&#xff0c;考虑了ssr&#xff0c;于是选用了nextJs&#xff0c;用的是nextUi,tailwincss,目前碰到两个比较难受的事情。 1.nextUI个别组件无法在服务器段渲染 目前简单的解决方法&…

【数据结构】二叉树(2)

目录 1. 二叉树的遍历 前序遍历 中序遍历 后序遍历 2. 计算二叉树中的节点个数 3. 计算二叉树中叶子节点个数 4. 计算二叉树的深度 5. 计算二叉树第k层节点个数 6. 二叉树基础练习 7. 二叉树的创建 8. 二叉树的销毁 9. 层序遍历 10. 判断二叉树是否为完全二叉树 1…

比特币与区块链原理解析:矿机挖矿与去中心化的未来

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…