当前位置: 首页 > news >正文

SpringBoot+Aop+注解方式 实现多数据源动态切换

整体思路:

  1. 引入基本依赖SpringBoot+Aop+MySql+MyBatis+lombok
  2. 在配置文件中配置多个数据源
  3. 创建数据源配置类用于读取配置
  4. 编写用于标识切换数据源的注解
  5. 创建数据源切换工具类DataSourceContextHolder
  6. 编写切面类用于在注解生效处切换数据源
  7. 编写配置类,加载数据源
  8. 创建动态数据源类,并继承AbstractRoutingDataSource,指定使用哪个数据源(关键)

项目demo gitee地址:多数据源动态切换demo

1.引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.10</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.7.10</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.21</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.20</version></dependency>

2.在配置文件中配置多个数据源

这里配置了上海,深圳,北京3个数据源,需要自己创建这3个库multi-sh,multi-sz,multi-bj

#默认数据源
datasource.default=sh
#上海库
spring.datasource.sh.url=jdbc:mysql://localhost:3306/multi-sh?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.sh.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.sh.username=root
spring.datasource.sh.password=123#深圳库
spring.datasource.sz.url=jdbc:mysql://localhost:3306/multi-sz?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.sz.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.sz.username=root
spring.datasource.sz.password=123#北京库
spring.datasource.bj.url=jdbc:mysql://localhost:3306/multi-bj?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.bj.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.bj.username=root
spring.datasource.bj.password=123

3.创建数据源配置类用于读取配置

spring获取统一前缀配置需要可以看我之前的文章:SpringBoot项目获取统一前缀配置以及获取非确定名称配置

package com.gooluke.datasource;import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Map;/*** @author gooluke*/
@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "spring")
public class MultiDataSourceProperties {/*** 这里的datasource是因为配置是spring.datasource.xx.xx,要配置成datasource,这样才会把配置自动映射进来* 分别映射到url、driverClassName、username、password*/private Map<String, DataSourceConfig> datasource;@Setter@Getterpublic static class DataSourceConfig {private String url;private String driverClassName;private String username;private String password;}
}

4.编写用于标识切换数据源的注解

package com.gooluke.common.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author gooluke*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FixedDataSource {String value();/*** 是否需要还原回之前的数据源(拓展)*/boolean needRecover() default false;}

5.创建数据源切换工具类DataSourceContextHolder

package com.gooluke.datasource;import com.gooluke.config.DataSourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author gooluke* 将数据源信息存放至ThreadLocal*/
public class DatasourceContextHolder {private static final Logger log = LoggerFactory.getLogger(DatasourceContextHolder.class);private static final ThreadLocal<String> DATASOURCE_THREAD_LOCAL = new ThreadLocal<>();public static void setDatasource(String datasource) {if (datasource != null && DataSourceConfig.dataSources.get(datasource) == null) {String errorMsg = String.format("数据源[%s]未配置", datasource);log.error(errorMsg);throw new RuntimeException(errorMsg);}DATASOURCE_THREAD_LOCAL.set(datasource);}public static String getDatasource() {return DATASOURCE_THREAD_LOCAL.get();}public static void clearDatasource() {DATASOURCE_THREAD_LOCAL.remove();}
}

6.编写切面类用于在注解生效处切换数据源

