【Spring】Spring 中事务的实现

news2024/9/20 6:19:36

目录

  • 1.编程式事务(手动编写代码)
  • 2.声明式事务(利用注解)
    • 2.1 @Transactional作用范围
    • 2.2 @Transactional参数说明
    • 2.3 @Transactional工作原理
  • 3.Spring 中设置事务隔离级别
    • 3.1 事务四大特性ACID
    • 3.2 事务的隔离级别
    • 3.2 Spring中设置事务的隔离级别
  • 4.Spring 中事务传播机制
    • 4.1 事务的传播机制是什么
    • 4.2 传播机制和隔离级别的作用
    • 4.3事务传播机制的种类
  • 5.Spring 中事务传播机制的使用
    • 5.1 支持当前事务REQUIRED
    • 5.2 不支持当前事务REQUIRES_NEW
    • 5.3 不支持当前事务NEVER抛异常
    • 5.4 NESTED嵌套事务
    • 5.5 嵌套事务和加入事务的区别


事务定义:将一组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败

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

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)。

1.编程式事务(手动编写代码)

编程式事务有三个步骤:

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

SpringBoot 内置了两个对象:

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

实现代码如下:

package com.example.mybatisdemo.controller;

import com.example.mybatisdemo.model.User;
import com.example.mybatisdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 手动提交事务
 */
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionController {

    @Autowired
    private UserService userService;

    //获取数据库事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //数据库事务默认配置
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        //获取一个事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        User user=new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响行数:"+result);
        //回滚到transaction状态
        //dataSourceTransactionManager.rollback(transaction);
        //事务提交
        dataSourceTransactionManager.commit(transaction);
        return  result;
    }
}


2.声明式事务(利用注解)

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

/**
 * 使用注解提交事务
 */
@Slf4j
@RequestMapping("/trans2")
@RestController
public class TransactionController2 {
    @Autowired
    private UserService userService;

    @Transactional //事务注解//在遇到运行时异常(RuntimeException)和error才会回滚,非运行时异常不回滚
    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        User user=new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响行数:"+result);
        //发生异常时,事务会回滚
        //int a=10/0;
        return  result;
    }
}

补充:如果异常被捕获了,事务不会回滚,代码示例:

    @Transactional
    @RequestMapping("/addUser3")
    public Integer addUser3(String username,String password) throws Exception {
        User user=new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响行数:"+result);
        try {
            int a=10/0;
        } catch (Exception e){
            e.printStackTrace();
        }
        return  result;
    }

2.1 @Transactional作用范围

@Transactional 可以⽤来修饰⽅法或类:

  • 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。

  • 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效

2.2 @Transactional参数说明

在这里插入图片描述

rollbackFornoRollbackFor示例代码:

    /**
     * 指定异常不回滚
     * @param username
     * @param password
     * @return
     */
    @Transactional(noRollbackFor = ArithmeticException.class)
    @RequestMapping("/addUser")
    public Integer addUser1(String username,String password){
        User user=new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响行数:"+result);
        //发生异常时,事务会回滚
        //int a=10/0;
        return  result;
    }

    /**
     * 指定异常回滚
     * @param username
     * @param password
     * @return
     */
    @Transactional(rollbackFor = Exception.class)  //所有异常都回滚
    @RequestMapping("/addUser2")
    public Integer addUser2(String username,String password) throws Exception {
        User user=new User(username,password);
        Integer result = userService.insert(user);
        log.info("影响行数:"+result);
        throwException();
        return  result;
    }
    public void throwException() throws Exception{
        throw new IOException();
    }

2.3 @Transactional工作原理

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路预览:

在这里插入图片描述


3.Spring 中设置事务隔离级别

3.1 事务四大特性ACID

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

原⼦性(Atomicity,或称不可分割性):⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。

⼀致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。

持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

隔离性(Isolation,⼜称独⽴性):数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化(Serializable)。

3.2 事务的隔离级别

事务的隔离级别有四种:

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

在这里插入图片描述
● 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的。
● 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。
● 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。

3.2 Spring中设置事务的隔离级别

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

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

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

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

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

4.Spring 中事务传播机制

4.1 事务的传播机制是什么

先来看一个示例:
在这里插入图片描述
此时有3个事务,A调用了B和C,如果C事务执行失败,B事务执行成功,那么B最终能否成功,A能否成功???

答案是在不同的事务传播机制中,结果是不同的。

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类:
在这里插入图片描述


5.Spring 中事务传播机制的使用

5.1 支持当前事务REQUIRED

正常情况演示:
先开启事务,插入用户,再插入日志:

    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser(String username,String password){
        //插入用户表
        User user=new User(username,password);
        userService.insert(user);
        //插入日志表
        UserLog userLog=new UserLog(username);
        userLogService.insertLog(userLog);
        return true;
    }

UserService实现代码:

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insert(User user) {
        return userMapper.insert(user);
    }

UserLogService实现代码:

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLog(UserLog userLog){
        return userLogMapper.insertLog(userLog);
    }

执行结果:数据库数据成功插入。

