在许多应用程序中,可能需要使用多个数据库或数据源来处理不同的业务需求。Spring Boot提供了简便的方式来配置和使用多数据源,使开发人员能够轻松处理多个数据库连接。如果你的项目中可能需要随时切换数据源的话,那我这篇文章可能能帮助到你
ℹ️:这里对于pom文件中坐标的引入我就不多赘言了
配置文件
1️⃣:properties文件中
# 数据源配置 spring.datasource.mysql.primary.url=jdbc:mysql://127.0.0.1:3351/tally_book?characterEncoding=utf8&serverTimezone=UTC spring.datasource.mysql.primary.username=root spring.datasource.mysql.primary.password=123456 spring.datasource.mysql.primary.driver-class-name=com.mysql.cj.jdbc.Driver # 数据源配置 spring.datasource.mysql.slave1.url=jdbc:mysql://127.0.0.1:3351/dingding_mid?characterEncoding=utf8&serverTimezone=UTC spring.datasource.mysql.slave1.username=root spring.datasource.mysql.slave1.password=123456 spring.datasource.mysql.slave1.driver-class-name=com.mysql.cj.jdbc.Driver
上面的配置文件中我只写了两个源,而且都是mysql 的,primary和slave1就是区分
2️⃣:配置类实现多数据源配置
package com.todoitbo.tallybookdasmart.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.todoitbo.tallybookdasmart.multiDataSource.DataSourceType; import com.todoitbo.tallybookdasmart.multiDataSource.DynamicDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.*; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author xiaobo * @date 2023/5/19 */ @Configuration @Slf4j public class MultiDataSourceConfig { @Bean public PlatformTransactionManager platformTransactionManager(DataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } @Bean @Primary @DependsOn("primaryDataSource") public DataSource dynamicDataSource(@Qualifier(DataSourceType.PRIMARY) DataSource primaryDataSource, @Qualifier(DataSourceType.SECOND) DataSource secondDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 1.设置默认数据源 dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); // 2.配置多数据源 Map<Object, Object> map = new HashMap<>(); map.put(DataSourceType.PRIMARY, primaryDataSource); map.put(DataSourceType.SECOND, secondDataSource); // 3.存放数据源集 dynamicDataSource.setTargetDataSources(map); return dynamicDataSource; } @Bean(name = DataSourceType.PRIMARY) @ConfigurationProperties(prefix = "spring.datasource.mysql.primary") public DataSource primaryDataSource() { log.info("主数据库连接池创建中......."); return DruidDataSourceBuilder.create().build(); } @Bean(name = DataSourceType.SECOND) @ConfigurationProperties(prefix = "spring.datasource.mysql.slave1") public DataSource secondDataSource() { log.info("second数据库连接池创建中......."); return DruidDataSourceBuilder.create().build(); } }
3️⃣:自定义注解实现,可使用自定义注解来切换数据源
package com.todoitbo.tallybookdasmart.multiDataSource; import java.lang.annotation.*; /** * description: 自定义注解,标记数据源 * * @author bo * @version 1.0 * @date 2023/5/19 08:45 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface DataSource { String value() default DataSourceType.PRIMARY; }
4️⃣:定义一个切面类
这段代码是一个切面类DataSourceAspect
,用于在方法调用前后切换数据源。以下是代码的解释:
@Aspect
:指定该类为切面类,用于定义切面的切入点和增强逻辑。@Order(value=1)
:指定切面的执行顺序,数值越小优先级越高。@Component
:将该切面类声明为Spring的组件,使其可以被自动扫描并装配到Spring容器中。@Pointcut(value = "execution(* com.todoitbo.tallybookdasmart.service.*.*(..)) || execution(* com.todoitbo.tallybookdasmart.*.*(..))")
:定义切入点表达式,指定需要切入的目标方法。@Around("dataSourcePointCut()")
:定义环绕通知,表示在目标方法执行前后执行切面逻辑。public Object around(ProceedingJoinPoint joinPoint) throws Throwable
:环绕通知方法,包含切面逻辑。- 在方法中通过反射获取目标方法的注解信息,判断是否存在
@DataSource
注解,并获取注解中设置的数据源名称。 - 调用
DataSourceContextHolder.setDataSource(dataSource)
方法,将获取到的数据源名称设置到当前线程的上下文中。 - 调用
joinPoint.proceed()
方法,继续执行目标方法。 - 在
finally
块中调用DataSourceContextHolder.clearDataSourceType()
方法,清除当前线程中存储的数据源信息。
package com.todoitbo.tallybookdasmart.multiDataSource; import lombok.extern.slf4j.Slf4j; 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.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author xiaobo */ @Aspect @Order(value=1) @Component @Slf4j public class DataSourceAspect { /** 定义切入点表达式*/ @Pointcut(value = "execution(* com.todoitbo.tallybookdasmart.service.*.*(..)) || execution(* com.todoitbo.tallybookdasmart.*.*(..))") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object target = joinPoint.getTarget(); String method = joinPoint.getSignature().getName(); Class<?> classz = target.getClass(); Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes(); try { // 使用反射获取目标类中指定方法名和参数类型的方法对象 Method m = classz.getMethod(method, parameterTypes); // 设置默认的数据源名称 String dataSource = DataSourceType.PRIMARY; // 判断方法是否被@DataSource注解标记。 if (m.isAnnotationPresent(DataSource.class)) { // 通过getAnnotation()方法获取方法上的@DataSource注解对象。 DataSource ds = m.getAnnotation(DataSource.class); // 获取注解对象中设置的数据源名称 dataSource = ds.value(); } // 将获取到的数据源名称设置到当前线程的上下文中 DataSourceContextHolder.setDataSource(dataSource); // 继续执行目标方法 return joinPoint.proceed(); } finally { DataSourceContextHolder.clearDataSourceType(); } } }
5️⃣:存储和获取当前线程数据源的上下文工具类
这段代码是一个用于存储和获取当前线程数据源的上下文工具类。它使用了Netty的FastThreadLocal
来实现线程本地的快速存取。
- 创建
FastThreadLocal
对象:在类中定义了一个名为CONTEXT_HOLDER
的FastThreadLocal
对象,用于存储当前线程的数据源信息。 - 设置数据源:
setDataSource
方法用于将数据源名称设置到当前线程的上下文中。通过调用CONTEXT_HOLDER.set(dataSource)
,将数据源名称存储在当前线程中。 - 获取数据源:
getDataSource
方法用于从当前线程的上下文中获取数据源名称。通过调用CONTEXT_HOLDER.get()
,可以获取当前线程的数据源名称。 - 清除数据源:
clearDataSourceType
方法用于清除当前线程中存储的数据源信息。通过调用CONTEXT_HOLDER.remove()
,可以清除当前线程中的数据源信息。
package com.todoitbo.tallybookdasmart.multiDataSource; import io.netty.util.concurrent.FastThreadLocal; /** * description: 存储和获取当前线程数据源的上下文工具类 * * @author bo * @version 1.0 * @date 2023/5/19 08:44 */ public class DataSourceContextHolder { /** * 创建FastThreadLocal对象,存储当前线程的数据源信息 */ private static final FastThreadLocal<String> CONTEXT_HOLDER = new FastThreadLocal<String>(); /** * 设置数据源 */ public static void setDataSource(String dataSource) { CONTEXT_HOLDER.set(dataSource); } /** * 获取数据源 */ public static String getDataSource() { return CONTEXT_HOLDER.get(); } /** * 清除数据源 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } }
6️⃣:数据源类型
package com.todoitbo.tallybookdasmart.multiDataSource; /** * @author xiaobo */ public class DataSourceType { public static final String PRIMARY = "primaryDataSource"; public static final String SECOND = "secondDataSource"; }
7️⃣:根据当前线程中的数据源上下文获取对应的数据源
package com.todoitbo.tallybookdasmart.multiDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * description: 根据当前线程中的数据源上下文获取对应的数据源。 * * @author bo * @version 1.0 * @date 2023/5/19 08:46 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
具体实现
在service的实现类的方法上加入注解即可
package com.todoitbo.tallybookdasmart.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.todoitbo.tallybookdasmart.entity.TbConfig; import com.todoitbo.tallybookdasmart.mapper.TbConfigMapper; import com.todoitbo.tallybookdasmart.multiDataSource.DataSource; import com.todoitbo.tallybookdasmart.multiDataSource.DataSourceType; import com.todoitbo.tallybookdasmart.service.ITbConfigService; import com.todoitbo.tallybookdasmart.service.base.BaseServiceImpl; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * (TbConfig)服务 * * @author bo * @since 2023-04-18 21:13:14 */ @Service public class TbConfigServiceImpl extends BaseServiceImpl<TbConfigMapper,TbConfig> implements ITbConfigService { @Resource protected TbConfigMapper mapper; @Override @DataSource(DataSourceType.SECOND) public List<TbConfig> testList() { return mapper.selectList(new QueryWrapper<>()); } }
效果图:
⚠️:这里只是想展示他确实是走了从数据源了