SpringBoot:多数据源配置——注解+AOP

2023-11-20

* maven依赖

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.1.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<!-- 整合freemarker -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-freemarker</artifactId>
	</dependency>

	<!-- log4j -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-log4j</artifactId>
	</dependency>
	<!-- aop -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	<!-- fastJson -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.32</version>
	</dependency>
	<!-- lombok -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
	</dependency>
	<!-- mybatis -->
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>1.1.1</version>
	</dependency>
	<!-- mysql -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

一,多数据源配置——注解+AOP

    前一篇基于拆包配置维度对多数据源配置进行了简单实现。两种方式对比来看,拆包方式规范性更强,而注解方式更加注重灵活性。通过AOP方式,直接反射获取自定义注解,解析注解值进行数据源动态添加,实现多数据源配置。

二,基于AOP配置流程;相对拆包流程比较复杂,先对流程进行梳理,然后按照流程一步步实现

    * Java整体结构

    * 动态多数据源配置

        -- DataSourceConfig

    * 创建线程持有数据库上下文

        -- DynamicDataSourceHolder

    * 基于Spring提供的AbstractRoutingDataSource,动态添加数据源(事务下可能存在问题)

        -- DynamicDataSource

    * 自定义注解,标识数据源

        -- TargetDataSource

    * AOP前后置拦截解析类,对Mapper方法代用进行拦截

        -- DataSourceAspect

    * 三层代码架构处理

        -- DataSourceAOPController,DataSourceAOPService,DataSourceMapper

三,代码变现

0,application.properties

    * 不同于分数据源配置,单一数据源配置,jdbc-url为url

### mapper存储路径_AOP
mybatis.mapper-locations=classpath:com.gupao.springboot.*.mapper/*.xml

### MYSQL_First数据源配置
spring.datasource.first.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.first.jdbc-url=jdbc:mysql://localhost:3306/first?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.first.username=root
spring.datasource.first.password=123456

### MYSQL_First数据源配置
spring.datasource.second.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.second.jdbc-url=jdbc:mysql://localhost:3306/second?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.second.username=root
spring.datasource.second.password=123456

1,动态多数据源配置

package com.gupao.springboot.datasourceaop.config;

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.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
 * 配置数据源
 * @author pj_zhang
 * @create 2018-12-28 12:03
 **/
@Configuration
public class DataSourceConfig {

    /**
     * First数据源
     * @return
     */
    @Bean(name = "firstAopDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.first")
    public DataSource firstDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * Second数据源
     * @return
     */
    @Bean(name = "secondAopDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.second")
    public DataSource secondDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 获取动态数据源
     * @return
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 设置默认数据源为first数据源
        dynamicDataSource.setDefaultTargetDataSource(firstDataSource());
        // 配置多数据源, 
        // 添加数据源标识和DataSource引用到目标源映射
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("firstAopDataSource", firstDataSource());
        dataSourceMap.put("secondAopDataSource", secondDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

}

2,创建线程持有数据库上下文,添加数据源到ThreadLocal中

package com.gupao.springboot.datasourceaop.context;

/**
 * 线程持有数据源上下文
 *
 * @author pj_zhang
 * @create 2018-12-28 12:00
 **/
public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();

    /**
     * 设置线程持有的DataSource, 底层以map形式呈现, key为当前线程
     *
     * @param dataSource
     */
    public static void setDataSource(String dataSource) {
        THREAD_LOCAL.set(dataSource);
    }

    /**
     * 获取线程持有的当前数据源
     *
     * @return
     */
    public static String getDataSource() {
        return THREAD_LOCAL.get();
    }

    /**
     * 清除数据源
     */
    public static void clear() {
        THREAD_LOCAL.remove();
    }

}

3,基于Spring提供的AbstractRoutingDataSource,动态添加数据源(事务下可能存在问题)

package com.gupao.springboot.datasourceaop.config;

