SpringBoot灵活集成多数据源(定制版)

news2024/11/26 11:53:36
如来说世界,非世界,是名世界

如来说目录,非目录,是名目录

  • 前言
  • 前期准备
  • 代码实现
  • 演示
  • 扩展

前言

本篇博客基于SpringBoot整合MyBatis-plus,如果有不懂这个的,
可以查看我的这篇博客:快速CRUD的秘诀之SpringBoot整合MyBatis-Plus

MyBatis-Plus官方推荐的简单切换数据源,可以参考上期博客:SpringBoot快速集成多数据源(自动版)

那么问题来了?我上期博客已经实现过一次多数据源切换了,并且非常简单,为什么还要出一期定制版呢?

是这样的,在上一期博客中,通过@DS注解在不同的方法上,然后再通过调用不同的方法来实现调用不同的数据源:
在这里插入图片描述
那么,如果有8个数据源呢?是不是要写8个方法,8个判断呢?

那,如果再有8个业务需要切换数据源呢?想想头就大了
在这里插入图片描述

所以,到底有没有方法能够简化代码呢?反正切换大概都是一样的逻辑。
在这里插入图片描述

本博主灵光一现,这篇博客也就应运而生了!

当然,如果已经规定好了哪些业务用哪些数据源,不存在同一个业务要根据不同的用户来切换的烦恼,您就用官方推荐的方式吧,甭折腾了!

前期准备

跟上期同样,为了实现效果,先在本地的mysql库里面创建两个数据库:
在这里插入图片描述
然后在两个数据库里面,分别创建同样的users表,但是插入不同的数据,
mydb的数据:
在这里插入图片描述
mydb2的数据:
在这里插入图片描述

代码实现

1.在pom.xml文件中引入AOP所需的依赖aspectjweaver

<!-- 使用AOP来增强对象 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

2.在application.yml中进行配置:

server:
  port: 8080

# 配置数据源
spring:
  datasource:
    master:
      # 数据库路径jdbc:mysql://localhost:3306/mydb 的缩写,并配置时区
      jdbc-url: jdbc:mysql:///mydb?serverTimezone=GMT%2B8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
    slave:
      jdbc-url: jdbc:mysql:///mydb2?serverTimezone=GMT%2B8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver


# 打印MyBatis SQL 日志
logging:
  level:
    com.guqueyue.dynamicdatasourcetest.dao: debug # 写接口的包名

这里需要注意的是数据源的路径名不能用url,要用jdbc-url!!!

3.编写DataSourceType类,用以设置数据源:

package com.guqueyue.dynamicdatasourcetest.config;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author: guqueyue
 * @Description: 用以设置数据源
 * @Date: 2023/12/25
 **/
@Slf4j
public class DataSourceType {

    // 内部枚举类,用以选择特定的数据源
    public enum DataBaseType {
        master,
        slave
    }

    // 使用ThreadLocal保证线程安全
    private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<>();

    // 往当前线程中设置数据源类型
    public static void setDataBaseType(DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }

        TYPE.set(dataBaseType);
        log.info("\033[31;0;42;30;1;2;3;4;41m" + "线程:[" + Thread.currentThread().getName() +"],取了[" + getDataBaseType() + "]=>这个数据源");
    }

    // 获取数据源类型
    public static DataBaseType getDataBaseType() {
        return TYPE.get() == null ? DataBaseType.master : TYPE.get();
    }

    // 清空数据源类型
    public static void clearDataBaseType() {
        TYPE.remove();
    }
}

4.扩展determineCurrentLookupKey()方法来实现数据源的切换:

package com.guqueyue.dynamicdatasourcetest.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @Author: guqueyue
 * @Description: 通过扩展determineCurrentLookupKey()方法来实现数据源的切换
 * @Date: 2023/12/25
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceType.getDataBaseType();
    }
}

5.重头戏,编写多数据源配置类DynamicDataSourceConfig:

