二级网站怎么建,wordpress旧版本,网页设计与制作课程建设规划方案,国家关于网站信息建设管理文件背景 随着公司业务战略的发展#xff0c;相关的软件服务也逐步的向多元化转变#xff0c;之前是单纯的拿项目#xff0c;赚人工钱#xff0c;现在开始向产品化\服务化转变。最近雷袭又接到一项新的挑战#xff1a;了解SAAS模型#xff0c;考虑怎么将公司的产品转换成多租… 背景 随着公司业务战略的发展相关的软件服务也逐步的向多元化转变之前是单纯的拿项目赚人工钱现在开始向产品化\服务化转变。最近雷袭又接到一项新的挑战了解SAAS模型考虑怎么将公司的产品转换成多租户架构。 经过一番百度雷袭对多租户架构总算有了一番了解以下是整理的笔记。 多租户架构是一种软件架构用于实现多用户环境下使用相同的系统或程序组件时能保证用户之间数据的隔离性。简单说就是使用共用的数据中心通过单一系统架构与服务提供多数客户端相同甚至可定制化的服务并且保障客户的数据隔离。一个支持多租户架构的系统需要在设计上对它的数据和配置进行虚拟分区从而使系统的每个租户或组织都能够使用一个单独的系统实例每个租户都可以根据自己的需求对租用的系统实例进行个性化配置。 多租户技术的实现重点在于不同租户间应用程序环境的隔离以及数据的隔离使得不同租户间应用程序不会相互干扰。应用程序可通过进程隔离或者多种运维工具实现数据存储上的隔离方案则是有三种 1、独立数据库优点是独立性最高缺点是数据库较多购置和维护成本高。 2、共享数据库隔离数据架构同一个数据库实例内多个用户/schema来对应多个租户优点是单实例可以支持更多租户缺点是数据恢复比较困难。 3、共享数据库共享数据结构物理分表表分区或者在表中通过字段区分优点是成本最低实现难度低缺点是数据隔离程度低。 第三种其实雷袭已经试过了之前的博客里就提到了表分区分表的实现方式这里不多缀述今天雷袭想试试前面两种因此不得不解决的一个问题如何实现同一个项目中数据源的动态切换 代码实践 雷袭在网上查阅了很多资料最终找到了两种合适的方式实现一种是通过AOP来实现另一种是通过Filter实现以下是实现的方式说明。 一、通过切面实现 1、准备工作创建数据库模式添加测试数据
--先创建三个用户设置密码
SAAS_MASTER leixi123
SAAS_DEV leixi123
SAAS_UAT leixi123--再用sysdba给用户授权
grant dba to SAAS_MASTER;
grant resource to SAAS_MASTER;
grant dba to SAAS_DEV;
grant resource to SAAS_DEV;
grant dba to SAAS_UAT;
grant resource to SAAS_UAT;CREATE TABLE SAAS_MASTER.sys_db_info
(id VARCHAR2(32) NOT NULL,url VARCHAR2(255) NOT NULL,username VARCHAR2(255) NOT NULL,password VARCHAR2(255) NOT NULL,driver_class_name VARCHAR2(255) NOT NULL,db_name VARCHAR2(255) NOT NULL,db_key VARCHAR2(255) NOT NULL,status INT DEFAULT 0 NOT NULL,remark VARCHAR2(255) DEFAULT NULL,PRIMARY KEY(id)) ;COMMENT ON TABLE SAAS_MASTER.sys_db_info IS 数据库配置信息表;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.db_key IS 数据库key即保存Map中的key(保证唯一并且和DataSourceType中的枚举项保持一致包括大小写);
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.db_name IS 数据库名称;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.driver_class_name IS 数据库驱动;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.id IS 主键ID;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.password IS 密码;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.remark IS 备注说明;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.status IS 是否停用;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.url IS 数据库URL;
COMMENT ON COLUMN SAAS_MASTER.sys_db_info.username IS 用户名;--添加数据源信息
insert into SAAS_MASTER.sys_db_info (id,url,username,password,driver_class_name,db_name,db_key,status,remark)
values (1, jdbc:dm://127.0.0.1:5236/SAAS_DEV, SAAS_DEV, leixi123, dm.jdbc.driver.DmDriver, DEV, DEV, 0, 连接DEV数据库);
insert into SAAS_MASTER.sys_db_info (id,url,username,password,driver_class_name,db_name,db_key,status,remark)
values (2, jdbc:dm://127.0.0.1:5236/SAAS_UAT, SAAS_UAT, leixi123, dm.jdbc.driver.DmDriver, UAT, UAT, 0, 连接UAT数据库);--添加测试数据库
CREATE TABLE SAAS_MASTER.leixi_test (id VARCHAR2(32) NOT NULL,name VARCHAR2(255) NOT NULL,PRIMARY KEY (id)
) ;CREATE TABLE SAAS_DEV.leixi_test (id VARCHAR2(32) NOT NULL,name VARCHAR2(255) NOT NULL,PRIMARY KEY (id)
) ;CREATE TABLE SAAS_UAT.leixi_test (id VARCHAR2(32) NOT NULL,name VARCHAR2(255) NOT NULL,PRIMARY KEY (id)
) ;insert into SAAS_MASTER.leixi_test(id, name) values(, 这里是leixi_test 的MASTER库数据);
insert into SAAS_DEV.leixi_test(id, name) values(1, 这里是leixi_test 的DEV库数据);
insert into SAAS_UAT.leixi_test(id, name) values(1, 这里是leixi_test 的UAT数据); 2、创建一个springboot项目项目环境为JDK17以下是相关配置和代码 pom.xml
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionpackagingjar/packagingparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.3.2/version !-- lookup parent from repository --/parentgroupIdcom.leixi.hub.saasdb/groupIdartifactIdleixi-saas-db/artifactIdversion1.0-SNAPSHOT/versionnameleixi-saas-db/namedescription用于动态切换数据源/descriptionpropertiesmaven.compiler.source17/maven.compiler.sourcemaven.compiler.target17/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncodinghutool.version5.8.15/hutool.versionmysql.version8.0.28/mysql.versiondruid.version1.2.16/druid.versionmybatis-plus.version3.5.3.1/mybatis-plus.versionlombok-mapstruct-binding.version0.2.0/lombok-mapstruct-binding.version/propertiesdependencies!-- Lombok --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId!--编译测试环境不打包在lib--scopeprovided/scope/dependency!-- 允许使用Lombok的Java Bean类中使用MapStruct注解 (Lombok 1.18.20) --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok-mapstruct-binding/artifactIdversion${lombok-mapstruct-binding.version}/versionscopeprovided/scope/dependency!-- hutool工具包 --dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion${hutool.version}/version/dependency!-- web支持 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- aop --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency!-- DM驱动 --dependencygroupIdcom.dameng/groupIdartifactIdDmJdbcDriver18/artifactIdversion8.1.1.193/version/dependency!-- 阿里druid工具包 --dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion${druid.version}/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.40/version/dependency!-- mybatis-plus --dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-spring-boot3-starter/artifactIdversion3.5.5/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-archetype-plugin/artifactIdversion3.0.0/version/plugin/plugins/build/projectapplication.yml
server:port: 19200servlet:context-path: /leixispring:jackson:## 默认序列化时间格式date-format: yyyy-MM-dd HH:mm:ss## 默认序列化时区time-zone: GMT8datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: dm.jdbc.driver.DmDriverurl: jdbc:dm://127.0.0.1:5236/SAAS_MASTERusername: SAAS_MASTERpassword: leixi123druid:slave: falseinitial-size: 15min-idle: 15max-active: 200max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: test-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: falseconnection-properties: falsetask:execution:thread-pool:core-size: 10max-size: 20queue-capacity: 100
mybatis-plus:mapper-locations: classpath:/mapper/**/*Mapper.xmlglobal-config:db-config:#主键类型 0:数据库ID自增,1:该类型为未设置主键类型, 2:用户输入ID,3:全局唯一ID (数字类型唯一ID), 4:全局唯一ID UUID;id-type: assign_uuid# 默认数据库表下划线命名table-underline: trueconfiguration:# 返回类型为Map,显示null对应的字段call-setters-on-nulls: truemap-underscore-to-camel-case: true #开启驼峰和下划线互转# 这个配置会将执行的sql打印出来在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
leixi:saas:data_source_key: data_source_keyload_source_form_db: true以下是设置数据源的核心代码其原理为在项目启动时先通过LoadDataSourceRunner从数据库中查询相关的数据连接存储在内存中对Controller中的方法添加DataSource注解执行方法时通过注解中的静态枚举切换对应的数据源对指定的数据库进行操作。
package com.leixi.hub.saasdb.config;import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.leixi.hub.saasdb.entity.SysDbInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** 实现动态数据源根据AbstractRoutingDataSource路由到不同数据源中** author 雷袭月启* since 2024/12/5 19:39*/
Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {// 数据源列表多数据源情况下具体使用哪一个数据源由此获取private final MapObject, Object targetDataSourceMap;/*** 构造方法设置默认数据源和目标多数据源** param defaultDataSource 默认主数据源只能有一个* param targetDataSources 从数据源可以是多个*/public DynamicDataSource(DataSource defaultDataSource, MapObject, Object targetDataSources) {super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSources);this.targetDataSourceMap targetDataSources;}/*** 动态数据源的切换核心* 决定使用哪个数据源** return Object*/Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSource();}/*** 添加数据源信息** param dataSources 数据源实体集合*/public void createDataSource(ListSysDbInfo dataSources) {try {if (CollectionUtils.isNotEmpty(dataSources)) {for (SysDbInfo ds : dataSources) {//校验数据库是否可以连接Class.forName(ds.getDriverClassName());DriverManager.getConnection(ds.getUrl(), ds.getUsername(), ds.getPassword());//定义数据源DruidDataSource dataSource new DruidDataSource();BeanUtils.copyProperties(ds, dataSource);//申请连接时执行validationQuery检测连接是否有效这里建议配置为TRUE防止取到的连接不可用dataSource.setTestOnBorrow(true);//建议配置为true不影响性能并且保证安全性。//申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。dataSource.setTestWhileIdle(true);//用来检测连接是否有效的sql要求是一个查询语句。dataSource.setValidationQuery(select 1 );dataSource.init();// 将数据源放入Map中key为数据源名称要和DataSourceType中的枚举项对应包括大小写并且保证唯一this.targetDataSourceMap.put(ds.getDbKey(), dataSource);}// 更新数据源配置列表这里主要是从数据源super.setTargetDataSources(this.targetDataSourceMap);// 将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();}} catch (ClassNotFoundException | SQLException e) {log.error(---解析数据源出错---{}, e.getMessage());}}/*** 校验数据源是否存在** param key 数据源保存的key* return 返回结果true存在false不存在*/public boolean existsDataSource(String key) {return Objects.nonNull(this.targetDataSourceMap) Objects.nonNull(this.targetDataSourceMap.get(key));}
}package com.leixi.hub.saasdb.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 设置数据源** author 雷袭月启* since 2024/12/5 19:39*/
Configuration
public class DataSourceConfig {private static final String MASTER_SOURCE_KEY MASTER;/*** 配置主数据源默认使用该数据源并且主数据源只能配置一个** return DataSource* description 该数据源是在application配置文件master中所配置的*/BeanConfigurationProperties(spring.datasource)public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}/*** 配置动态数据源的核心配置项** return DynamicDataSource*/PrimaryBean(name dynamicDataSource)public DynamicDataSource createDynamicDataSource() {MapObject, Object dataSourceMap new HashMap();// 默认的数据源主数据源DataSource defaultDataSource masterDataSource();// 配置主数据源默认使用该数据源并且主数据源只能配置一个dataSourceMap.put(MASTER_SOURCE_KEY, defaultDataSource);// 配置动态数据源默认使用主数据源如果有从数据源配则使用从数据库中读取源并加载到dataSourceMap中return new DynamicDataSource(defaultDataSource, dataSourceMap);}
}package com.leixi.hub.saasdb.config;
/*** 动态数据源类型** author 雷袭月启* since 2024/12/5 19:39*/
public enum DataSourceType {// 注意枚举项要和 DataSourceConfig 中的 createDynamicDataSource()方法dataSourceMap的key保持一致/*** 主库*/MASTER,/*** 从库*/UAT,
}package com.leixi.hub.saasdb.config;import lombok.extern.slf4j.Slf4j;/*** 创建一个类用于实现ThreadLocal主要是通过getsetremove方法来获取、设置、删除当前线程对应的数据源。** author 雷袭月启* since 2024/12/5 19:39*/
Slf4j
public class DynamicDataSourceContextHolder {//此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。private static final ThreadLocalString DATASOURCE_HOLDER new ThreadLocal();/*** 设置数据源** param dataSourceName 数据源名称*/public static void setDataSource(String dataSourceName) {log.info(切换数据源到{}, dataSourceName);DATASOURCE_HOLDER.set(dataSourceName);}/*** 获取当前线程的数据源** return 数据源名称*/public static String getDataSource() {return DATASOURCE_HOLDER.get();}/*** 删除当前数据源*/public static void removeDataSource() {log.info(删除当前数据源{}, getDataSource());DATASOURCE_HOLDER.remove();}
}package com.leixi.hub.saasdb.config;import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.leixi.hub.saasdb.dao.SysDbInfoMapper;
import com.leixi.hub.saasdb.entity.SysDbInfo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.List;/*** CommandLineRunner 项目启动时执行** author 雷袭月启* since 2024/12/5 19:39*/
Slf4j
Component
public class LoadDataSourceRunner implements CommandLineRunner {/*** 是否启用从库多数据源配置*/Value(${leixi.saas.load_source_form_db:false})private boolean enabled;Resourceprivate DynamicDataSource dynamicDataSource;Resourceprivate SysDbInfoMapper dbInfoMapper;/*** 项目启动时加载数据源*/Overridepublic void run(String... args) {if (!enabled) return;refreshDataSource();}/*** 刷新数据源*/public void refreshDataSource() {ListSysDbInfo dbInfos dbInfoMapper.selectList(new LambdaQueryWrapperSysDbInfo().eq(SysDbInfo::getStatus, 0));if (CollectionUtils.isEmpty(dbInfos)) return;ListSysDbInfo ds new ArrayList();log.info(开始加载数据源);for (SysDbInfo info : dbInfos) {if (StrUtil.isAllNotBlank(info.getUrl(), // 数据库连接地址info.getDriverClassName(), // 数据库驱动info.getUsername(), // 数据库用户名info.getPassword(), // 数据库密码info.getDbKey() // 数据源key)) {ds.add(info);log.info(加载到数据源 --- dbName{}、dbKey{}、remark{}, info.getDbName(), info.getDbKey(), info.getRemark());}}dynamicDataSource.createDataSource(ds);log.info(数据源加载完成);}
}package com.leixi.hub.saasdb.config;import java.lang.annotation.*;/*** 自定义多数据源切换注解* p* 优先级先方法后类如果方法覆盖了类上的数据源类型以方法的为准否则以类上的为准* author 雷袭月启* since 2024/12/5 19:39*/Inherited
Documented
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.METHOD, ElementType.TYPE})
public interface DataSource {/*** 切换数据源名称默认是主数据源test01*/public DataSourceType value() default DataSourceType.MASTER;
}
package com.leixi.hub.saasdb.config;import io.micrometer.common.util.StringUtils;
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.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import java.util.Objects;/*** 多数据源切换* author 雷袭月启* since 2024/12/5 19:39*/
Aspect
Order(1)
Component
public class DataSourceAspect {// 配置织入点为DataSource 注解Pointcut(annotation(com.leixi.hub.saasdb.config.DataSource) || within(com.leixi.hub.saasdb.config.DataSource))public void dsPointCut() {}/*** * 环绕通知** param point 切入点* return Object* throws Throwable 异常*/Around(dsPointCut())public Object around(ProceedingJoinPoint point) throws Throwable {DataSource dataSource getDataSource(point);if (Objects.nonNull(dataSource) StringUtils.isNotEmpty(dataSource.value().name())) {// 将用户自定义配置的数据源添加到线程局部变量中DynamicDataSourceContextHolder.setDataSource(dataSource.value().name());}try {return point.proceed();} finally {// 在执行完方法之后销毁数据源DynamicDataSourceContextHolder.removeDataSource();}}/*** 获取需要切换的数据源* 注意顺序为方法类方法上加了注解后类上的将不会生效* 注意当类上配置后方法上没有该注解那么当前类中的所有方法都将使用类上配置的数据源*/public DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature (MethodSignature) point.getSignature();// 从方法上获取注解DataSource dataSource AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);// 方法上不存在时再从类上匹配return Objects.nonNull(dataSource) ? dataSource : AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}接下来是测试的一些实体类Controller方法
package com.leixi.hub.saasdb.entity;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;import java.io.Serial;
import java.io.Serializable;/**** author 雷袭月启* since 2024/12/5 19:39*/
Data
ToString
EqualsAndHashCode
NoArgsConstructor
Accessors(chain true)
TableName(value sys_db_info)
public class SysDbInfo implements Serializable {SerialTableField(exist false)private static final long serialVersionUID 8115921127536664152L;/*** 数据库地址*/private String url;/*** 数据库用户名*/private String username;/*** 密码*/private String password;/*** 数据库驱动*/private String driverClassName;/*** 数据库key即保存Map中的key(保证唯一)* 定义一个key用于作为DynamicDataSource中Map中的key。* 这里的key需要和DataSourceType中的枚举项保持一致*/private String dbKey;/*** 数据库名称*/private String dbName;/*** 是否停用0-正常1-停用*/private Integer status;/*** 备注*/private String remark;
}package com.leixi.hub.saasdb.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leixi.hub.saasdb.entity.SysDbInfo;
import org.apache.ibatis.annotations.Mapper;Mapper
public interface SysDbInfoMapper extends BaseMapperSysDbInfo {}package com.leixi.hub.saasdb.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;
import java.util.Map;/**** author 雷袭月启* since 2024/12/5 19:39*/
Mapper
public interface CommonMapper extends BaseMapper {ListMapString, Object getDataBySql(Param(sql) String sql);void updateDataBySql(Param(sql) String sql);}?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.leixi.hub.saasdb.dao.CommonMapperselect idgetDataBySql resultTypejava.util.Map${sql}/selectupdate idupdateDataBySql${sql}/update
/mapperpackage com.leixi.hub.saasdb.controller;import com.leixi.hub.saasdb.config.DataSource;
import com.leixi.hub.saasdb.config.DataSourceType;
import com.leixi.hub.saasdb.dao.CommonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/**** author 雷袭月启* since 2024/12/5 19:39*/
RestController
public class DemoController {GetMapping(/demo)public Object demo() {return Hello World;}Autowiredprivate CommonMapper commonMapper;GetMapping(/getDataBySqlFromMaster)DataSource(DataSourceType.MASTER)public Object getDataBySqlFromMaster(RequestParam(value sql) String sql) {return commonMapper.getDataBySql(sql);}GetMapping(/getDataBySqlFromUat)DataSource(DataSourceType.UAT)public Object getDataBySqlFromSlave(RequestParam(value sql) String sql) {return commonMapper.getDataBySql(sql);}GetMapping(/getDataBySql)public Object getDataBySql(RequestParam(value sql) String sql) {return commonMapper.getDataBySql(sql);}}3、启动项目通过Postman测试结果和预期一致 二、通过Filter实现 上述的方法虽然有效但多少有些固化了为何一只有添加了注解的类或方法才能动态切换数据源需要对已有代码进行修改那就多少会有漏改少改的位置二来可选的数据源在枚举或代码中写死了假设在数据库里新增了一个数据源则程序中必须要做相应的调整可扩展性不高综合考虑后我决定再用过滤器的方式试试。 过滤器的原理其实和AOP相似只是在Header中添加一个数据库的Key在过滤器中根据这个Key来指定数据源实现代码如下
package com.leixi.hub.saasdb.filter;import com.leixi.hub.saasdb.config.DynamicDataSourceContextHolder;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;import java.io.IOException;/**** author 雷袭月启* since 2024/12/5 19:39*/
Order(1)
public class DataSourceChangeFilter implements Filter {private String dataSourceKey;public DataSourceChangeFilter(String dataSourceKey) {this.dataSourceKey dataSourceKey;}Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest (HttpServletRequest) request;String dataSource httpRequest.getHeader(dataSourceKey);if (StringUtils.isNotEmpty(dataSource)) {DynamicDataSourceContextHolder.setDataSource(dataSource);chain.doFilter(request, response);destroy();} else {chain.doFilter(request, response);}}Overridepublic void destroy() {DynamicDataSourceContextHolder.removeDataSource();}}package com.leixi.hub.saasdb.filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/**** author 雷袭月启* since 2024/12/5 19:39*/
Configuration
public class FilterConfig {Value(${leixi.saas.data_source_key:data_source_key})private String dataSourceKey;Beanpublic FilterRegistrationBeanDataSourceChangeFilter licenseValidationFilterRegistration() {FilterRegistrationBeanDataSourceChangeFilter registration new FilterRegistrationBean();registration.setFilter(new DataSourceChangeFilter(dataSourceKey));registration.addUrlPatterns(/*); // 应用于所有URL /* 应用于登陆 /loginreturn registration;}
} 测试过程如下 而不传Header时默认查询的是Master库 三、推广使用 当前这个项目是已经实现了多数据源的动态切换那么如果想让其他项目也支持应该怎么办呢咱可以把这个项目打成一个jar包然后让其他项目引入依赖即可改动如下 1、删除Application.java文件。 2、在pom中用以下打包语法进行打包。 !--可以打成供其他包依赖的包--buildpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-archetype-plugin/artifactIdversion3.0.0/version/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.11.0/versionconfigurationsource17/sourcetarget17/targetencodingUTF-8/encoding/configuration/plugin/pluginsresourcesresourcedirectorysrc/main/resources/config/directoryfilteringtrue/filteringexcludesexclude*/exclude/excludes/resource/resources/build 3、打包完成后可以在target中看到对应的jar文件也可以在其他项目中引用该文件如下 后记与致谢 以上就是我今天的全部分享了 Demo比较简单手法也相对稚嫩希望不会贻笑大方也希望新手看到这个Demo能有所启发。这次实践也并非一蹴而就的离不开大佬们的支持和点拨雷袭在网上找了很多资料以下这篇博客是最有价值的可以说雷袭完全是照抄了他的成果这里附上原文链接拜谢大佬 SpringBoot3多数据源动态切换-陌路