Spring Boot 事务和事务传播机制

news2024/11/20 2:40:57

1. 为什么需要事务?

事务定义

将一组操作封装成一个执行单元 (封装到一起),这一组的执行具备原子性, 那么就要么全部成功,要么全部失败.

为什么要用事务?

比如转账分为两个操作:

第一步操作:A 账户-100 元。
第二步操作:B账户 +100 元。

如果没有事务,第一步执行成功了,第二步执行失败了,那么 A 账户平白无故的 100 元就“人间蒸发”了。而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

2. Spring中事务的实现

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

  1. 编程式事务(手动写代码操作事务)
  2. 声明式事务(利用注解自动开启和提交事务)

在开始讲解它们之前,咱们先来回顾事务在MSQL 中是如何使用的?

2.1 MySQL中的事务使用

事务在 MySQL 有 3个重要的操作: 开启事务、提交事务、回滚事务,它们对应的操作命令如下:

-- 开启事务
start transaction;
-- 业务执行

-- 提交事务
commit;

-- 回滚事务
rollback;

2.2 Spring 编程式事务(手动)

Spring 手动操作事务和上面MySQL 操作事务类似,它也是有3个重要操作步骤:

  • 开启事务(获取事务)
  • 提交事务
  • 回滚事务

SpringBoot 内置了两个对象,DataSourceTransactionManager 用来获取事务(开启事务)、提交或回滚事务的,而 TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从而获得一个事务 TransactionStatus,实现代码如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    // 编程式事务
    // JDBC 事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 定义事务属性 (使用TransactionDefinition记录事务属性)
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public int del(Integer id) {
        if (id == null || id <= 0) return 0;
        // 1. 开启事务
        TransactionStatus transactionStatus = null;
        int result = 0;
        try {
            transactionStatus = transactionManager.getTransaction(transactionDefinition);
            // 业务操作: 删除用户
            result = userService.del(id);
            System.out.println("删除: " + result);
            // 2. 提交事务 / 回滚事务
//            transactionManager.commit(transactionStatus); // 提交事务
        } catch (Exception e) {
            if (transactionStatus != null) {
                transactionManager.rollback(transactionStatus); // 回滚事务
            }
        }
        return result;
    }
}

业务操作相关代码如下:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public int del(Integer id){
        return userMapper.del(id);
    }
}
@Mapper
public interface UserMapper {
    int del(@Param("id") Integer id);
}
    <delete id="del">
        delete from userinfo where id=#{id}
    </delete>

运行结果:

可以看到, 删除的业务操作已经成功了, 但是因为回滚操作, 所以在执行前后查询数据库的时候是可以看到对应的数据依然存在.

从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。

2.3 Spring 声明式事务 (自动)

声明式事务的实现很简单,只需要在需要的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体实现代码如下:

@RestController
@RequestMapping("/user2")
public class UserController2 {

    @Autowired
    private UserService userService;

    @Transactional  // 在方法开始之前开启事务, 方法正常执行结束之后提交事务, 如果执行途中发生异常, 则回滚事务.
    @RequestMapping("/del")
    public int del(Integer id) {
        if (id == null || id <= 0) return 0;
        return userService.del(id);
    }
}                  

执行前后查询数据库, 可以看到由于正常执行所以没有触发回滚.

2.3.1 @Transactional作用范围

@Transactional 可以用来修饰方法或类

  • 修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法
  • 修饰类时:表明该注解对该类中所有的 public 方法都生效。

2.3.2 @Transactional 参数说明

参数

作用

value

当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器

transactionManager

当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器

propagation

事务的传播行为,默认值为 Propagation.REQUIRED

isolation

事务的隔离级别.默认值为 Isolation.DEFAULT

timeout

事务的超时时间,默认值为-1. 如果超过该时间限制但事务还没有完成,则自动回滚事务.

readOnly

指定事务是否为只读事务.默认值为 false;

为了忽略那些不需要事务的方法,比如读取数据可以设置read-only为 true.

rollbackFor

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

rollbackForClassName

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