错误情况演示:
先开启事务,插入用户,再插入日志(插入日志异常):

UserLogService实现代码(出现除0异常):

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        int a=10/0;
        return result;
    }

执行结果:程序报错,数据库没有插入数据。

执行流程描述:

  1. UserService 中的保存⽅法正常执⾏完成。
  2. UserLogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚。

5.2 不支持当前事务REQUIRES_NEW

先开启事务,插入用户,再插入日志(插入日志异常):

    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser(String username,String password){
        //插入用户表
        User user=new User(username,password);
        userService.insert(user);
        //插入日志表
        UserLog userLog=new UserLog(username);
        userLogService.insertLog(userLog);
        return true;
    }

UserService实现代码:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insert(User user) {
        return userMapper.insert(user);
    }

UserLogService实现代码:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        int a=10/0;
        return result;
    }

执行结果:用户表数据插入成功,日志表数据插入失败
原因:
在这里插入图片描述

5.3 不支持当前事务NEVER抛异常

先开启事务,插入用户,再插入日志(插入日志异常):

UserService实现代码:

    @Transactional(propagation = Propagation.NEVER)
    public Integer insert(User user) {
        return userMapper.insert(user);
    }

UserLogService实现代码:

    @Transactional(propagation = Propagation.NEVER)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        int a=10/0;
        return result;
    }

执行结果:程序报错,执行失败,数据库没有插入数据。

5.4 NESTED嵌套事务

先开启事务,插入用户,再插入日志(插入日志异常):

UserService实现代码:

    @Transactional(propagation = Propagation.NESTED)
    public Integer insert(User user) {
        return userMapper.insert(user);
    }

UserLogService实现代码:

    @Transactional(propagation = Propagation.NESTED)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        int a=10/0;
        return result;
    }

执行结果:执行失败,数据库没有插入数据。

