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

怎么自己注册网站平台了学习软件

怎么自己注册网站平台了,学习软件,展示型网站举例,深圳专业英文网站建设第一章 需求背景与技术选型1.1 多数据源场景概述在大型企业级应用中#xff0c;单一数据库往往无法满足高并发和多业务线的需求#xff0c;因此需要引入 多数据源 的架构设计。常见的多数据源场景包括#xff1a;读写分离、多租户、分库分表以及数据源负载均衡等。读写分离单一数据库往往无法满足高并发和多业务线的需求因此需要引入 多数据源 的架构设计。常见的多数据源场景包括读写分离、多租户、分库分表以及数据源负载均衡等。读写分离在此场景下通常有一个主库Master用于写操作以及一个或多个从库Slave用于读操作。应用层可以将写请求路由到主库读请求路由到从库以减轻主库负载并提高读写吞吐量。例如在电子商务系统中用户下单会写入主库而商品浏览、列表查询等场景可以走从库从而提高整体性能。多租户在 SaaS 系统中不同租户的数据需要隔离。实现方式有独立库、多库共享架构等。通常根据当前租户ID动态选择相应的数据源以达到数据隔离的目的。比如一个 CRM 系统不同公司租户使用相同代码但数据存储在不同的数据库中。分库分表Sharding为了处理海量数据会将数据水平切分到多个库或表中。按照业务切分规则如用户ID、时间等路由到对应的数据源。这样可以并行扩展数据库容量和查询吞吐率。负载均衡在微服务或高可用场景下可能部署多个相同功能的数据库实例。可以通过策略轮询、权重等将请求均匀分布到不同数据源提高可用性和并发处理能力。此外在某些复杂场景中还可能需要支持多种数据库类型关系型、NoSQL 混合或动态增加新数据源的能力。抽象的数据源路由策略能够统一管理数据源选取逻辑而不侵入业务代码这就是 AbstractRoutingDataSource 等技术产生的背景。1.2 MyBatis 与 Spring Boot 生态的集成优势随着 Java 微服务和云原生架构的普及Spring BootMyBatis 已成为常见的开发栈。Spring Boot 提供自动化配置和丰富生态如 spring-jdbc、spring-tx 等MyBatis 提供简洁灵活的 ORM 框架将 SQL 与 Java 对象映射无缝结合。两者集成有以下优势易用的配置Spring Boot 自动化装配机制可以简化数据源、事务管理和 MyBatis 的配置。只需引入 spring-boot-starter-jdbc、mybatis-spring-boot-starter 等依赖并在 application.yml 中配置数据源即可使用。灵活的插件机制MyBatis 支持插件Interceptor机制可以在执行 SQL 之前或之后插入自定义逻辑。这为动态切换数据源提供了另一种实现思路如自定义 MyBatis 拦截器来切换 DataSource。丰富的事务支持Spring 框架提供统一的事务管理抽象包括声明式事务注解和编程事务 API可与动态数据源无缝集成例如 Spring DataSource 事务管理可以自动适配切换后的目标数据源。生态插件和库除了手工实现还有成熟的开源项目支持动态数据源管理如 MyBatis-Plus 动态数据源插件、Apache ShardingSphere 等可用于更高级的数据路由和分片需求。通过 Spring Boot 和 MyBatis 的集成我们可以在保持业务代码简洁的同时利用框架特性来做数据源路由和切换而不需要在每个 DAO 调用中手工传递 DataSource 信息。1.3 动态数据源切换的必要性在多数据源场景下硬编码数据源会导致配置僵化和代码侵入。动态数据源切换提供一种透明、非侵入式的方式业务代码仅需关注要执行的业务操作由底层框架根据当前上下文动态选择合适的数据源。这样做的必要性包括解耦业务与数据源逻辑业务层无需感知当前要使用哪个库调用 DAO 接口即可。通过切换逻辑如通过注解、上下文参数等将路由策略集中管理提高代码可维护性。灵活应对需求变化运行时可以根据配置或上下文动态添加新数据源、读写比例调整、租户增减等。无需停服重新部署即可扩展数据源体系。满足性能与隔离需求对于读写分离场景通过切换到从库可以缓解主库压力对于多租户场景根据租户上下文路由到对应数据库保证数据隔离。减少重复实现不需要在每个 Service/DAO 中手写切换逻辑。使用统一的 AbstractRoutingDataSource 或 AOP 切面等方案可以复用切换代码。总之动态数据源切换是为了解决多数据库环境下灵活路由和管理数据源的需求。下文将深入分析底层实现原理并以 Spring Boot 为例给出生产级的实践方案。第二章 核心组件与实现原理2.1 AbstractRoutingDataSource 源码解析Spring Framework 提供了一个核心组件 AbstractRoutingDataSource 用于数据源路由。根据官方文档的描述它是“一个抽象的 DataSource 实现用于基于查找键lookup key将 getConnection() 调用路由到多个目标 DataSource 之一”。核心实现逻辑如下路由数据源配置AbstractRoutingDataSource 拥有一个 targetDataSources 映射其中键是查找键Object一般为 String、Enum 等值是实际的 DataSource 对象。还可以配置一个默认数据源 defaultTargetDataSource。查找键获取AbstractRoutingDataSource 定义了一个抽象方法 determineCurrentLookupKey()子类需重写此方法来决定当前调用应使用哪个键。通常这个键信息会从某种线程上下文中获取如 ThreadLocal 存储的租户ID或读写标识。DataSource 路由在每次调用 getConnection() 时AbstractRoutingDataSource 会首先调用 determineCurrentLookupKey() 获取当前键然后根据这个键在 targetDataSources 中查找对应的实际 DataSource。如果找不到匹配的数据源则会根据 lenientFallback 属性决定是抛出异常还是使用默认数据源。初始化与刷新AbstractRoutingDataSource 实现了 InitializingBean 接口其中的 afterPropertiesSet() 方法会解析配置将指定的数据源对象解析为实际的 DataSource 实例并存入内部结构供路由使用。简而言之AbstractRoutingDataSource 是一个“路由器”它本身也实现了 DataSource 接口但内部并不存储连接而是根据当前上下文查找真正的目标 DataSource然后将 getConnection() 的调用委派给该目标数据源。这就像一个中间层业务代码通过此代理 DataSource 调用数据库代理会根据规则动态决定链接到哪个实际数据库。这个原理类似于春季博客所说的“路由DataSource作为中介真正的 DataSource 可以在运行时动态确定”。AbstractRoutingDataSource 工作流程应用启动时Spring 容器会实例化 AbstractRoutingDataSource 的子类调用其 afterPropertiesSet() 方法解析配置文件中定义的所有目标数据源并存入一个 Map。业务层注入的是这个 AbstractRoutingDataSource 代理对象而非单一的 DataSource。在执行数据库操作时例如通过 MyBatis 打开 SqlSession 或 Spring 的 JdbcTemplate 调用会触发 AbstractRoutingDataSource.getConnection()。该方法调用 determineCurrentLookupKey() 获取当前数据源的键。例如在多租户场景下determineCurrentLookupKey() 可能返回当前线程所对应的租户ID。根据查找键从预先配置的 Map 中取得对应的实际 DataSource并调用其 getConnection() 获得 JDBC 连接。如果查找键没有对应的数据源则根据 lenientFallback 决定若允许回退则使用默认数据源否则抛出异常。可以说AbstractRoutingDataSource 将数据源选取逻辑与应用业务逻辑解耦。用户只需关注如何获取当前上下文的键而实际的数据库连接细节由框架完成。2.2 ThreadLocal 上下文管理机制在动态数据源切换中通常需要一种机制来保存当前线程所需使用的数据源标识。Java 提供了 ThreadLocal 类用于在线程之间隔离存储变量每个线程都会持有一份 ThreadLocal 变量的副本。典型的做法是使用 ThreadLocal 来存放当前线程要使用的数据源名称或Key。以常见的设计模式为例定义一个 DataSourceContextHolder或称 DynamicDataSourceContextHolder类其内部维护一个 ThreadLocal 变量有的实现用 Deque 来支持嵌套调用。调用方法通常是在进入需要切换数据源的逻辑前先将目标数据源Key压入 ThreadLocal业务代码执行时AbstractRoutingDataSource 会调用 determineCurrentLookupKey() 方法此方法从 ThreadLocal 中取出当前Key。执行完毕后再清除或弹出 ThreadLocal 中的Key避免污染后续请求。例如在开源项目中数据源上下文管理器可能是这样的结构 public final class DynamicDataSourceContextHolder {private static final ThreadLocalDequeString CONTEXT_HOLDER ThreadLocal.withInitial(ArrayDeque::new);public static String peek() {DequeString deque CONTEXT_HOLDER.get();return deque.peek();}public static void push(String ds) {CONTEXT_HOLDER.get().push(ds);}public static void poll() {DequeString deque CONTEXT_HOLDER.get();deque.poll();} } 上例使用 ThreadLocalDequeString 来维护一个堆栈支持嵌套切换这在多层服务调用需要多次切换时很有用。每个线程都会有自己的 Deque因此数据源的设置互不干扰。多个实现示例表明核心思路都是“通过 ThreadLocal 管理数据源标识然后在执行SQL前获取当前标识并完成切换”。需要注意的是使用 ThreadLocal 时应谨慎清理。Web 应用中线程复用如线程池会导致如果不在请求结束时清空 ThreadLocal下一个请求可能错误地继承了上一个请求的标识即所谓的ThreadLocal 污染问题本章后续会讨论。因此常在切面或拦截器的 finally 代码块中弹出/清除 ThreadLocal 数据以保证线程干净。2.3 AOP 拦截器与 MyBatis 拦截器的对比实现动态数据源切换时常用的方法有基于 Spring AOP 切面注解以及基于 MyBatis 插件 的拦截。两者的原理和使用场景有所不同Spring AOP 切面通过自定义注解如 DataSource标记在业务方法或类上然后编写一个 AOP 切面Aspect在切面中通过 Around 通知来在方法执行前设置线程上下文中的数据源然后执行方法最后在 finally 中清除上下文。Spring AOP 本质上使用动态代理JDK 或 CGLIB来拦截对 Spring 管理 Bean 的方法调用适用于需要在 Service 层方法调用时切换数据源的场景。优点是使用简单、可读性好缺点是仅对由 Spring 容器管理的 Bean 生效对内部调用self-invocation或非 Spring 托管对象不可用。MyBatis 插件拦截MyBatis 提供了插件机制可以拦截 Executor、StatementHandler、ResultSetHandler、ParameterHandler 等对象的执行方法。可以编写一个 MyBatis Interceptor实现 org.apache.ibatis.plugin.Interceptor 并用 Intercepts 注解指定拦截点在 intercept() 方法中根据当前上下文选择数据源。这样做的优点是作用于 MyBatis 层可以拦截所有通过 MyBatis 执行的 SQL 请求不依赖 Spring AOP缺点是实现较复杂且 MyBatis 插件拦截点一般在执行 SQL 语句时才起作用无法像 AOP 那样轻松在方法入口处处理业务逻辑。动态代理模式动态数据源本身就是一种代理模式——AbstractRoutingDataSource 就是对目标数据源的代理。另外Spring AOP 使用的正是动态代理机制对目标 Bean 生成代理对象对外提供切面功能。从性能角度看代理调用会带来微小开销但通常可以忽略。正如社区讨论所说“使用基于代理的 AOP每应用一个切面只会多一次方法调用性能开销几乎可以忽略”。MyBatis 插件也是通过包装底层对象实现拦截性能差异很小。除非在极端性能要求的场景下一般不需要担心 AOP vs 插件 vs 代理的性能差异每次切换仅相当于额外几次方法调用或反射调用耗时在纳秒级。下面举个对比总结切面AOP基于业务逻辑层灵活使用注解进行数据源切换代码可读性好易于集成事务。但仅拦截 Spring 管理的 Bean 方法不适用于 Mapper 接口的内部调用。MyBatis 插件直接作用于 SQL 执行层可拦截所有 MyBatis 调用适合在 Mapper 级别强制切换数据源比如读操作全部拦截到从库。配置繁琐度较高一般配合 ThreadLocal 使用。动态代理本质机制与 AOP 类似。Spring AOP 在 bean 层使用 JDK/CGlib 动态代理技术MyBatis 插件在 Executor 层使用代理。总体上都带来很小的调用开销。在实际应用中最常用的方案是自定义注解 Spring AOP 切面 的组合。这种方案直观且结合 Spring Boot 注解驱动编程体验好。另一个常见做法是使用成熟的动态数据源框架如 MyBatis-Plus 提供的 DS 注解其内部也利用 AOP ThreadLocal 实现。当然根据业务需要也可以在 MyBatis 插件层面实现切换但需要开发者深入理解 MyBatis 拦截原理。第三章 Spring Boot 实现方案本章我们将以 Spring Boot 为例从零开始演示如何实现动态数据源切换包括配置数据源、定义注解和切面、编写测试等。3.1 多数据源配置类编写首先需要在 Spring Boot 项目中配置多个数据库连接。在 application.yml 中定义多个数据源如 master 和 slave spring:datasource:master:url: jdbc:mysql://localhost:3306/master_dbusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverslave:url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driver 然后在 Java 代码中定义两个 DataSource Bean。例如 Configuration public class DataSourceConfig {Bean(name masterDataSource)ConfigurationProperties(prefix spring.datasource.master)public DataSource masterDataSource() {return DataSourceBuilder.create().build();}Bean(name slaveDataSource)ConfigurationProperties(prefix spring.datasource.slave)public DataSource slaveDataSource() {return DataSourceBuilder.create().build();} } 这里使用 ConfigurationProperties 从 YAML 注入属性DataSourceBuilder 可以自动选择 HikariCP 或 Druid 等连接池。接下来需要定义一个 动态路由数据源继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() public class DynamicRoutingDataSource extends AbstractRoutingDataSource {Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.peek();} } 同时编写配置将上述两个数据源注入到动态路由数据源中 Configuration public class DynamicDataSourceConfig {AutowiredQualifier(masterDataSource)private DataSource masterDataSource;AutowiredQualifier(slaveDataSource)private DataSource slaveDataSource;Beanpublic DynamicRoutingDataSource dynamicDataSource() {MapObject, Object targetDataSources new HashMap();targetDataSources.put(master, masterDataSource);targetDataSources.put(slave, slaveDataSource);DynamicRoutingDataSource ds new DynamicRoutingDataSource();ds.setTargetDataSources(targetDataSources);ds.setDefaultTargetDataSource(masterDataSource); // 设置默认数据源return ds;}// 配置 SqlSessionFactory使 MyBatis 使用动态数据源Beanpublic SqlSessionFactory sqlSessionFactory(DynamicRoutingDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean();factoryBean.setDataSource(dynamicDataSource);// 可设置 MyBatis 配置、映射文件等return factoryBean.getObject();} } 在上述配置中我们创建了两个命名的 DataSource Bean再用 DynamicRoutingDataSource 将它们放到一个 Map 中key 为数据源标识如 master、slave并设置默认数据源。DynamicRoutingDataSource 继承 AbstractRoutingDataSource会在运行时根据 determineCurrentLookupKey() 返回的键决定使用哪个子数据源。我们再把 DynamicRoutingDataSource 作为 MyBatis 的 SqlSessionFactory 的数据源确保所有 MyBatis 操作都经过它。Spring Boot 项目结构示例 src/main/java/com/example/dynamicds/config/DataSourceConfig.java // 配置 master/slave DataSourceDynamicDataSourceConfig.java // 配置 DynamicRoutingDataSource、SqlSessionFactoryannotation/DataSource.java // 自定义注解aspect/DataSourceAspect.java // AOP 切面holder/DynamicDataSourceContextHolder.java // ThreadLocal 上下文mapper/UserMapper.java // MyBatis Mapper 接口entity/User.java // 实体类service/UserService.java // 业务层接口及实现DynamicDataSourceApplication.java // 启动类 src/main/resources/application.yml // 数据源配置mapper/UserMapper.xml // MyBatis 映射文件 pom.xml // 依赖配置 pom.xml部分示例依赖 dependencies!-- Spring Boot 及其 Starter --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactId/dependency!-- MyBatis Spring Boot Starter --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.2/version/dependency!-- 数据库连接池可选 HikariCP--dependencygroupIdcom.zaxxer/groupIdartifactIdHikariCP/artifactId/dependency!-- MySQL 驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependency!-- Spring AOP --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency /dependencies 上面代码中核心在于将多个 DataSource 注入到 DynamicRoutingDataSource 中通过 setTargetDataSources 指定路由映射。此外我们使用 Bean 注册了 SqlSessionFactory将 DataSource 设置为我们的动态数据源。这样后续所有的数据库操作都会走 DynamicRoutingDataSource由其决定实际连接哪个库。3.2 自定义 DataSource 注解与 AOP 切面实现为了在业务层灵活选择数据源我们可以定义一个自定义注解如 DataSource并通过 Spring AOP 切面来拦截带该注解的方法。在切面中我们将注解中的数据源名称压入 DynamicDataSourceContextHolder 的 ThreadLocal。自定义注解 DataSource.java Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DataSource {String value() default master; } 该注解可标注在类或方法上value 用于指定使用的数据源名称。上下文持有器 DynamicDataSourceContextHolder.java public class DynamicDataSourceContextHolder {private static final ThreadLocalDequeString CONTEXT_HOLDER ThreadLocal.withInitial(ArrayDeque::new);// 获得当前线程的数据源public static String peek() {DequeString deque CONTEXT_HOLDER.get();return deque.peek();}// 将数据源压入栈顶public static void push(String ds) {CONTEXT_HOLDER.get().push(ds);}// 弹出当前数据源public static void poll() {CONTEXT_HOLDER.get().poll();} } 如前所述这个 ThreadLocal 栈结构允许嵌套调用时恢复前一个数据源。AOP 切面 DataSourceAspect.java Aspect Component public class DataSourceAspect {Pointcut(annotation(com.example.dynamicds.annotation.DataSource) || within(com.example.dynamicds.annotation.DataSource))public void dataSourcePointCut() {}Around(dataSourcePointCut())public Object around(ProceedingJoinPoint point) throws Throwable {// 解析注解上的数据源名称MethodSignature signature (MethodSignature) point.getSignature();Method method signature.getMethod();String ds master; // 默认数据源if (method.isAnnotationPresent(DataSource.class)) {DataSource annotation method.getAnnotation(DataSource.class);ds annotation.value();} else {// 如果方法上没有查看类上是否有注解Class? targetClass point.getTarget().getClass();if (targetClass.isAnnotationPresent(DataSource.class)) {DataSource annotation targetClass.getAnnotation(DataSource.class);ds annotation.value();}}try {// 切换数据源DynamicDataSourceContextHolder.push(ds);return point.proceed();} finally {// 切换完毕后弹出数据源DynamicDataSourceContextHolder.poll();}} } 上面代码说明切面拦截标注了 DataSource 注解的方法或类优先取方法上的注解值否则取类上的。获取到的 ds 就是要使用的数据库标识如 slave。在执行业务方法前将该标识 push 到 ThreadLocal 栈方法执行完成后再弹出确保后续线程不受影响。这样就形成了完整的 注解驱动 AOP 切面 的动态切换方案当调用一个被 DataSource(slave) 标记的方法时切面会把 ThreadLocal 里当前数据源设置为 slave从而使 determineCurrentLookupKey() 返回 slave动态数据源路由到从库。示例业务代码 Service public class UserService {Autowiredprivate UserMapper userMapper;// 默认使用 masterpublic ListUser listUsers() {return userMapper.selectAll();}DataSource(slave)public ListUser listUsersFromSlave() {return userMapper.selectAll();} } 在这个例子中listUsersFromSlave() 方法被标记为使用 slave 数据源调用时会路由到从库执行查询。核心逻辑完全由切面和路由组件完成业务层无需关心数据源切换的细节。3.3 动态数据源切换的测试用例设计为了确保动态数据源切换正确可以编写单元测试或集成测试验证。思路是在测试中通过设置上下文类似切面方式切换数据源执行 CRUD 操作并检查结果是否来自预期的库。示例测试 SpringBootTest public class DynamicDataSourceTest {Autowiredprivate UserMapper userMapper;Testpublic void testMasterSlaveSwitch() {// 准备在 master 和 slave 中插入可区分的数据// 假设在 masterDB 中有 user {id1, nameA}slaveDB中有 user{id2, nameB}// 默认使用 masterListUser masterUsers userMapper.selectAll();assertTrue(masterUsers.stream().anyMatch(u - u.getName().equals(A)));// 切换到 slaveDynamicDataSourceContextHolder.push(slave);try {ListUser slaveUsers userMapper.selectAll();assertTrue(slaveUsers.stream().anyMatch(u - u.getName().equals(B)));} finally {DynamicDataSourceContextHolder.poll();}} } 上例使用了手动 push/poll 的方式模拟切换。在更高层的测试框架下可以直接调用带注解的方法来进行测试。关键在于验证在切换前后得到的结果应该明显不同以证明数据是从不同的数据源中读取。另一个常见测试是多线程环境的切换确保在并发执行切换时各线程能够正确分辨使用不同的数据源且互不干扰。可以使用多线程测试框架或模拟请求的方式验证线程安全性。测试注意点在测试数据库中插入明确可区分的数据不同库中插入不同内容进行验证。在单元测试结束后清理 ThreadLocal或者在测试类中加上 After 注解清理上下文。如果使用 Spring 事务需要特别注意默认事务配置下切面是否按预期执行可能需要设置事务为 REQUIRES_NEW 或测试中手动处理事务。通过上述配置和测试基础的动态数据源切换功能就能正确运行。在此基础上下一章我们将探讨更高级的功能和优化。第四章 进阶实践与优化4.1 数据源自动注册与动态加载在生产环境中有时需要运行时动态新增或移除数据源。例如多租户平台中租户随时注册或注销或者需要在不重启服务的情况下将新数据库接入系统。实现思路是利用 AbstractRoutingDataSource 的内部结构在运行时修改其内部的目标数据源映射targetDataSources并调用 afterPropertiesSet() 刷新路由。以前述 DynamicRoutingDataSource 为例我们可以扩展接口来动态添加 public class DynamicRoutingDataSource extends AbstractRoutingDataSource {// 存放所有数据源实例private MapObject, DataSource dataSourceMap new ConcurrentHashMap();public void addDataSource(String dsKey, DataSource dataSource) {dataSourceMap.put(dsKey, dataSource);// 添加到 AbstractRoutingDataSource 的 targetDataSourcessuper.setTargetDataSources(dataSourceMap);super.afterPropertiesSet(); // 刷新解析}Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.peek();} } 在上述例子中当需要新增数据源时可以通过 dynamicRoutingDataSource.addDataSource(newDs, newDataSource) 来实现。注意调用 afterPropertiesSet() 以让 AbstractRoutingDataSource 重新解析新的配置。同样地可以实现 removeDataSource 方法从 Map 中删除并刷新。这种动态注册方式允许我们在运行时基于配置中心、后台管理等方式来管理数据源。例如可以在服务中添加一个管理接口 RestController public class DataSourceController {Autowiredprivate DynamicRoutingDataSource dynamicDataSource;PostMapping(/datasource)public String addDataSource(RequestBody DataSourceProperties props) {DataSource ds DataSourceBuilder.create().driverClassName(props.getDriver()).url(props.getUrl()).username(props.getUsername()).password(props.getPassword()).build();dynamicDataSource.addDataSource(props.getName(), ds);return DataSource added;} } 以上代码接收一个 POST 请求传递新的数据库配置动态创建并注册数据源。需要注意线程安全和异常处理例如若添加重复键应抛出错误。动态加载最大的挑战是确保在切换发生过程中所有组件事务管理、SqlSession 等都能感知到更新过的路由表避免并发时出现找不到数据源的情况。4.2 数据源切换异常的兜底策略在多数据源路由中常见的问题是找不到目标数据源或者没有配置默认数据源时的处理。Spring 的 AbstractRoutingDataSource 提供了 宽松回退lenientFallback 机制。其含义是当 determineCurrentLookupKey() 返回的键在 targetDataSources 中没有匹配时如果开启宽松回退则会自动使用默认数据源否则会抛出 IllegalStateException。默认情况下lenientFallback 为 true即非严格模式只有当查找键对应的 DataSource 为空才回落到默认数据源。可通过配置类或 XML 调用 setLenientFallback(false)在查找键未配置时抛出异常以便快速定位问题。无论如何推荐在系统启动时至少提供一个默认数据源可以防止在切换出错时导致所有操作都失败。如果决定关闭回退lenientFallbackfalse需要注意如果切换的 ThreadLocal 键发生意外错误如未设置或清理不当导致为空则会抛出异常因此生产代码中应保证在使用前必须设置键或在注解切面中做默认保护。对于不使用 DataSource 注解的方法未明确切换我们一般让其走默认数据源。可以在上下文取值时做判断如 String ds DynamicDataSourceContextHolder.peek(); if (ds null) dsmaster;。另外当数据源不存在时还可以自定义兜底逻辑。例如捕获抛出的 DataAccessException 或 SQLException 并记录错误信息或发送告警。而对于读写分离场景可以制定“失败重试”策略若切换到从库后读操作失败再自动重试主库。总之需要合理配置默认源和回退策略并在代码层面做好异常处理避免生产时因为数据源名错误而全局不可用。Spring 参考答案也曾提到如果完全不想出现默认数据源可考虑禁用 Hibernate 的自动 DDL 和元数据校验因为初始化阶段可能会触发数据库连接否则可以通过配置 spring.jpa.hibernate.ddl-autonone、spring.jpa.properties.javax.persistence.validation.modenone 等避免启动时的连接尝试。4.3 性能调优技巧连接池参数配置动态数据源切换本身并不会显著影响性能但多数据源环境下需要特别关注每个数据源连接池参数的配置。合理配置可提升并发能力和稳定性。以下是一些常见的优化建议连接池类型与并发能力Spring Boot 默认使用 HikariCP 作为连接池若引入 spring-boot-starter-jdbc。HikariCP 以高性能著称可通过调整 spring.datasource.hikari.maximum-pool-size 来控制最大连接数。默认值为 10在高并发场景下可以根据服务器资源适当增大比如 50、100。但要避免配置过大导致数据库压力。连接超时与空闲超时connectionTimeout获取连接等待时间默认 30 秒。可根据业务需求调整防止线程长时间阻塞等待连接。idleTimeout连接空闲超时和 maxLifetime连接最大存活时间也应合理设置避免频繁创建销毁连接推荐一个较长的 maxLifetime如 30 分钟或更高防止频繁重建。多环境配置分离开发、测试环境可以使用较小的连接池规模生产环境可根据实际负载增加连接数。同时生产中若读写分离应为从库和主库分别配置连接池例如主库承载写请求可配置更大连接数从库承载读请求可根据读请求量配置。监控连接池状态集成监控工具如 HikariCP 自带的指标、或 MicrometerPrometheus来实时观察各个数据源的连接使用情况及时调整参数。SQL 调优与批量操作对于动态路由逻辑应尽量减少切换开销避免在批量操作中频繁切换数据源。对于大量写操作推荐批量提交或使用批处理方式。缓存与命中率如果使用 Redis 等缓存减轻数据库压力同样需要根据多数据源环境配置缓存键策略避免不同库间数据污染。最后可以引用工具链的诊断数据来验证优化效果。例如使用 Spring Boot 提供的 Actuator 监控数据源状态或通过 select * from information_schema.processlist 查看连接情况。正确的连接池参数可以显著减少连接建立/关闭的开销提高持续负载下的稳定性。第五章 生产环境注意事项5.1 线程安全与 ThreadLocal 污染问题如前文所述动态数据源切换依赖 ThreadLocal 来保存当前线程的数据源标识。生产环境通常使用线程池如 Tomcat 池、WebFlux 线程池等来复用线程此时ThreadLocal 污染风险需要特别关注如果在线程执行完成后未及时清理 ThreadLocal下一个任务复用该线程时可能误用上一个任务的数据源标识。常见防范方法切面 finally 清理在 AOP 切面中务必使用 try...finally 结构在 finally 块中调用 DynamicDataSourceContextHolder.poll()弹出当前数据源或 remove() 来清空标识。确保无论业务逻辑是否异常结束都能清理上下文。检查默认值在上下文管理类中如 DynamicDataSourceContextHolder.peek() 返回 null 或空字符串时应该默认使用主库。这可以作为保险机制避免因清理不当导致 lookupKey 为空时抛出异常。避免同一线程并行处理多任务在特殊场景下比如使用 CompletableFuture、ForkJoinPool 等框架时一个线程可能并行处理多个任务或等待任务结果最好不要在异步回调中依赖 ThreadLocal。需要时可使用 Executor 代理或 ThreadLocal 继承InheritableThreadLocal来传递上下文但要格外谨慎。定期审查日志当出现请求访问错误数据库或返回数据错乱时怀疑是线程污染时应在日志中记录切换和清理操作比如在切面中加入日志 try {DynamicDataSourceContextHolder.push(ds);logger.debug(Switch DataSource to [{}], ds);return point.proceed(); } finally {DynamicDataSourceContextHolder.poll();logger.debug(Revert DataSource); } 这样在异常情况可查看是否某条请求没有执行清理而影响了后续请求。总之“线程安全”是动态数据源方案必须关注的。ThreadLocal 本身是线程隔离的只要使用正确的编程模式及时清理、避免跨线程传播在多线程环境下仍可安全使用动态切换。反之若忽视清理就会出现“线程池复用导致数据源标识错乱”的问题。我们可以将这种情况比喻为“快递分拣系统”每辆车线程在发出新的路线指令数据源时必须清除之前的路线否则货物数据会送错地方。5.2 数据源切换与事务管理的兼容性在 Spring 中使用动态数据源切换时事务管理也是一个重点难点。常见问题包括事务边界应在数据源切换之后设定以及跨数据源事务的兼容。事务与切换顺序通常我们希望切换数据源后再开启事务。因为事务管理器会绑定到具体数据源连接上。如果先开启事务Spring 的 Transactional 注解默认在切面之后执行再切换数据源则事务仍然作用在旧数据源上。解决方法有两种切换逻辑早于事务可以使用 Transactional 注解的 EnableTransactionManagement(order X) 属性调整切面执行顺序确保数据源切换的 AOP 执行顺序高于事务 AOP。通常给切换切面较高优先级较低 order 值。声明式事务使用动态事务管理器配置 AbstractRoutingDataSource 作为 DataSource使得 Spring 事务管理通过它获取连接这样即使事务切面在切换切面之后也会从路由数据源中获取正确的实际连接。多数据源事务如果业务逻辑需要同时操作多个库如跨租户查询则单个事务无法跨多个数据源管理。解决方案是使用分布式事务如使用 Seata、Atomikos 等第三方分布式事务管理框架协调多个数据源事务。自行管理事务在最简单的场景下可以明确不同数据源的操作由不同事务管理并在业务层分别开启事务然后手动协调这种方式较为复杂且容易出错不推荐。只读事务与写事务结合读写分离场景如果在只读方法上使用 Transactional(readOnly true)可以让其默认切换到从库。而在写方法上使用默认事务可以切换到主库。这一做法需要我们在切面逻辑中可判断事务属性例如 boolean readOnly ((MethodSignature)point.getSignature()).getMethod().getAnnotation(Transactional.class).readOnly(); if (readOnly) {DynamicDataSourceContextHolder.push(slave); } else {DynamicDataSourceContextHolder.push(master); } 结合 Transactional 的 readOnly 标志来自动切换是一种常见的优化策略。需要确保事务配置优先于切换切面的次序或者在切面中根据事务信息设置。事务回滚注意当发生异常回滚时由于事务可能会重新获取连接或释放连接ThreadLocal 不要在事务回滚时才进行清理事务异常可提前触发切面 finally 块清理。同时在使用分布式事务时需要根据所用框架的规范进行切换管理。综上动态数据源切换与事务管理需要配合使用。最佳实践是动态切换逻辑在 Spring AOP 配置为比事务切面更高优先级确保在获取连接前已切换到正确的 DataSource并在切面清理后让事务正常关闭连接。这样可以保证事务逻辑应用到预期的数据库上。5.3 日志记录与监控埋点为了运维和排查问题建议在动态数据源切换的关键位置加入日志和监控埋点数据源切换日志在 AOP 切面中增加日志输出如前述示例所示当每次切换发生时记录目标数据源。日志内容可包含类名、方法名和数据源标识方便定位哪个服务调用产生切换。连接获取跟踪在使用 AbstractRoutingDataSource 时可以开启 JDBC 连接池的日志或监控监控每个数据源的连接池状态。例如 HikariCP 支持 JMX可以定期查看连接使用情况。SQL 监控可集成 MyBatis 的 SQL 日志Spring Boot 支持 logging.level.com.example.mapperDEBUG或使用像 P6Spy 这样的工具在日志中标记当前使用的是哪一个数据源执行的 SQL。动态数据源切换框架如 dynamic-datasource-spring-boot-starter通常提供 SPI 接口可以在连接获取时输出额外信息。监控埋点如果项目使用了 APM例如 SkyWalking、Pinpoint或自定义埋点工具可以在切面中埋点以跟踪跨库调用。记录数据源切换、事务执行、SQL 执行时长等关键指标。通过完善的日志和监控运维人员可以快速发现配置错误如数据源名称写错导致使用了默认库、性能瓶颈例如某个库连接池满载等问题。例如“如果发现请求日志中某些方法频繁切换到错误的数据源”则说明切面逻辑或配置有问题如果“某个数据源的连接使用率常年接近上限”则需要扩容或优化查询。第六章 扩展场景与替代方案6.1 分库分表场景下的数据源路由在分库分表场景下通常会将数据按某种规则拆分到多个数据库或表中。例如将用户数据按地区或用户ID范围分到不同库。动态数据源切换可作为分库路由的基础但一般仅能处理库级别的切换。如果同时需要分表则需进一步结合 MyBatis-Plus、Sharding-Sphere 等中间件。实现思路首先确定分库规则如通过计算 (userId % N) 确定目标数据库然后在业务或 MyBatis 层将 ThreadLocal 切换到对应数据库。再配合分表插件如 MyBatis-Plus 动态表名插件或 ShardingSphere 的表策略实现表级路由。例如手动实现分库 // 计算应当使用哪个库 String dbKey (userId % 2 0) ? db0 : db1; DynamicDataSourceContextHolder.push(dbKey); try {userMapper.insert(user); } finally {DynamicDataSourceContextHolder.poll(); } 如果仅靠 AOP 注解不方便也可使用 MyBatis-Plus 的分库分表插件它支持根据租户ID或表键自动路由。MyBatis-Plus 的 MultiDataSource 插件也支持多租户场景兼顾分库分表配置。6.2 动态数据源与 MyBatis-Plus 的集成MyBatis-Plus 提供了开源的 dynamic-datasource-spring-boot-starter以下简称 DynamicDataSource在 Spring Boot 项目中非常流行。这个组件内置了我们上面自定义的功能包括注解、切面、数据源注册等。它特点如下注解使用提供 DS 注解类似我们上面定义的 DataSource功能相近。自动配置扫描 spring.datasource.dynamic.* 下的配置自动注入所有子数据源支持分组数据源配置多个从库别名为组名。增强功能除了动态切换还提供如 数据源加密ENC()、动态刷新热更新数据源、独立初始化表结构 等特性。与 MyBatis-Plus 兼容直接支持 MyBatis-Plus无需额外配置也可以和 Quartz 等库兼容。多租户支持可以自定义租户ID获取器实现租户自动注入逻辑。集成方法大致如下引入依赖根据 Spring Boot 版本选择 com.baomidou:dynamic-datasource-spring-boot-starter 或其 Boot 3 版本。在 application.yml 中添加多数据源配置结构同前述只是键名可能略有差异以下划线分组。使用注解 DS(slave1) 切换数据源。MyBatis-Plus 方案对开发者透明度很高如果只是需要常规的读写分离和多数据源切换功能直接使用它会比手写更快捷。它的源码同样采用了 AbstractRoutingDataSource 和 ThreadLocal并做了丰富的边缘处理建议阅读其官方文档了解更多。6.3 其他框架方案对比例如 ShardingSphere除了上述手写和 MyBatis-Plus 插件方案还有一些第三方中间件支持数据源动态路由Apache ShardingSphereShardingSphere-JDBC原 Sharding-JDBC是一个开源分布式数据库中间件。它本身能作为一个提供分片能力的 JDBC 驱动支持水平分库分表、读写分离、分布式事务等。与 Spring Boot 集成时将数据源 URL 改为 jdbc:shardingsphere:... 即可使用。ShardingSphere 支持配置多数据源读写分离策略以及复杂的分表规则如提示键、标准分片算法等。不过使用 ShardingSphere 需要额外的 YAML 或 API 配置学习成本和运维成本相对较高。它更适用于大规模分库分表的场景。对于简单的读写分离或少量数据源切换可能用手写切面更轻量。官方文档说明“可以直接将 ShardingSphereDataSource 配合 ORM 框架使用”如 MyBatis。Drools / 自定义路由有些团队也会实现自定义的数据源路由插件或者使用 AOPSPI 的方式类似于 Spring 的 AbstractRoutingDataSource 变体。但这通常是极少数场景的定制方案。Spring Cloud 组件对于微服务架构还可以使用 Spring Cloud 提供的配置管理中心如 Config Server动态推送数据源配置配合自定义上下文更新。总的来说各方案优缺点为手写 Spring AOP本章介绍的方案灵活可控完全掌握实现细节适合团队有定制需求时使用。缺点是需要自行维护代码、处理边界情况。MyBatis-Plus DynamicDatasource快速上手功能丰富适合常规场景依赖第三方库升级。ShardingSphere 等中间件功能全面分库分表分布式事务可视化程度高。适合对分库分表、读写分离有复杂需求的项目。缺点是学习曲线陡峭配置复杂。在选择上如果项目只需要简单的多数据源切换且团队熟悉 Spring 技术栈那么手写或 MyBatis-Plus 即可如果项目中对分片、事务、监控要求较高可以考虑 ShardingSphere 或类似的方案。
http://www.zqtcl.cn/news/144815/