noRollbackFor

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

noRollbackForClassName

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

2.3.3 注意事项

@Transactional 在异常被捕获的情况下,不会进行事务自动回滚,验证以下代码是否会发生事务回滚:

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;

    @Transactional 
    @RequestMapping("/del")
    public int del(Integer id) {
        if (id == null || id <= 0) return 0;
        int result = userService.del(id);
        System.out.println("删除: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return result;
    }
}                  

执行前后查询数据库, 可以看到在异常被捕获的情况下,并不会进行事务自动回滚.

事务不会自动回滚解决方案

解决方案1

对于捕获的异常,事务是会自动回滚的,因此解决方案1就是可以将异常重新抛出,具体实现如下:

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;

    @Transactional 
    @RequestMapping("/del")
    public int del(Integer id) {
        if (id == null || id <= 0) return 0;
        int result = userService.del(id);
        System.out.println("删除: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw e;
        }
        return result;
    }
}                  

执行前后查询数据库, 可以看到这里是有异常, 触发了回滚.

解决方案2

手动回滚事务,在方法中使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码如下:

public class UserController2 {
        // .. 省略代码, 同上
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}                  

执行前后访问数据库可以看到, 这里成功实现了回滚操作.

2.3.4 @Transactional 工作原理

@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB 动态代理。

@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。

@Transactional 实现思路预览:

@Transactional 具体执行细节如下图所示:

3. 事务隔离级别

3.1 事务特性回顾

事务有4 大特性 (ACID),原子性、持久性、一致性和隔离性,具体概念如下:

  • 原子性: 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚 (Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交 (Read uncommitted)、读提交 (read committed) 、可重复读 (repeatable read) 和串行化(Serializable)。

上面 4个属性,可以简称为ACID

原子性 (Atomicity,或称不可分割性)
一致性 (Consistency)
隔离性 (lsolation,又称独立性)
持久性 (Durability)

而这 4 种特性中,只有隔离性 (隔离级别) 是可以设置的

为什么要设置事务的隔离级别?

设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的。

什么是可控呢?

比如近几年比较严重的新冠病毒,我们会把直接接触到确诊病例的人员隔离到酒店,而把间接接触者 (和直接接触着但未确诊的人) 隔离在自己的家中,也就是针对不同的人群,采取不同的隔离级别,这种隔离方式就和事务的隔离级别类似,都是采取某种行动让某个事件变的“更可控”。而事务的隔离级别就是为了防止,其他的事务影响当前事务执行的一种策略

3.2 Spring 中设置事务隔离级别

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置,具体操作如下图所示:

3.2.1 MySQL事务隔离级别有 4 种

  1. READ UNCOMMITTED: 读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
  2. READ COMMITTED: 读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL查询中,可能会得到不同的结果,这种现象叫做不可重复读。
  3. REPEATABLE READ: 可重复读,是MySQL的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)
  4. SERIALIZABLE: 序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多

1. 脏读: 一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。
2. 不可重复读: 一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。
3. 幻读: 一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。

在数据库中通过以下SQL 查询全局事务隔离级别和当前连接的事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;

以上SQL的执行结果如下:

3.2.2 Spring 中事务隔离级别有 5 种

Spring 中事务隔离级别包含以下 5 种:

  1. Isolation.DEFAULT: 以连接的数据库的事务隔离级别为主
  2. lsolation.READ_UNCOMMITTED: 读未提交,可以读取到未提交的事务,存在脏读
  3. Isolation.READ_COMMITTED: 读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ: 可重复读,解决了不可重复读,但存在幻读(MVSQL默认级
  5. lsolation.SERIALIZABLE: 串行化,可以解决所有并发问题,但性能太低。

从上述介绍可以看出,相比于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了一个lsolation.DEFAULT (以数据库的全局事务隔离级别为主)

Spring 中事务隔离级别只需要设置 @Transactional 里的 isolation 属性即可,具体实现代码如下:

@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
    // 业务实现
}

4. Spring 事务传播机制

4.1 事务传播机制是什么?

Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。