package com.guqueyue.dynamicdatasourcetest.config;

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: guqueyue
 * @Description: 多数据源配置类
 * @Date: 2023/12/25
 **/
@Configuration
@MapperScan(basePackages = "com.guqueyue.dynamicdatasourcetest.dao", sqlSessionFactoryRef = "SqlSessionFactory") // basePackages为接口地址
public class DynamicDataSourceConfig {

    // 将这个对象放入Spring容器中
    @Bean(name = "masterDataSource")
    // 表示这个数据源为默认数据源
    @Primary
    // 读取 application.yml 中的配置参数映射成为一个对象
    // prefix表示配置文件中参数的前缀
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource getMasterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource getSlaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) {

        // 核心点: targetDataSource为 数据源和注入的Bean 之间的映射
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.DataBaseType.master, masterDataSource);
        targetDataSource.put(DataSourceType.DataBaseType.slave, slaveDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(masterDataSource); // 设置默认数据源
        return dataSource;
    }

    @Bean(name = "SqlSessionFactory")
    @DependsOn("globalConfig") // 明确调用顺序,在调用本方法前先调用 globalConfig() 方法
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {

//        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);

        // 设置xml文件路径
//        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/guqueyue/dynamicdatasourcetest/dao/*.xml"));
        // 设置别名的扫描 - 实体类的包名引用
        bean.setTypeAliasesPackage("com.guqueyue.dynamicdatasourcetest.entity");
        bean.setGlobalConfig(globalConfig());
        return bean.getObject();
    }

    /**
     * @Description 解决Oracle数据库下,用 @KeySequence注解 实现主键序列自增失效 问题解决
     * @Param []
     * @return com.baomidou.mybatisplus.core.config.GlobalConfig
     **/
    @Bean
    public GlobalConfig globalConfig() {
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerator(new OracleKeyGenerator()));
        return globalConfig;
    }

    @Bean(name = "sqlSessionTemplate")
    @Primary // 首选
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * @Description 事务管理
     * @Param [dataSource]
     * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
     **/
    @Bean(name = "dataSourceTransactionManager")
    @Primary // 首选
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

6.编写自定义注解

关于自定义注解的了解,可以参考我的这篇博客:框架的灵魂之注解基础篇

package com.guqueyue.dynamicdatasourcetest.annotation;

import java.lang.annotation.*;

/**
 * @Author: guqueyue
 * @Description: 自定义注解,用以切换数据源
 * @Date: 2023/12/25
 **/
@Target({ElementType.METHOD}) // 注解使用范围为:方法
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期为运行时
@Documented
public @interface MyDataSource {

//    DataSourceType.DataBaseType value() default DataSourceType.DataBaseType.master; // 默认使用master库
}

7.进行自定义注解拦截,使用AOP来增强对象:
这里有一个核心逻辑,我们根据@MyDataSource 自定义注解拦截方法,然后根据方法所传的第一个参数值来判断使用哪个数据源。
这样我们就不用编写很多方法了。当然,如果没找到,就默认为主数据源。

package com.guqueyue.dynamicdatasourcetest.aop;

import com.guqueyue.dynamicdatasourcetest.annotation.MyDataSource;
import com.guqueyue.dynamicdatasourcetest.config.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author: guqueyue
 * @Description: 自定义注解拦截,使用AOP来增强对象
 * @Date: 2023/12/25
 **/
@Slf4j
@Aspect
@Component
public class DynamicDataSourceAspect {

    // 拦截自定义注解, @annotation()里面是注解的路径,需要根据你自己的替换
    @Before("@annotation(com.guqueyue.dynamicdatasourcetest.annotation.MyDataSource)")
    public void changeDataSource(JoinPoint point) {

//        // 获取增强方法
//        MethodSignature methodSignature = (MethodSignature) point.getSignature();
//        // 获取增强方法的反射对象
//        Method method = methodSignature.getMethod();
//        // 获取@MyDataSource注解
//        MyDataSource myDataSource = method.getAnnotation(MyDataSource.class);

        String dataSourceType = "master";
        // 获取方法的第一个参数
        Object[] args = point.getArgs();
        if (args != null && args.length > 0) {
            dataSourceType = args[0].toString();
        }

        // 选择数据源
        switch (dataSourceType) {
            case "slave":
                DataSourceType.setDataBaseType(DataSourceType.DataBaseType.slave);
                break;
            default: // 默认为主数据源
                DataSourceType.setDataBaseType(DataSourceType.DataBaseType.master);
                break;
        }
    }