package com.gooluke.aspect;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.datasource.DatasourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** @author gooluke* 切换数据源切面类* 这个已不再使用,使用com.gooluke.aop.DataSourceAnnotationAdvisor替代*/
@Aspect
@Component
public class DataSourceAspect {private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);/*** 注解加在方法上*/@Pointcut("@annotation(com.gooluke.common.annotation.FixedDataSource)")private void methodPointCut() {}/*** 注解加在方法上*/@Pointcut("@within(com.gooluke.common.annotation.FixedDataSource)")public void classPointcut() {}@Around(value = "methodPointCut() || classPointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//记录当前数据源,和准备切换的数据源String oldDatasource = DatasourceContextHolder.getDatasource();MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();java.lang.reflect.Method method = methodSignature.getMethod();FixedDataSource annotation = method.getAnnotation(FixedDataSource.class);//方法上获取注解为空,再从类上获取if (annotation == null) {annotation = method.getDeclaringClass().getAnnotation(FixedDataSource.class);}String newDatasource = annotation.value();//切换数据源,并执行操作DatasourceContextHolder.setDatasource(newDatasource);try {return joinPoint.proceed();} finally {//是否切换回初始数据源if (annotation.needRecover()) {DatasourceContextHolder.setDatasource(oldDatasource);}}}
}

7.编写配置类,加载数据源

这个配置类,主要就是将我们配置的多数据源解析然后统一管理,dynamicDataSource.setTargetDataSources(targetDataSources); 以及设置默认数据源。

package com.gooluke.config;import com.alibaba.druid.pool.DruidDataSource;
import com.gooluke.aop.DataSourceAnnotationAdvisor;
import com.gooluke.aop.DataSourceAnnotationInterceptor;
import com.gooluke.datasource.DynamicDataSource;
import com.gooluke.datasource.MultiDataSourceProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 gooluke*/@Configuration
@Slf4j
public class DataSourceConfig {public static final Map<String, String> dataSources = new HashMap<>();@AutowiredMultiDataSourceProperties dataSourceProperties;@Value("${datasource.default:}")private String defaultDataSourceName;@Bean@Primarypublic DynamicDataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();//存放所有数据源Map<Object, Object> targetDataSources = new HashMap<>();Map<String, MultiDataSourceProperties.DataSourceConfig> datasourceMap = dataSourceProperties.getDatasource();if (datasourceMap.entrySet().size() > 1 && (defaultDataSourceName == null || defaultDataSourceName.isEmpty())) {throw new RuntimeException("存在多个数据源,未配置默认数据源:datasource.default");}datasourceMap.forEach((datasourceName, config) -> {DataSource dataSource = createDataSource(config);targetDataSources.put(datasourceName, dataSource);dataSources.put(datasourceName, datasourceName);log.info("已初始化数据库:{}", datasourceName);if (datasourceMap.size() == 1 || (defaultDataSourceName != null && !defaultDataSourceName.isEmpty() && defaultDataSourceName.equals(datasourceName))) {//这里设置默认数据源dynamicDataSource.setDefaultTargetDataSource(dataSource);log.info("已设置默认数据源: {}", datasourceName);}});//这里把数据源统一管理dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}private DataSource createDataSource(MultiDataSourceProperties.DataSourceConfig dataSourceConfig) {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(dataSourceConfig.getUrl());dataSource.setDriverClassName(dataSourceConfig.getDriverClassName());dataSource.setUsername(dataSourceConfig.getUsername());dataSource.setPassword(dataSourceConfig.getPassword());dataSource.setValidationQuery("SELECT 1");dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(false);dataSource.setTestOnReturn(false);dataSource.setPoolPreparedStatements(true);dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);return dataSource;}@Bean@Primarypublic SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dynamicDataSource);PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:/mapper/*.xml");//org.springframework.core.io.Resource config = resolver.getResource("classpath:mybatis-config.xml");sessionFactory.setMapperLocations(resources);//sessionFactory.setConfigLocation(config);return sessionFactory.getObject();}@Bean@Primarypublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {return new DataSourceTransactionManager(dynamicDataSource);}}

8.创建动态数据源类,并继承AbstractRoutingDataSource,指定使用哪个数据源(关键)

这里可以理解为就是一个口子,让我们自己指定数据源,如果你返回的是null,则会指定我们配置类中设置的默认数据源:dynamicDataSource.setDefaultTargetDataSource(dataSource);

package com.gooluke.datasource;import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** @author gooluke* 动态数据源*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 这里返回ThreadLocal中的数据源*/@Overrideprotected Object determineCurrentLookupKey() {return DatasourceContextHolder.getDatasource();}}

