注:数据源切换不可在事务内部、同一事务中数据源确定、不可切换!
亦可说数据源切换必须在事务处理之前!
一、功能介绍
在实际的开发中,同一个项目中使用多个数据源是很常见的场景。最近在项目中正好使用到多数据源、搭建完成后记录一下!可供参考。
二、配置文件
application.yml中添加多数据源配置
spring: application: name: businesscooperation-server-dev main: allow-bean-definition-overriding: true jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: type: com.alibaba.druid.pool.DruidDataSource # 多数据源 配置 datasource: defaultName: master driverClassName: com.mysql.cj.jdbc.Driver # 多数据源配置数据源名称 multipleDataSources: master,supplier master: url: jdbc:mysql://ip:3306/mboms_mall?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai username: xxx password: xxx supplier: url: jdbc:mysql://ip:3306/mbproduct_mall?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai username: xxx password: xxx三、java处理
1、自定义数据源配置DBProperties.java
package com.hbis.mysql.config; import com.alibaba.druid.pool.DruidDataSource; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 自定义数据源配置 * * @author henry * @date 2020/6/19 14:34 */ @Data @Component public class DBProperties { @Resource private Environment env; @Value("#{'${datasource.multipleDataSources}'.split(',')}") private List<String> multipleDataSources; @Value("${datasource.driverClassName}") private String driverClassName; @Value("${datasource.defaultName}") private String defaultName; /** * 数据源存储 */ private Map<Object, Object> dataSources = new HashMap<>(); private DruidDataSource initDataSource(String url, String username, String password) { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } /** * 初始化自定义数据源集 */ void init() { multipleDataSources.forEach(r -> dataSources.put(r, initDataSource(env.getProperty("datasource." + r + ".url"), env.getProperty("datasource." + r + ".username"), env.getProperty("datasource." + r + ".password")))); } /** * 获得默认的数据源 Get the default data source * * @return DruidDataSource */ DruidDataSource getDefaultDataSource() { return (DruidDataSource) this.dataSources.get(defaultName); } }2、数据源配置,将自定义的所有的数据源传给数据源路由器
package com.hbis.mysql.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.annotation.Resource; import javax.sql.DataSource; /** * 数据源配置,将自定义的所有的数据源传给数据源路由器 * * @author henry * @date 2020/6/19 14:33 */ @Configuration public class DataSourceConfig { @Resource private DBProperties dbProperties; /** * 配置数据源 Configure data sources * * @return DataSource */ @Bean(name = "dataSource") public DataSource dataSource() { //初始化自定义数据源集 dbProperties.init(); // 采用AbstractRoutingDataSource的对象包装多数据源 DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(dbProperties.getDataSources()); // 设置默认的数据源,当拿不到指定数据源或者未指定数据源时使用该配置 dataSource.setDefaultTargetDataSource(dbProperties.getDefaultDataSource()); return dataSource; } /** * 配置事务管理 Configuration transaction management * * @return PlatformTransactionManager */ @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } }3、动态数据源
package com.hbis.mysql.config; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 动态数据源 Dynamic data source * * @author henry * @date 2020/6/19 14:36 */ public class DynamicDataSource extends AbstractRoutingDataSource { //数据源路由器,获取最终被执行的数据源 @Override protected Object determineCurrentLookupKey() { //从本地线程中获取最终被执行的数据源名称 return DynamicDataSourceHolder.getDataSource(); } }4、动态数据源持有者,负责利用ThreadLocal存取本线程使用的数据源的名称
package com.hbis.mysql.config; /** * 动态数据源持有者,负责利用ThreadLocal存取本线程使用的数据源的名称 * * @author henry * @date 2020/7/1 10:56 */ public class DynamicDataSourceHolder { /** * 本地线程共享对象 */ private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); public static void putDataSource(String name) { THREAD_LOCAL.set(name); } static String getDataSource() { return THREAD_LOCAL.get(); } /** * 清除本线程指定的数据源使用默认数据源 */ public static void clear() { THREAD_LOCAL.remove(); } }5、基于aop拦截 注解/java类
package com.hbis.mysql.aop; import com.hbis.annotation.DataSource; import com.hbis.mysql.config.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; /** * 数据源切换控制切面 * * @auther: henry */ @Component @Aspect @Order(0) //保证先被执行 @Slf4j public class DataSourceAspect { /** * 注解拦截切面表达式 * * @annotation 用于拦截所有被该注解标注的方法 * @within 用于拦截被所有该注解标注的类 */ @Pointcut("@annotation(com.hbis.annotation.DataSource) || @within(com.hbis.annotation.DataSource) ") public void pointcut() { } @Before("pointcut()") public void annotationMethodBefore(JoinPoint joinPoint) { Class<?> clazz = joinPoint.getTarget().getClass(); DataSource annotation = clazz.getAnnotation(DataSource.class); //先判断类上是否有DataSource注解,如果没有在判断方法上是否有注解 if (annotation == null) {//类上没有 //获取方法上的注解 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); annotation = method.getAnnotation(DataSource.class); //如果还是为null则退出,这次方法调用将使用默认的数据源 if (annotation == null) { return; } } //获取注解上得值 String dataSourceName = annotation.value(); log.info("---------------------------切换到数据源:" + dataSourceName + "----------------------------------"); //因为有默认数据源的存在,所以不用担心注解上的值无对应的数据源,当找不到指定数据源时,会使用默认的数据源 DynamicDataSourceHolder.putDataSource(dataSourceName); } //执行完切面后,将线程共享中的数据源名称清空 让程序使用默认数据源 @After("pointcut()") public void annotationMethodAfter(JoinPoint joinPoint) { DynamicDataSourceHolder.clear(); } //---------------------------------基于AOP,拦截某种\某个类------------------------------------------------------------- @Pointcut("execution( * com.hbis.framework.service.impl.*.*(..))") public void pointcut2() { } @Before("pointcut2()") public void dataSourcePointCut(JoinPoint joinPoint) { Class<?> aClass = joinPoint.getTarget().getClass(); DataSource annotation = aClass.getAnnotation(DataSource.class); //取出类上的注解,并将ThreadLocal 中的数据源设置为指定的数据源, 当然 也可以按照业务需要不适用注解,直接固定某一数据源 if (annotation != null) { String dataSource = annotation.value(); DynamicDataSourceHolder.putDataSource(dataSource); } } @After("pointcut2()") public void annotationMethodAfter1(JoinPoint joinPoint) { DynamicDataSourceHolder.clear(); } }6、自定义注解
package com.hbis.annotation; import com.hbis.constants.DBConstants; import java.lang.annotation.*; /** * 用以切换数据源的注解 * * @author henry * @date 2020/6/19 14:14 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface DataSource { String value() default DBConstants.DATASOURCE_MASTER; }7、数据库使用常量类
package com.hbis.constants; public class DBConstants { /** * 数据源分组 */ public static final String DATASOURCE_SUPPLIER = "supplier"; /** * 数据源分组 */ public static final String DATASOURCE_MASTER = "master"; }8、使用示列 基于注解!两种方式二选一即可!
/* * Copyright (c) 2018-2022 henry, . */ package com.hbis.businesscooperationmanager.service.impl; import com.hbis.annotation.DataSource; import com.hbis.businesscooperationmanager.dao.SupplierInfoDao; import com.hbis.businesscooperationmanager.model.dto.BoSupplierContractVO; import com.hbis.businesscooperationmanager.model.entity.SupplierInfo; import com.hbis.businesscooperationmanager.service.SupplierInfoService; import com.hbis.constants.DBConstants; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; /** * <p> * 供应商信息管理表 服务实现类 * </p> * * @author henry */ @Service @DataSource(DBConstants.DATASOURCE_SUPPLIER) public class SupplierInfoServiceImpl implements SupplierInfoService { @Override @DataSource(DBConstants.DATASOURCE_SUPPLIER) public Map<String, SupplierInfo> querySupplierInfoByIds(List<String> ids) { return baseDao.querySupplierInfoByIds(ids); } }