相关文章:

  • 建设部网站举报壹搜网站建设优化排名
  • 做软件界面的网站洛可可成都设计公司
  • 微信建立免费网站app网站制作软件
  • 上海工程建设造价信息网站黑帽seo易下拉霸屏
  • 网站建设公司需要申请icp吗网站续费
  • 宁波快速建站公司滕州网站设计
  • logo成品效果图网站网站意见反馈源码
  • 宁志网站两学一做高端网站建设代码
  • 企业做可信网站认证的好处电影网站制作
  • 大学网站建设课程课综温州网站推广好不好
  • 做电影ppt模板下载网站有什么网站可以做海报
  • 搭建网站需要做什么国外互动网站
  • 淘宝客导购网站怎么做建设网站天河区
  • 做网站的优势有哪些wordpress 一直崩溃
  • 长沙交互网站设计服务商优秀的网页网站设计
  • android 旅游网站开发有哪些做伦敦金的网站
  • 物流网站系统php源码seo课程多少钱
  • 手机 网站品牌网站建设 d磐石网络
  • 免费用搭建网站珠海住房和建设局网站
  • 天津做胎儿鉴定网站广州古德室内设计有限公司logo
  • 做爰的最好看的视频的网站简洁型网页
  • 网站一直显示建设中网页制作工具程
  • 苏州seo网站管理网站后台更新后主页没有变化
  • 上海网站公司电话中国电子信息网
  • 合作网站seo在哪里建网站免费
  • 需求网站自动发卡网站开发
  • 用asp做网站span电子商务网站建设的教案
  • 厦门市住房建设网站音乐主题wordpress
  • 小说网站开发文档建站公司用wordpress
  • 自己做手机版网站制作佛山网站建设企划动力