4.2 为什么需要事务传播机制?

事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性的 (稳定性的)。

例子: 像新冠病毒一样,它有不同的隔离方式(酒店隔离还是居家隔离),是为了保证疫情可控,然而在每个人的隔离过程中,会有很多个执行的环节,比如酒店隔离,需要负责人员运送、物品运送消杀原生活区域、定时核算检查和定时送餐等很多环节,而事务传播机制就是保证一个事务在传递过程中是可靠性的,回到本身案例中就是保证每个人在隔离的过程中可控的。

事务隔离级别解决的是多个事务同时调用一个数据库的问题,如下图所示:

而事务传播机制解决的是一个事务在多个节点(方法) 中传递的问题,如下图所示:

4.3 事务传播机制有哪些?

Spring 事务传播机制包含以下 7种:

  1. Propagation.REQUIRED: 默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. Propagation.SUPPORTS: 如果当前存在事务,则加入该事务; 如果当前没有事务,则以非事务的方式继续运行。
  3. Propagation.MANDATORY: (mandatory: 强制性) 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW: 表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5. Propagation.NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常
  7. Propagation.NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

以上 7 种传播行为,可以根据是否支持当前事务分为以下 3类:

以情侣关系为例来理解以上分类:

4.4 Spring 事务传播机制使用和各种场景演示

4.4.1 支持当前事务(REQUIRED)

我们有多个方法进行数据的传递.

不考虑拦截器, 考虑标准的分层, 比如要执行用户User的添加add操作, 下图为该执行流程图

首先要执行add, 就会先在UserController加@Transactional, 下面为了演示事务的传播机制, 就需要让用户调用UserService, 并加上@Transactional.
在UserService中, 我们需要操作两张表, 先调用UserMapper中的add添加方法, 这个方法是添加用户的, 再调用日志添加类.

从上图可见, @Transactional是通过UserController传递到UserService, 再从UserService传递到LogService.

注意, 正常来说在*Service要调用*Mapper类, 但是这里如果使用LogMapper是Interface, 就无法演示清楚预期效果, 所以这里调用LogService.

在mycnblog中创建日志表.