代码①:现在修改一下UserLogService实现代码(加入回滚):

    @Transactional(propagation = Propagation.NESTED)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        try {
            int a = 10 / 0;
        }catch (Exception e){
            //设置回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

执行结果:日志表插入成功,用户表插入失败
原因:在UserLogService加入部分回滚,用户表运行成功,日志表回滚了。

代码②:现在修改一下UserLogService实现代码(将NESTED改为REQUIRED):

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLog(UserLog userLog){
        Integer result=userLogMapper.insertLog(userLog);
        try {
            int a = 10 / 0;
        }catch (Exception e){
            //设置回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

执行结果:数据表没有插入数据
原因:日志表回滚,用户表也回滚了

5.5 嵌套事务和加入事务的区别

由上面代码①和代码②可得:

嵌套事务(NESTED)和加⼊事务(REQUIRED )的区别:

  • 整个事务如果全部执⾏成功,⼆者的结果是⼀样的。
  • 如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;⽽嵌套事务会局部回滚,不会影响上⼀个⽅法中执⾏的结果。

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

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

相关文章

(13) Qt事件系统(two)

目录 事件分发函数 无边框窗口拖动 自定义事件 发送事件的函数 自定义事件 系统定义的事件号 自定义事件号 自定义事件类 发送和处理事件 sendEvent与postEvent的区别 栈区对象 堆区对象 事件传播机制 事件传播的过程 事件传播到父组件 鼠标单击事件与按钮单击信…

【STM32零基础入门教程03】GPIO输入输出之GPIO框图分析

本章节主要讲解点亮LED的基本原理,以及GPIO框图的讲解。 如何点亮LED(输出) 首先我们查看原理图,观察电路图中LED的连接情况,如下图可以看出我们的板子中LED一端通过限流电阻连接的PB0另一端连接的是高电平VCC&#xf…

30. 利用linprog 解决 生产决策问题(matlab程序)

1.简述 线线规划的几个基本性质:【文献[1]第46页】 (1)线性规划问题的可行域如果非空,则是一个凸集-凸多面体; (2)如果线性规划问题有最优解,那么最优解可在可行域的顶点中确定; (3)如果可行域有界,且可行域…

【数据中台】DataX源码进行二开插件

参考官方 使用的离线数据同步工具/平台&#xff0c;实现不同数据库等各种异构数据源之间高效的数据同步功能 工具部署 https://github.com/alibaba/DataX/blob/master/userGuid.md 拉取下来的代码&#xff0c;pom.xml里面注释 <!--<module>tsdbreader</module&g…

大整数截取解决方法(java代码)

大整数截取解决方法&#xff08;java代码&#xff09; 描述输入描述输出描述输入示例输出示例前置知识&#xff1a;代码 解题思路来自这个博客&#xff1a;简单^不简单 https://blog.csdn.net/younger_china/article/details/126376374 描述 花花有一个很珍贵的数字串&#xf…

P4053 [JSOI2007] 建筑抢修(贪心)(内附封面)

[JSOI2007] 建筑抢修 题目描述 小刚在玩 JSOI 提供的一个称之为“建筑抢修”的电脑游戏&#xff1a;经过了一场激烈的战斗&#xff0c;T 部落消灭了所有 Z 部落的入侵者。但是 T 部落的基地里已经有 N N N 个建筑设施受到了严重的损伤&#xff0c;如果不尽快修复的话&#x…

python项目开发案例集锦,python开发程序流程

大家好&#xff0c;给大家分享一下python项目开发案例集锦 源码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 今天任务 1.创建Python项目为pythontest1以及test1.py文件 2.修改字号 3.输入九九乘法表程序&#xff0c;编译调试执行 4.配置…

Python selenium对应的浏览器chromedriver版本不一致

1、chrome和chromedriver版本不一致导致的&#xff0c;我们只需要升级下chromedriver的版本即可 浏览器版本查看 //打开google浏览器直接访问&#xff0c;查看浏览器版本 chrome://version/ 查看chromedriver的版本 //查看驱动版本 chromedriver chromedriver下载 可看到浏…

基于 Debian GNU/Linux 12 “书虫 “的Neptune 8.0 “Juna “来了

导读Neptune Linux 发行版背后的团队发布了 Neptune 8.0&#xff0c;作为这个基于 Debian 的 GNU/Linux 发行版的重大更新&#xff0c;它围绕最新的 KDE Plasma 桌面环境构建。 Neptune 8.0 被命名为 “Juna”&#xff0c;是在Neptune 7.5 发布 11 个月后发布的&#xff0c;也是…

2.1 密码学基础

数据参考&#xff1a;CISP官方 目录 密码学基本概念对称密码算法非对称密码算法哈希函数与数字签名公钥基础设施 一、密码学基本概念 1、密码学形成与发展 发展历程 古典密码学 (1949年之前) 主要特点&#xff1a;数据的安全基于算法的保密 近代密码学 (1949~1975年…

第4章 案例研究:JavaScript图片库

案例 html部分 <h1 id"title">图片1</h1> <ul><li><!-- onclick绑定点击事件&#xff0c;this为触发dom&#xff0c;return false阻止默认行为 --><a onclick"show_img(this); return false" title"图片1" h…

数字信号处理中的基本运算——乘法运算

一、二进制乘法原理 二进制乘法可分为&#xff1a;无符号乘法和有符号乘法 整个相乘过程可分解为一系列的移位、相加操作。 有符号数乘法可分为&#xff1a;&#xff08;1&#xff09;正数*正数&#xff1b;&#xff08;2&#xff09;正数*负数&#xff1b;&#xff08;3&…

申请软件著作权都有什么好处?

随着社会的发展&#xff0c;知识产权保护意识对于公司而言尤为重要&#xff0c;对自己的权利进行最大限度的保护&#xff0c;以防止被别有用心的人侵权。那么&#xff0c;申请软著的好处到底是什么?软著有什么用呢? 无形资产软著是一种无形的知识产权&#xff0c;是开发者智慧…

(常压)室温超导体:The First Room-Temperature Ambient-Pressure Superconductor

2023年7月23日&#xff0c;一支韩国的研究团队声称他们已经成功研制出了一种在室温和常压下的超导体&#xff0c;名为LK-99。这一发现在科学界引起了广泛的关注和讨论。 然而&#xff0c;这项研究的结果也引起了一些科学家的怀疑。有些人对数据的真实性表示了疑虑&#xff0c;认…

【UEC++学习】UE网络 - Replication、RPC

1. UE网络架构 &#xff08;1&#xff09;UE的网络架构是SC&#xff08;Server - Client&#xff09;的模式&#xff0c;这种模式的优势&#xff1a;这种模式让所有客户端都在服务器端进行安全验证&#xff0c;这样可以有效的防止客户端上的作弊问题。 &#xff08;2&#xff…

【编程范式】聊聊什么是数据类型和范式的本质

什么是编程范式 范式其实就是做事的方式&#xff0c;编程范式可以理解为如何编程&#xff0c;按照什么样的模式或者风格进行编程。 编程范式包含哪些 泛型编程函数式编程面向对象编程编程本质和逻辑编程 虽然有不同的编程范式&#xff0c;但是对于目的来说都是为了解决同一…

关于vs下多态虚表中存储的地址和实际成员函数地址不一样的原因

以如下代码为例&#xff1a; class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual…

BES 平台 SDK之LED的配置

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; BES 平台 SDK之代码架构讲解二_谢文浩的博客-CSDN博客 关于SDK 系统框架简介可参考上一篇文章。链接如上所示&#xff01…

学python需要下载什么软件,自学python需要安装什么

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;学python需要安装一些什么软件好&#xff0c;学python需要安装一些什么软件&#xff0c;今天让我们一起来看看吧&#xff01; 工欲善其事必先利其器。初学者在学Python的时候&#xff0c;往往会因为没有好用的软件工具&…

2.5 BUMP图改进

一、Bump Mapping介绍 凹凸贴图映射技术是对物体表面贴图进行变化然后进行光计算的一种技术。例如给法线分量添加噪音&#xff0c;或者在一个保护扰动值的纹理图中进行查找。这是一个提升物理真实感的有效方法&#xff0c;但却不需要额外的提升物体的几何复杂度。这种法式在提…