import com.gupao.springboot.datasourceaop.context.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。
 * 继承后我们需要实现它的determineCurrentLookupKey(),
 * 该方法用于自定义实际数据源名称的路由选择方法,
 * 由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。
 * @author pj_zhang
 * @create 2018-12-28 12:04
 **/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource  {
    @Override
    protected Object determineCurrentLookupKey() {
        // 直接从ThreadLocal中获取拿到的数据源
        log.info("DynamicDataSource.determineCurrentLookupKey curr data source :" + DynamicDataSourceHolder.getDataSource());
        return DynamicDataSourceHolder.getDataSource();
    }
}

4,自定义注解,标识数据源

package com.gupao.springboot.datasourceaop.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author pj_zhang
 * @create 2018-12-28 12:13
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
    // 数据源名称
    String value() default "";
}

5,AOP前后置拦截解析类,对Mapper方法代用进行拦截

package com.gupao.springboot.datasourceaop.aspect;

import com.gupao.springboot.datasourceaop.annotations.TargetDataSource;
import com.gupao.springboot.datasourceaop.context.DynamicDataSourceHolder;
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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 多数据源配置, 拦截器配置
 * @author pj_zhang
 * @create 2018-12-28 12:15
 **/
@Aspect
@Component
@Slf4j
// 优先级, 1标识最先执行
@Order(1)
public class DataSourceAspect {

    private final String DEFAULT_DATA_SOURCE = "firstAopDataSource";

    @Pointcut("execution(public * com.gupao.springboot.*.mapper.*.*(..))")
    public void dataSourcePoint() {}

    @Before("dataSourcePoint()")
    public void before(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        // 执行方法名
        String methodName = methodSignature.getName();
        // 方法参数
        Class[] parameterTypes = methodSignature.getParameterTypes();
        try {
            // 获取方法, 直接getClass获取对象可能为代理对象
            Method method = target.getClass().getInterfaces()[0].getMethod(methodName, parameterTypes);
            // 添加默认数据源
            String dataSource = DEFAULT_DATA_SOURCE;
            if (null != method && method.isAnnotationPresent(TargetDataSource.class)) {
                TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
                dataSource = targetDataSource.value();
            }
            // 此处添加线程对应的数据源到上下文
            // 在AbstractRoutingDataSource子类中拿到数据源, 加载后进行配置
            DynamicDataSourceHolder.setDataSource(dataSource);
            log.info("generate data source : " + dataSource);
        } catch (Exception e) {
            log.info("error", e);
        }

    }

    /**
     * 清除数据源, 方法执行完成后, 清除数据源
     */
    @After("dataSourcePoint()")
    public void after(JoinPoint joinPoint) {
        DynamicDataSourceHolder.clear();
    }

}

6,Controller层

package com.gupao.springboot.datasourceaop.controller;

import com.gupao.springboot.datasourceaop.service.IDataSourceAOPService;
import com.gupao.springboot.entitys.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author pj_zhang
 * @create 2018-12-28 10:42
 **/
@Slf4j
@RestController
public class DataSourceAOPController {

    @Autowired
    private IDataSourceAOPService dataSourceAOPService;

    @RequestMapping("/firstAOPInsert")
    public Integer firstInsert(String userName, String password) {
        UserVO userVO = new UserVO();
        userVO.setUserName(userName);
        userVO.setPassword(password);
        return dataSourceAOPService.insertFirstUserLst(userVO);
    }

    @RequestMapping("/secondAOPInsert")
    public Integer secondInsert(String userName, String password) {
        UserVO userVO = new UserVO();
        userVO.setUserName(userName);
        userVO.setPassword(password);
        return dataSourceAOPService.insertSecondUserLst(userVO);
    }

    @RequestMapping("/firstAOPSelect")
    public List<UserVO> findFirstData() {
        return dataSourceAOPService.findFirstData();
    }

    @RequestMapping("/secondAOPSelect")
    public List<UserVO> findSecondData() {
        return dataSourceAOPService.findSecondData();
    }

    @RequestMapping("/insertFirstAndSecond")
    public Integer insertFirstAndSecond(String userName, String password) {
        UserVO userVO = new UserVO();
        userVO.setUserName(userName);
        userVO.setPassword(password);
        return dataSourceAOPService.insertFirstAndSecond(userVO);
    }

}