    // 在方法执行完之后清除特定数据源的配置,使用默认数据源
    @After("@annotation(myDataSource)")
    public void restoreDataSource(JoinPoint point, MyDataSource myDataSource) {
        DataSourceType.clearDataBaseType();
    }
}

总体实现下来的项目结构为:
在这里插入图片描述

演示

1.控制层代码

package com.guqueyue.dynamicdatasourcetest.controller;

import com.guqueyue.dynamicdatasourcetest.entity.User;
import com.guqueyue.dynamicdatasourcetest.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: guqueyue
 * @Description: 用户控制层
 * @Date: 2023/12/25
 **/
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;

    /**
     * 查询用户列表
     * @return
     */
    @RequestMapping("/list")
    public List<User> userList(String type) {
        return userService.selectUserList(type);
    }
}

2.service接口

package com.guqueyue.dynamicdatasourcetest.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.guqueyue.dynamicdatasourcetest.entity.User;

import java.util.List;

/**
 * @Author: guqueyue
 * @Description: 用户service接口
 * @Date: 2023/12/25
 **/
public interface IUserService extends IService<User> {


    List<User> selectUserList(String type);
}

3.service实现类

package com.guqueyue.dynamicdatasourcetest.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guqueyue.dynamicdatasourcetest.annotation.MyDataSource;
import com.guqueyue.dynamicdatasourcetest.dao.UserMapper;
import com.guqueyue.dynamicdatasourcetest.entity.User;
import com.guqueyue.dynamicdatasourcetest.service.IUserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: guqueyue
 * @Description: 用户实现类
 * @Date: 2023/12/25
 **/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public List<User> selectUserList(String type) {

        // do something: 此处可根据实际情况进行业务选择,如根据当前登录的用户信息来选择不同的数据库
        // 此处为了方便演示,直接用前端传入的type来判断

        return userMapper.selectUserList(type);
    }
}

4.dao层

package com.guqueyue.dynamicdatasourcetest.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.guqueyue.dynamicdatasourcetest.annotation.MyDataSource;
import com.guqueyue.dynamicdatasourcetest.entity.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @Author: guqueyue
 * @Description: 映射接口UserMapper
 * @Date: 2023/12/25
 **/
public interface UserMapper extends BaseMapper<User> {

    @MyDataSource
    @Select("SELECT id,password,username FROM users")
    List<User> selectUserList(String type);
}

5.启动项目,看效果

根据之前自定义注解拦截时编写的逻辑,我们会根据selectUserList()方法中的第一个参数type来决定取哪个数据源。

话不多说,项目,启动!
在这里插入图片描述

在控制台看到tomcat在你配置的端口号上启动了,说明项目启动成功了!

Tomcat started on port(s): 8080 (http) with context path ''

在浏览器输入:http://localhost:8080/user/list?type=master
可以看到控制台打印:
在这里插入图片描述
浏览器返回:
在这里插入图片描述
再在浏览器输入:http://localhost:8080/user/list?type=slave
可以看到控制台打印:
在这里插入图片描述
浏览器返回:
在这里插入图片描述
说明成功切换了不同的数据源!

扩展

本文在演示的时候,在dao层使用的是@Select注解来执行SQL语句,并没有使用xml文件。

如需使用xml文件执行SQL语句的话,

可以参照我的这篇博客:快速CRUD的秘诀之SpringBoot整合MyBatis-Plus 的扩展部分。

