springboot整合mysql多数据源

说明

基于springboot+jdk8
使用DataSource+切面的方式实现数据库动态切换,不影响程序正常数据库的操作,在此不做过多介绍,直接代码走起, 深入的东西可交流

application.yml配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test1
    username: root
    password: 123456
    hikari:
      idle-timeout: 570000
      max-lifetime: 570000

report:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test2
    username: root
    password: 123456
    hikari:
      idle-timeout: 570000
      max-lifetime: 570000

代码实现

注解

通过注解的方式实现动态切换,也可以通过不同的包名进行拦截切换
/**
 * @author huheng
 * @date 2020/06/16
 **/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}
/**
 * @author huheng
 * @date 2020/06/16
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
/**
 * @author huheng
 * @date 2020/06/16
 **/
@Data
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceIds = ListUtils.newArrayList();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }

}
数据源配置注册
/**
 * 动态数据源配置
 * @author huheng
 * @date 2020/06/16
 **/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
    private DataSource defaultDataSource;
    private Map<String, DataSource> customDataSources = MapUtils.newHashMap();

    @Override
    public void setEnvironment(Environment environment) {
        initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }

    private void initDefaultDataSource(Environment env) {
        Iterable<ConfigurationPropertySource> configurationPropertySources = ConfigurationPropertySources.get(env);
        Binder binder = new Binder(configurationPropertySources);
        BindResult<Properties> bindResult = binder.bind("spring.datasource", Properties.class);
        Properties properties = bindResult.get();
        defaultDataSource = buildDataSource(properties);
    }

    private void initCustomDataSources(Environment env) {
        Iterable<ConfigurationPropertySource> configurationPropertySources = ConfigurationPropertySources.get(env);
        Binder binder = new Binder(configurationPropertySources);
        BindResult<Properties> bindResult = binder.bind("report.datasource", Properties.class);
        Properties properties = bindResult.get();
        // 此处保存单数据源  如有需求 此处可更改为多自定义数据源配置
        DataSource ds = buildDataSource(properties);
        if (ObjectUtils.isNull(ds)) {
            bindResult = binder.bind("spring.datasource", Properties.class);
            properties = bindResult.get();
            ds = buildDataSource(properties);
        }
        customDataSources.put("report", ds);
    }

    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Properties properties) {
        Object type = properties.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
        }
        Class<? extends DataSource> dataSourceType;
        try {
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = properties.get("driver-class-name").toString();
            String url = properties.get("url").toString();
            String username = properties.get("username").toString();
            String password = properties.get("password").toString();
            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = MapUtils.newHashMap();
        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.dataSourceIds.add(key);
        }
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }

}
通过切面的方式拦截,实现数据源动态切换
/**
 * @author huheng
 * @date 2020/06/16
 **/

@Aspect
@Order(-10) // 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
    public static Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
        String dsId = targetDataSource.value();
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            SystemLogger.wrap(logger).info("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + point.getSignature());
        } else {
            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
        }
    }

    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        DynamicDataSourceContextHolder.clearDataSourceType();
    }

}
启动类上加此注解
@Import(DynamicDataSourceRegister.class)
Spring Boot提供了很方便的方式来整合多数据源。以下是一些步骤: 1. 在`application.properties`或`application.yml`文件中配置多个数据源的连接信息。例如: ```properties # 第一个数据源 spring.datasource.url=jdbc:mysql://localhost/db1 spring.datasource.username=username1 spring.datasource.password=password1 # 第二个数据源 spring.second-datasource.url=jdbc:mysql://localhost/db2 spring.second-datasource.username=username2 spring.second-datasource.password=password2 ``` 2. 创建多个数据源的配置类。可以使用`@ConfigurationProperties`注解将配置文件中的属性绑定到配置类上。例如: ```java @Configuration @ConfigurationProperties(prefix = "spring.second-datasource") public class SecondDataSourceConfig { private String url; private String username; private String password; // Getters and setters // ... } ``` 3. 创建多个数据源的`DataSource` bean。可以使用`DataSourceBuilder`来创建数据源。例如: ```java @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource secondDataSource(SecondDataSourceConfig config) { return DataSourceBuilder.create() .url(config.getUrl()) .username(config.getUsername()) .password(config.getPassword()) .build(); } } ``` 4. 配置多个`JdbcTemplate` bean,每个bean使用不同的数据源。例如: ```java @Configuration public class JdbcTemplateConfig { @Bean public JdbcTemplate primaryJdbcTemplate(DataSource primaryDataSource) { return new JdbcTemplate(primaryDataSource); } @Bean public JdbcTemplate secondJdbcTemplate(DataSource secondDataSource) { return new JdbcTemplate(secondDataSource); } } ``` 现在,你可以在需要使用不同数据源的地方注入相应的`JdbcTemplate`,并使用它来执行数据库操作了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值