7,Service层

    * Service接口

package com.gupao.springboot.datasourceaop.service;

import com.gupao.springboot.entitys.UserVO;

import java.util.List;

/**
 * @author pj_zhang
 * @create 2018-12-28 10:50
 **/
public interface IDataSourceAOPService {

    /**
     * 新增用户到FIRST
     * @param userVO
     * @return
     */
    Integer insertFirstUserLst(UserVO userVO);

    /**
     * 新增用户到SECEND
     * @param userVO
     * @return
     */
    Integer insertSecondUserLst(UserVO userVO);

    /**
     * 查找数据FIRST
     * @return
     */
    List<UserVO> findFirstData();

    /**
     * 查找数据SECOND
     * @return
     */
    List<UserVO> findSecondData();

    /**
     * 新增数据到FIRST_SECOND
     * @param userVO
     * @return
     */
    Integer insertFirstAndSecond(UserVO userVO);
}

    * Service.Impl

package com.gupao.springboot.datasourceaop.service.impl;

import com.gupao.springboot.datasourceaop.mapper.DataSourceMapper;
import com.gupao.springboot.datasourceaop.service.IDataSourceAOPService;
import com.gupao.springboot.entitys.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author pj_zhang
 * @create 2018-12-28 10:43
 **/
@Slf4j
@Service
public class DataSourceAOPService implements IDataSourceAOPService {

    @Autowired
    private DataSourceMapper dataSourceMapper;

    @Override
    public Integer insertFirstUserLst(UserVO userVO) {
        return dataSourceMapper.insertFirstUser(userVO);
    }

    @Override
    public Integer insertSecondUserLst(UserVO userVO) {
        return dataSourceMapper.insertSecondUser(userVO);
    }

    @Override
    public List<UserVO> findFirstData() {
        return dataSourceMapper.findFirstData();
    }

    @Override
    public List<UserVO> findSecondData() {
        return dataSourceMapper.findSecondData();
    }

    @Override
    public Integer insertFirstAndSecond(UserVO userVO) {
        dataSourceMapper.insertFirstUser(userVO);
        dataSourceMapper.insertSecondUser(userVO);
        return 1;
    }

}

8,Mapper层

package com.gupao.springboot.datasourceaop.mapper;

import com.gupao.springboot.datasourceaop.annotations.TargetDataSource;
import com.gupao.springboot.entitys.UserVO;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * 动态加载数据源
 * value值为DataSource源数据配置map映射的key值
 * @author pj_zhang
 * @create 2018-12-28 10:43
 **/
@Mapper
public interface DataSourceMapper {

    /**
     * 注解为FIRST数据库
     * @param userVO
     * @return
     */
    @TargetDataSource("firstAopDataSource")
    Integer insertFirstUser(UserVO userVO);

    /**
     * 注解为SECOND数据库
     * @param userVO
     * @return
     */
    @TargetDataSource("secondAopDataSource")
    Integer insertSecondUser(UserVO userVO);

    @TargetDataSource("firstAopDataSource")
    List<UserVO> findFirstData();

    @TargetDataSource("secondAopDataSource")
    List<UserVO> findSecondData();
}
<?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.gupao.springboot.datasourceaop.mapper.DataSourceMapper">
    <insert id="insertFirstUser" parameterType="com.gupao.springboot.entitys.UserVO">
        INSERT INTO
          USER_T(USER, PASSWORD)
        VALUES (
          #{userName, jdbcType=VARCHAR},
          #{password, jdbcType=VARCHAR}
        )
    </insert>

    <insert id="insertSecondUser" parameterType="com.gupao.springboot.entitys.UserVO">
        INSERT INTO
          USER_T(USER, PASSWORD)
        VALUES (
          #{userName, jdbcType=VARCHAR},
          #{password, jdbcType=VARCHAR}
        )
    </insert>

    <select id="findFirstData" resultType="com.gupao.springboot.entitys.UserVO">
        SELECT
          user as userName,
          password as password
        from
        USER_T
    </select>

    <select id="findSecondData" resultType="com.gupao.springboot.entitys.UserVO">
        SELECT
        user as userName,
        password as password
        from
        USER_T
    </select>