9.请求完成后,记得清空ThreadLocal,否则会造成内存泄漏

编写一个拦截器,在请求完成后,remove

package com.gooluke.interceptor;import com.gooluke.datasource.DatasourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author gooluke*/
@Component
@Slf4j
public class DataSourceInterceptor implements HandlerInterceptor {@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {DatasourceContextHolder.clearDatasource();log.info("请求处理完成,清除数据源");}
}

10.代码演示

将注解加在实现类方法上,或者加在mapper/dao接口上(一般加在这里,因为dao接口一般都是操作同一个库,这里指定了,其它别的方法直接调用即可)

10.1 service层:

package com.gooluke.service.impl;import com.gooluke.dao.UserInfoDao;
import com.gooluke.dao.UserInfoDao2;
import com.gooluke.entity.TUserInfo;
import com.gooluke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author gooluke*/
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserInfoDao userInfoDao;@Autowiredprivate UserInfoDao2 userInfoDao2;/*** 在这里没有设置数据源,dao层设置了数据源,可以自动切换*/@Overridepublic List<TUserInfo> selectList() {//先查深圳库,再查上海库List<TUserInfo> tUserInfos = userInfoDao.selectUserList(new TUserInfo());tUserInfos.forEach(System.out::println);List<TUserInfo> tUserInfos2 = userInfoDao2.selectUserList(new TUserInfo());tUserInfos2.forEach(System.out::println);tUserInfos.addAll(tUserInfos2);return tUserInfos;}}

10.2 dao层:

dao1指定深圳库:

package com.gooluke.dao;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.common.constants.DataSourceName;
import com.gooluke.entity.TUserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** @author gooluke*/
@Mapper
@FixedDataSource(DataSourceName.SHENZHEN)
public interface UserInfoDao {List<TUserInfo> selectUserList(TUserInfo userInfo);}

dao2指定上海库:

package com.gooluke.dao;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.common.constants.DataSourceName;
import com.gooluke.entity.TUserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** @author gooluke*/
@Mapper
@FixedDataSource(DataSourceName.SHANGHAI)
public interface UserInfoDao2 {List<TUserInfo> selectUserList(TUserInfo userInfo);}

10.3 观察结果

切库成功,分别查询了不同库的数据,并在最后清空了ThreadLocal中的数据

11.动态数据源(开源)dynamic-datasource-spring-boot-starter

上面这种Aop的实现方式在注解加在service接口的方法上其实是不生效的,当然也不建议加在service接口上,通常是加在实现类类上或者方法上。而Mapper/Dao接口的实现类是通过mybatis动态代理生成的,注解加在Mapper/Dao接口上是能生效的,我没有找到为啥他的实现类可以的文章。而我们也可以通过别的方式,把注解加在接口上的场景通过Aop拦截,只是不建议。下面是开源组件-动态数据源

'com.baomidou:dynamic-datasource-spring-boot-starter:3.3.2'

的Aop方案,有兴趣的可以去看一下他的源码,我的工程里也是用的这种方案,需要在配置类中声明@bean

11.1 创建一个DataSourceAnnotationAdvisor去继承AbstractPointcutAdvisor类,并实现BeanFactoryAware接口

11.2 重写getPointcut()、getAdvice()、setBeanFactory()方法

11.3 配置声明@Bean

    @Beanpublic DataSourceAnnotationAdvisor dataSourceAnnotationAdvisor() {DataSourceAnnotationInterceptor dataSourceAnnotationInterceptor = new DataSourceAnnotationInterceptor();return new DataSourceAnnotationAdvisor(dataSourceAnnotationInterceptor);}

相关文章:

SpringBoot+Aop+注解方式 实现多数据源动态切换

整体思路&#xff1a; 引入基本依赖SpringBootAopMySqlMyBatislombok在配置文件中配置多个数据源创建数据源配置类用于读取配置编写用于标识切换数据源的注解创建数据源切换工具类DataSourceContextHolder编写切面类用于在注解生效处切换数据源编写配置类&#xff0c;加载数据…...

企业如何高效应对多类型知识产权事务的复杂挑战?

随着企业的发展和创新活动的不断推进&#xff0c;越来越多的企业拥有了大量的专利、商标和软著等知识产权&#xff0c;这些不仅关乎企业的技术创新成果&#xff0c;更直接影响到企业的品牌价值和市场竞争力。然而&#xff0c;当企业拥有多件知识产权时&#xff0c;复杂的申请、…...

openeuler22.03 LTS 源码编译安装nginx1.22.1

openeuler22.03 LTS 源码编译安装nginx1.22.1 下载安装包 #官网下载nginx1.22.1 wget http://nginx.org/download/nginx-1.22.1.tar.gz安装依赖包 #安装依赖包&#xff0c;NGINX是C语言写的&#xff0c;pcre-devel支持正则表达式&#xff0c;openssl 开启加密 [rootproxy ~]…...

图片压缩工具免费怎么找?归纳了这几个压缩工具

有哪些图片压缩工具免费&#xff1f;在数字化时代&#xff0c;图像已成为我们生活中不可或缺的一部分。无论是网站设计、社交媒体分享还是文件传输&#xff0c;高质量的图片都扮演着重要的角色。但高质量往往意味着大文件体积&#xff0c;这可能会导致加载速度变慢或存储空间不…...

【Kubernetes知识点】解读HPA的 thrashing(抖动)问题

【Kubernetes知识点】解读HPA的 thrashing&#xff08;抖动&#xff09;问题 目录 1 概念 1.1 什么是 Thrashing 现象&#xff1f;1.2 HPA 中 Thrashing 产生的原因1.3 解决 Thrashing 的优化措施 1.3.1 设置合适的阈值1.3.2 使用自定义指标和基于负载的自动扩缩1.3.3 增加扩…...

Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】

Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】 目录 Unity 设计模式 之 结构型模式 -【装饰者模式】【外观模式】【享元模式】【代理模式】 一、简单介绍 二、装饰者模式&#xff08;Decorator Pattern&#xff09; 1、什么时候使用装…...

Linux上Qt安装相关的内容及在QtCreator使用QChart模块需要的配置

引言 下面是Ubuntu上Qt安装相关的内容及在QtCreator使用QChart模块需要的配置。 关于Qt安装及环境 Qt的模块 查看已经安装的模块 sudo apt search qt5-安装新的模块 sudo apt install qt5-svg # 安装Qt SVG模块3.查看qt已经安装了哪些模块 dpkg -l | grep libqt安装qt,…...

lettuce引起的Redis command timeout异常

项目使用Lettuce&#xff0c;在自己的环境下跑是没有问题的。在给客户做售前压测时&#xff0c;因为客户端环境比较恶劣&#xff0c;service服务和中间件服务不在同一机房。服务启动后不一会就会出现Redis command timeout异常。 经过差不多两周的追查&#xff0c;最后没办法把…...

【Hadoop】一、Hadoop入门:基础配置、集群配置、常用脚本

基础设置 网络设置 创建好一个 centos 虚拟机&#xff0c;修改网络配置文件&#xff1a; /etc/sysconfig/network-scripts/ifcfg-ens33修改 BOOTPROTO 为 static 以及添加 IPADDR、GATEWAY、DNS1 TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY&quo…...

Ollama:本地运行大模型【含UI界面】

文章目录 Ollama 简介安装 ollamaWindows 安装Docker 安装其它平台安装支持的模型模型清单模型参数与运行内存快速启动 llama 模型llama 模型介绍运行 llama3.1 模型通过 HTTP API 访问ollama 命令语法常用示例特别示例自定义模型创建 Modelfile创建模型并运行集成 Web 页面Ope…...

【论文阅读】Grounding Language with Visual Affordances over Unstructured Data

Abstract 最近的研究表明&#xff0c;大型语言模型&#xff08;llms&#xff09;可以应用于将自然语言应用于各种各样的机器人技能。然而&#xff0c;在实践中&#xff0c;学习多任务、语言条件机器人技能通常需要大规模的数据收集和频繁的人为干预来重置环境或帮助纠正当前的…...

目标检测:滑块验证

最近在做一些爬虫相关的任务&#xff0c;有时候在登录时候需要去做滑块验证&#xff0c;刚好自己是做AI这一块得&#xff0c;就想着使用目标检测去做检测&#xff0c;然后绕过滑块。...

Unreal Engine 5 C++: 编辑器工具编写入门01(中文解释)

目录 准备工作 1.创建插件 2.修改插件设置 快速资产操作&#xff08;quick asset action) 自定义编辑器功能 0.创建编辑器button&#xff0c;测试debug message功能 大致流程 详细步骤 1.ctrlF5 launch editor 2.创建新的cpp class&#xff0c;derived from AssetAction…...

力扣上刷题之C语言实现-Day2

一. 简介 本文记录一下&#xff0c;力扣C语言逻辑题。主要涉及 数组方面的知识。 二. 涉及数组的 C语言逻辑题 1. 两数之和 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target…...

Visual Studio 2022 - QT 环境中文字符乱码问题

Visual Studio 2022 - QT 环境中文字符乱码问题 一、Visual Studio 2022 - Qt 环境 在 QT 中使用中文字符串常会出现乱码现象&#xff0c;如下&#xff1a;以下提供了几个解决方法&#xff0c;仅供参考 QString str "百香果真是一直可爱的小猫咪"; qDebug() <…...

获得ASPICE认证需要满足哪些条件?

要获得ASPICE认证&#xff0c;需要满足以下条件&#xff1a; ( 要明确的是&#xff1a;在ASPICE行业中专业来说&#xff0c;ASPICE项目是没有认证&#xff0c;而只有评估。不过&#xff0c;为了方便沟通&#xff0c;人们常将这一评估过程称为认证。&#xff09; 一、基础条件…...

鸿蒙_异步详解

参考详细链接&#xff1a; 鸿蒙HarmonyOS异步并发开发指南...

linux日志查询搜索view

view 命令实际上是 vim 编辑器的一个只读模式。当你使用 view 打开一个文件时&#xff0c;实际上是在用 vim 查看该文件&#xff0c;只是不能编辑内容。因此&#xff0c;view 下的搜索操作与 vim 类似。 以下是如何在 view 模式下进行搜索&#xff1a; 启动 view 并打开文件&a…...

性能测试工具——JMeter

目录 一、JMeter介绍 1、下载安装JMeter 2、打开JMeter 方式一&#xff1a; 方式二&#xff1a; 3、JMeter基础设置 4、JMeter基本使用流程 &#xff08;1&#xff09;启动JMeter &#xff08;2&#xff09;在测试计划下添加线程组 &#xff08;3&#xff09;在 “线…...

1.《DevOps》系列K8S部署CICD流水线之部署K8S集群~version1.28.2

架构 服务器IP服务名称硬件配置192.168.1.100k8s-master8核、16G、120G192.168.1.101k8s-node18核、16G、120G192.168.1.102k8s-node28核、16G、120G192.168.1.103nfs2核、4G、500G 操作系统&#xff1a;Rocky9.3 后续通过K8S部署GitLab、Harbor、Jenkins 一、环境准备 关…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...

【Java多线程从青铜到王者】单例设计模式(八)

wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本&#xff0c;sleep也是可以指定时间的&#xff0c;也就是说时间一到就会解除阻塞&#xff0c;继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒)&#xff0c;wait能被notify提前唤醒&#xf…...