但不同的是,需要在多数据源配置类DynamicDataSourceConfigsqlSessionFactory()方法中进行配置,而不是applicaton.yml配置文件中:

 @Bean(name = "SqlSessionFactory")
    @DependsOn("globalConfig") // 明确调用顺序,在调用本方法前先调用 globalConfig() 方法
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {

//        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);

        // 设置xml文件路径
//        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/guqueyue/dynamicdatasourcetest/dao/*.xml"));
        // 设置别名的扫描 - 实体类的包名引用
        bean.setTypeAliasesPackage("com.guqueyue.dynamicdatasourcetest.entity");
        bean.setGlobalConfig(globalConfig());
        return bean.getObject();
    }

将这两行代码根据你的实际情况修改即可:

 // 设置xml文件路径
//        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/guqueyue/dynamicdatasourcetest/dao/*.xml"));
        // 设置别名的扫描 - 实体类的包名引用
        bean.setTypeAliasesPackage("com.guqueyue.dynamicdatasourcetest.entity");

我们下期博客再见!

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

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

相关文章

云计算复习提纲

第一章 大数据的概念&#xff1a;海量数据的规模巨大到无法通过目前主流的计算机系统在合理时间内获取、存储、管理、处理并提炼以帮助使用者决策 大数据的特点&#xff1a;①数据量大&#xff0c;存储的数据量巨大&#xff0c;PB级别是常态&#xff1b;②多样&#xff0c;数…

SpringBoot自动配置原理和自定义启动器

1、自动配置的原理 项目在加载上下文时&#xff0c;会根据SpringBootApplication注解运行。该注解中有一个CompoentScan注解&#xff0c;会扫描和加载当前启动类所在的目录&#xff0c;以及所有的子目录&#xff1b;还有一个是EnableAutoConfiguration注解&#xff0c;这个注解…

nginx 多端口部署多站点

目录 1.进行nginx.conf 2.复制粘贴 3.修改端口及站点根目录 4. 网站上传 1.进行nginx.conf 在 nginx 主要配置文件 nginx.conf 中&#xff0c;server 是负责一个网站配置的&#xff0c;我们想要多个端口访问的话&#xff0c;可以复制多个 server 先进入到 nginx.conf 中 …

鸿蒙 DevEco Studio 3.1 入门指南

本文主要记录开发者入门&#xff0c;从软件安装到项目运行&#xff0c;以及后续的学习 1&#xff0c;配置开发环境 1.1 下载安装包 官网下载链接 点击立即下载找到对应版版本 下载完成&#xff0c;按照提示默认安装即可 1.2 下载SDK及工具链 运行已安装的DevEco Studio&…

Rust学习笔记005:结构体 struct

在 Rust 中&#xff0c;struct 是一种用于创建自定义数据类型的关键字&#xff0c;它允许你定义和组织数据的结构。struct 可以包含多个不同类型的字段&#xff08;fields&#xff09;&#xff0c;每个字段都有一个名称和一个类型。 定义结构体 下面是一个简单的例子&#xff…

【我与CSDN的128天】相识相知相守

目录: 相识相知相守 相识 为什么选择写博客? 写博客的目的,我觉得是因为想要记录。记录学习的过程,整理学过的知识,方便今后的复习。 更重要的是热爱分享,分享给别人知识也是一种快乐。 在某一瞬间教会某一个你不认识的人,难道不是一个很酷的事情吗? 为什么选择CSDN? 作…

系列四、Eureka自我保护

一、Eureka自我保护 1.1、故障现象 保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式&#xff0c;Eureka Server将会尝试保护其服务注册表中的信息&#xff0c;不再删除服务注册表中的数据&#xff0c;也就是不会注销任何微服务。如…

PACC:数据中心网络的主动 CNP 生成方案

PACC&#xff1a;数据中心网络的主动 CNP 生成方案 文章目录 PACC&#xff1a;数据中心网络的主动 CNP 生成方案PACC算法CNP数据结构PACC参数仿真结果参考文献 PACC算法 CNP数据结构 PACC参数 仿真结果 PACC Hadoop Load0.2 的情况&#xff1a; PACC Hadoop Load0.4 的情况&a…