CREATE TABLE `log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

在项目中创建实体类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}
@Data
public class Log {
    private int id;
    private LocalDateTime timestamp;
    private String message;
}

演示事务传播机制代码实现:

User

@RestController
@RequestMapping("/user3")
public class UserController3 {

    @Autowired
    private UserService userService;

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username, String password){
        if (null == username || null == password ||
                username.equals("") || password.equals("")) return 0;
        UserInfo user = new UserInfo();
        user.setUsername(username);
        user.setPassword(password);
        int result = userService.add(user);
        // 用户添加操作
        return 0;
    }
}

UserService 实现代码如下:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo) {
        // 给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: " + addUserResult);
        // 添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息"); 
        logService.add(log);
        return addUserResult;
    }
}

在UserMapper中写添加add方法.

@Mapper
public interface UserMapper {
    int add(UserInfo userInfo);
}

在xml中写具体的实现:

    <insert id="add">
        insert into userinfo(username, password) values
        (#{username},#{password})
    </insert>

Log

LogMapper.interface

@Mapper
public interface LogMapper {
    int add(Log log);
}

LogMapper.xml

    <insert id="add">
        insert into log(`message`) values(#{message})
    </insert>

LogService:

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Log log) {
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);
        // 回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        int num = 10 / 0;
        return result;
    }
}

接下来我们观察一下在UserService中已经执行成功的user添加操作有没有回滚事务, 如果user中没有正常添加数据, 那么就说明REQUIRED是支持当前的事务, 并且是以加入的方式去进行的.

可以看到, 添加用户和添加日志都已经成功了, 最终有一个算数异常报出. 我们再来看一下数据库.

可以看到, log回滚了, 符合预期, 然而userinfo中并没有将前面url所传数据wangwu添加过来, 那么这就说明, REQUIRED这种事务传播机制是支持当前事务的, 并且是加入事务的方式, 让自己变成事务的一部分, 如果其中有任何一个地方出现问题, 不论前面做了多少业务, 那么整条调用链上所有的方法都会进行回滚.

4.4.2 不支持当前事务(REQUIRESNEW)

将整条事务调用链上的所有方法修改为 REQUIRES_NEW不支持当前事务,重新创建事务,观察执行结果:

    @Transactional(propagation = Propagation.REQUIRES_NEW)

预期执行结果: 在一个调用链上的事务,各自执行相互不干扰。

对上面的示例来说, 是用户添加 (事务) 成功、但是日志添加 (事务) 失败。

可以看到, 我们的执行结果并没有符合我们的预计结果.

原因是由于没有处理 by zero的异常, 导致整个调用链上的其他对象都感知到了, 所以进行了回滚.

此时修改LogService代码:

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Log log) {
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);
        // 回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}

这个时候便不会报错了.

可以看到, 用户和日志都添加成功了, 但是日志中进行了手动回滚.

验证一下:

说明符合了预期.


4.4.3 嵌套事务 (NESTED) 和加入事务 (REQUIRED )的区别

  • 整个事务如果全部执行成功,二者的结果是一样的。
  • 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果

至此, 整个JavaEE专栏的知识介绍就到这里.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【WinForm】WebView2-个性化浏览器-桌面程序开发详解

这是一个桌面程序上的浏览器&#xff0c;是用插件WebView2开发的浏览器桌面程序&#xff0c;功能体验堪比Edge浏览器&#xff0c;相比使用Chrome内核插件开发浏览器来说&#xff0c;还是用插件WebView2开发来得简单一些&#xff0c;接下来讲一讲实现过程。 开发之前&#xff0c…

Centos7部署Python程序详解

Centos7服务器部署Python 本文章前半部分为部署过程&#xff0c;后半部分为部署中碰到的问题及解决方案&#xff0c;仅供参考&#xff01;&#xff01;&#xff01;&#xff0c;本文示例为部署py文件为例。 部署步骤&#xff1a; 登录centos7服务器后 1.查看python版本 py…

Kotlin 高阶函数详解

高阶函数 在 Kotlin 中&#xff0c;函数是一等公民&#xff0c;高阶函数是 Kotlin 的一大难点&#xff0c;如果高阶函数不懂的话&#xff0c;那么要学习 Kotlin 中的协程、阅读 Kotlin 的源码是非常难的&#xff0c;因为源码中有太多高阶函数了。 高阶函数的定义 高阶函数的…

CGAL 点云分类

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 点云分类一直是点云数据应用的永恒课题,它包含很多,如地面点分类、建筑物分类、植被分类等。CGAL中也为我们提供了一种点云分类的方式,其具体的计算过程如下所述: 首先,使用点云中所携带的一些几何特征来对数据…

三、数据库索引

1、索引介绍 索引是一种用于快速查询和检索数据的数据结构&#xff0c;其本质可以看成是一种排序好的数据结构。 常见的索引结构有&#xff1a;B数&#xff0c;B树&#xff0c;Hash和红黑树等。在MySQL中&#xff0c;无论是 InnoDB还是MyISAM&#xff0c;都使用了B树作为索引…

西班牙卡瓦起泡酒的风味搭配

卡瓦是一种对食物友好的西班牙起泡酒&#xff0c;它的制作方法和香槟一样&#xff0c;可以和类似的食物搭配。卡瓦食物搭配包括各种食物&#xff0c;从海鲜和鱼到火腿&#xff0c;以及不同类型的小吃&#xff0c;也可以将卡瓦酒与甜点、水果和奶酪搭配。 卡瓦酒是世界上最著名的…

IDEA常用插件之注解插件

文章目录 注解插件JavaDoc插件安装修改配置生成文档加入自己信息 Easy JavaDoc安装插件在线安装离线安装中文名自动转英文加注释默认快捷键&#xff08;可通过IDEA快捷键设置修改&#xff09; 注解插件 JavaDoc插件 安装 修改配置 生成文档加入自己信息 Easy JavaDoc 中文文…

一种pug与html相互转换的工具

有时候看pug很不方便&#xff0c;这个语言虽然简洁&#xff0c;但可读性与维护性较差&#xff0c;所以需要进行转换&#xff0c;这个是win工具&#xff0c;比较方便。 这个工具的下载地址如下&#xff1a; https://download.csdn.net/download/qq_40032778/88244980 解压后如下…

PDFPrinting.Net Crack

PDFPrinting.Net Crack 它能够轻松灵活地预测完美的打印结果以及用户文件的示例性显示。在.NET的PDF打印中&#xff0c;可以快速浏览最关键的元素。如果用户需要获得更详细的概述&#xff0c;那么他可以查看快速入门手册&#xff0c;甚至现有文档的详细概述参考。 在这种情况下…

atxserver bug记录

8. 解决无法点击屏幕 原因&#xff1a;remotecontrol_android.html为按比例自动缩放&#xff0c;play.html&#xff08;Django&#xff09;显示的屏幕大小不会随页面放大缩小。有个h265方式获取的宽高是720*448&#xff0c;电脑上显示的大小是545*339&#xff0c;这个对不上&am…

理解机制,再探单元工厂的实现原理

最近有点忙,好久没更新文章了,今天继续再研究一下单元工厂的实现机制。为什么我们要这么重视这一块的内容呢?因为用计算机的目的是为了处理大量数据,如果数据量不大,大多情况下用纸就好了,专门用个计算设备的便捷性也就体现不出来。而大量数据的呈现方式的多样性精髓就在…

cuda编程002—流

没有使用同步的情况&#xff1a; #include <stdio.h> #include <cuda_runtime.h>__global__ void test_kernel(){printf("Message from Device.\n"); } void test(){test_kernel<<<1, 1>>>(); } #include <cuda_runtime.h> #i…

蓝蓝设计-UI设计公司作品-博晖创新原子吸收光谱仪软件交互及界面设计

博晖创新原子吸收光谱仪软件交互及界面设计 图标设计 | 交互设计 | 界面设计 博晖公司拥有强大的自主研发实力&#xff0c;建立了专业的研发团队&#xff0c;通过不断的技术创新&#xff0c;形成了分子诊断、免疫诊断、原子吸收、原子荧光及质谱五大技术平台&#xff0c;并成功…

ESP8266显示gif动态图,使用U8g2库

一.代码 #include <U8g2lib.h> //实现gif火柴人跑步动画// 定义GIF动画的帧数据 const unsigned char frame1[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0…

安装docker服务及docker基本操作

一、docker安装&#xff08;yum安装&#xff09; 基于centos7 1.添加docker-ce 源信息 安装依赖包&#xff08;yum-utils 提供了 yum-config-manager &#xff0c;并且 device mapper 存储驱动程序需要device-mapper-persistent-data 和 lvm2&#xff09; yum install yum-…

阿丹千问V1.5-迭代升级-使用人工智能提炼文字-java代码生成流程图-百度千帆大模型

阿丹&#xff1a; 今天在学习新技的时候发现了一个可以偷懒的地方&#xff0c;在学习新的知识体系的时候需要去理解文档中的逻辑关系等等&#xff0c;那么如果有一个东西可以支持输入一段具有逻辑的文字就可以帮我提炼起其中的逻辑&#xff0c;并帮助我绘制一个流程图岂不是美哉…

Node基础--Node基础使用体验

在上一篇文章中提到我们按照好Node.js之后&#xff0c;就可以在控制台看到其版本。那么下面我们一起来看看如何使用node执行js文件代码。 (1).在本地创建一个名称为hello.js的文件&#xff0c;输入内容如下所示: console.log("helloworld");var a 1;var b 2;cons…

leetcode 118.杨辉三角

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;https://leetcode.cn/problems/pascals-triangle/description/ 代码&#xff1a; class Solution { public:vector<vector<int>> generate(int numRows) {// 先开空间vector<vector<int>> v;v.…