</mapper>

9,启动入库

package com.gupao.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
// 去除SpringBoot自动配置, 采用自定义数据源配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GupaoSpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(GupaoSpringbootApplication.class, args);
    }

}

10,测试

    * FIRST入库数据

    * 注意两条日志打印顺序;先通过AOP变更了数据源,再通过实现类进行数据源加载,后续同!

    * SECOND入库数据

    * FIRST+SECOND入库数据

    * FIRST查询数据

    * SECOND查询数据

11,数据库数据

    * FIRST

    * SECOND

四,存在问题

1,踩过的一个坑,数据库连接错误,非正常错误

    error :java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone val

    这个问题可能是数据库时区问题导致的,具体解决措施在jdbc-url后面加上参数,如下

    spring.datasource.first.jdbc-url=jdbc:mysql://localhost:3306/first?characterEncoding=utf-8&serverTimezone=GMT%2B8

2,AOP配置引起的数据源加载问题

    * 基于AbstractRoutingDataSource实现类配置的数据源动态加载,依赖于程序的执行顺序。先通过Mapper方法调用变更DynamicDataSourceHolder上下文持有的DataSource再进行数据源加载时,此时配置没有任何问题;

    * 但是单纯添加了声明式事务后,因为事务执行流程影响,AbstractRoutingDataSource实现类会先于Mapper方法执行,此时DynamicDataSourceHolder上下文并不持有数据源,则数据源为DataSourceConfig中配置的默认数据源;

    * 鉴于上面存在的问题,有的配置方式不基于AbstractRoutingDataSource实现类去动态加载数据源,而是在AOP前置拦截方法中,拦截到注解的数据源后,直接从Spring容器中获取DataSource并进行更改,直接跳过执行顺序可能存在的影响,该配置后续会继续完善在后面!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot:多数据源配置——注解+AOP 的相关文章