2023海内外零知识证明学习资料汇总(一)(故事中的零知识证明篇)

工欲善其事,必先利其器 Web3开发中&#xff0c;各种工具、教程、社区、语言框架.。。。 种类繁多&#xff0c;是否有一个包罗万象的工具专注与Web3开发和相关资讯能毕其功于一役&#xff1f; 参见另一篇博文&#x1f449; 2024最全面且有知识深度的web3开发工具、web3学习项目…

Head First Design Patterns - 装饰者模式

什么是装饰者模式 装饰者模式动态地将额外责任附加到对象上。对于拓展功能&#xff0c;装饰者提供子类化的弹性替代方案。 --《Head First Design Patterns》中的定义 为什么会有装饰者模式 根据上述定义&#xff0c;简单来说&#xff0c;装饰者模式就是对原有的类&#xff0c…

将产品手册与数字营销结合已经是一种大趋势了!

在数字化时代&#xff0c;产品手册已经不再是单一的纸质文档&#xff0c;而是成为了与数字营销策略紧密相连的重要工具。通过巧妙地将产品手册融入数字营销战略中&#xff0c;企业不仅可以更有效地推广产品&#xff0c;还能提高用户转化率&#xff0c;从而增加销售额。 | 一、产…

计算机科学速成课【学习笔记】(1)——计算机早期历史

本集课程B站链接&#xff1a; 【计算机科学速成课】[40集全/精校] - Crash Course Computer Science_哔哩哔哩_bilibili【计算机科学速成课】[40集全/精校] - Crash Course Computer Science共计40条视频&#xff0c;包括&#xff1a;1. 计算机早期历史-Early Computing、2. 电…

Vue 插槽:让你的组件更具扩展性(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Topics(动态路由)

Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列中。只不过Topic类型Exchange可以让队列在绑定路由时可以使用通配符。 *&#xff1a;匹配不多不少刚好一个单词。 #&#xff1a;匹配一个或多个词。 举例&#xff1a; audit.#可以匹配…

读算法霸权笔记09_信用数据的陷阱

1. 信用评级模型 1.1. 评估个人贷款违约风险的模型为FICO 1.1.1. 唯一评分参数就是贷款者的资产&#xff0c;主要依据是贷款者的债务负担和账单支付记录 1.1.2. 这种信用评分模型相对透明 1.1.3. 信用评分行业受政府管制 1.1.4. 信用评分系统的使用得到了广泛普及 1.2. 脸…

lv14 注册字符设备 3

1 注册字符设备 1.1 结构体介绍 struct cdev {struct kobject kobj;//表示该类型实体是一种内核对象struct module *owner;//填THIS_MODULE&#xff0c;表示该字符设备从属于哪个内核模块const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址str…

GRU算法

前置知识&#xff1a;RNN&#xff0c;LSTM LSTM需要训练的参数很多&#xff0c;极消耗计算资源。GRU是一种LSTM的改进算法&#xff0c;参数更少&#xff0c;更容易训练。 它将忘记门和输入门合并成为一个单一的更新门&#xff0c;同时合并了数据单元状态和隐藏状态&#xff0…

win10安装虚拟机

一、下载virualbox https://www.virtualbox.org/wiki/Downloads&#xff0c;要开启cpu虚拟化&#xff0c;无脑安装 二、下载vargrant https://www.vagrantup.com/&#xff0c;无脑安装 下载完重启电脑&#xff0c;在命令窗口输入vagrant有提示则安装成功 通过查询vagrant的…

租房数据分析可视化大屏+58同城 Django框架 大数据毕业设计(附源码)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

STM32CubeMX教程14 ADC - 多通道DMA转换

目录 1、准备材料 2、实验目标 3、实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.1、外设初始化调用流程 3.2.2、外设中断调用流程 3.2.3、添加其他必要代码 4、常用函数 5、烧录验…