随机推荐

  • 常用搜索引擎使用技巧

    1 指定站内搜索 使用site指定在某网站内搜索 如只在知乎中搜索 liuwons liuwons site zhihu com 2 精确匹配 使用双引号来指定精确匹配单词或短语 如精确搜索 liuwons liuwons 3 模糊搜索 使
  • 通讯编程001——Nodejs快速开发Modbus TCP Master

    本文介绍如何利用ModbusJs库快速开发Modbus TCP Master 相关源码请登录网信数智 wangxinzhihui com 下载 ModbusJs是一个基于Nodejs的Modbus TCP的开发库 目前支持的功能函数有 re
  • vue-tabel 中使用 el-autocomplete 出现的问题

    必须加 popper append to body false popper class vxetableignoreclear 我自己用的话缺一不可 说一下我自己项目中遇到的问题吧 我写的是表格中套表格 会出现就是当下拉选的时候用 sel
  • 【华为OD统一考试A卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是
  • 正则表达式中的特殊字符

    字符 含意 做为转意 即通常在 后面的字符不按原来意义解释 如 b 匹配字符 b 当b前面加了反斜杆后 b 转意为匹配一个单词的边界 或 对正则表达式功能字符的还原 如 匹配它前面元字符0次或多次 a 将匹配a aa aaa 加了 后 a
  • python自学篇十五[Numpy——基础(一):(jupyter Notebook+Anaconda+conda+jupyter配置及简单操作 ]

    文章目录 概括 Numpy Scipy pandas matplotlib 一 Numpy 基础 1 jupyter Notebook 1 安装Anaconda 2 Anaconda是什么 1 Anaconda Navigator 2 Ju
  • DNS欺骗原理及工作工程分析

    DNS欺骗 DNS欺骗是这样一种中间人攻击形式 它是攻击者冒充域名服务器的一种欺骗行为 它主要用于向主机提供错误DNS信息 当用户尝试浏览网页 例如IP地址为XXX XX XX XX 网址为www bankofamerica com 而实际
  • 工作与身体健康之间的平衡

    大厂裁员 称35岁以后体能下滑 无法继续高效率地完成工作 体重上涨 因为35岁以后新陈代谢开始变慢 甚至坐久了会腰疼 睡眠困扰开始加重 在众多的归因中 仿佛35岁的到来 会为一切的焦虑埋下伏笔 实际上 生理年龄不代表全部 体能素质的下降更与
  • 各种汇编器masm masm32 fasm nasm yasm gas的区别

    原文地址 http www verydemo com demo c269 i661 html masm MASM是微软公司开发的汇编开发环境 拥有可视化的开发界面 使开发人员不必再使用DOS环境进行汇编的开发 编译速度快 支持80x86汇编
  • Debug of AMBA AXI Outstanding Transactions

    Verifying today s complex designs is time consuming as simulations run for long time and millions of transaction are exe
  • Win7(WinDbg) + VMware(Win7) 双机调试环境搭建之三

    更多精彩内容 请见 http www 16boke com 环境 主机 Win7 虚拟机 VMware 11 1 0 build 2496824 虚拟机内操作系统 又称GuestOS Win7 WinDbg 适合调试机的相应位数的版本就可以
  • springboot使用Mybatis-Plus实现分页查询

    1 导入依赖 MyBatis Plus opens new window 简称 MP 是一个 MyBatis opens new window 的增强工具 在 MyBatis 的基础上只做增强不做改变 为简化开发 提高效率而生 我个人感觉使
  • JAVA--GUI(2)--布局

    布局 为了更好适应不同平台而引入的概念 Java的布局管理器是一个实现了LayoutManager接口的实例 用户无法设置setLocation 这些方法 如果想自己设置则需要取消布局管理器 采用布局管理器 边界布局 顺序布局 网格布局 卡
  • BMVC 2022 (东京大学)仅需90K参数!实时完成低光增强, 曝光矫正的超轻量级Transformer网络IAT,已开源

    本文由 52CV 粉丝投稿 作者 信息门下奶狗 知乎地址 https zhuanlan zhihu com p 535695807 我们提出Illumination Adaptive Transformer IAT 网络 用来探索实时的暗光
  • CPU体系架构-ARM/MIPS/X86

    转自 http nieyong github io wiki cpu CPU体系架构 ARM MIPS X86 第一部分 从寄存器 寻址方式 汇编指令等方面总结了ARM MIPS X86的异同 CPU体系架构 RISC和CISC CPU体系
  • efi分区隐藏_win10如何隐藏efi分区

    windows10系统升级最新1703版本后发现制作pe系统的u盘插上电脑后会同时显示可见分区和efi分区 以前的efi隐藏手段统统失效了 目前没找到完美的方法 本文的方法是在自己电脑隐藏efi分区 换别的1703版本win10电脑无效 解
  • 利用OpenCV实现人眼的检测与跟踪

    图像处理开发需求 图像处理接私活挣零花钱 请加微信 QQ 2487872782 图像处理开发资料 图像处理技术交流请加QQ群 群号 271891601 本篇博文的基础是 利用OpenCV的级联分类器类CascadeClassifier和Ha
  • 【电机学】直流电机

    直流电机 什么是直流电机 直流电机的工作原理 直流发电机的工作原理 直流电动机的工作原理 可逆性原理 直流电机的主要结构部件 直流电机的电枢绕组 基本特点 并联支路对数 电刷的放置 一些概念 直流电机的磁场 直流电机的空载磁场 电枢电流Ia
  • Win10 自带【屏幕录制】功能(win + G)----(附带:录屏时没有声音,声音不清楚 问题解决;---提取视频中的音频)

    目录 前言 各种工具的快捷键 以及使用 1 Win V 笔记 2 Win G 进入游戏模式 即 运行Xbox Game Bar 3 Win Tab 虚拟桌面 4 Win Shift S 截屏工具 录制视频时 声音超级不清楚 问题解决 1 w
  • SpringBoot:多数据源配置——注解+AOP

    maven依赖