SpringBoot 多数据源配置分析

SpringBoot 多数据源配置分析

  • 前言
  • application.properties 方式配置
  • bean.xml 方式配置
  • 多数据源设置默认数据源
  • 动态数据源切换
  • 注解动态切换数据源
  • 断点跟踪源码验证切换

前言

我们配置主从数据源的时候,就需要多数据源配置。

application.properties 方式配置

#数据库连接 Master
spring.datasource.master.username=root
spring.datasource.master.password=12345678
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3308/h5_db?characterEncoding=utf-8
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接 Slave
spring.datasource.slave.username=user
spring.datasource.slave.password=12345678
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3307/h5_db?characterEncoding=utf-8
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver

代码

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Primary
    @Bean
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.Master, masterDataSource);
        dataSourceMap.put(DataSourceType.Slave, slaveDataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(dataSourceMap);
        dataSource.setDefaultTargetDataSource(masterDataSource());
        dataSource.afterPropertiesSet();
        return dataSource;
    }
}

bean.xml 方式配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="masterDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl"
                  value="jdbc:mysql://localhost:3308/h5_db?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="12345678"/>
    </bean>

    <bean id="slaveDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl"
                  value="jdbc:mysql://localhost:3307/h5_db?characterEncoding=utf-8"/>
        <property name="username" value="user"/>
        <property name="password" value="12345678"/>
    </bean>


    <bean id="dataSource" class="com.ysp.template.route.DynamicDataSource" primary="true">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="master" value-ref="masterDataSource"/>
                <entry key="slave" value-ref="slaveDataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource"/>
    </bean>
    
</beans>

多数据源设置默认数据源

此外使用primary="true"设置默认数据源为动态数据源。SpringBoot自动装配SqlSessionFactory和DataSourceTransactionManager时注入的都是动态数据源。就不需要代码配置。
重点:配置多数据源强烈建议过滤数据源自动配置
过滤代码

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

SqlSessionFactory配置
xml方式

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
        <property name="typeAliasesPackage" value="com.ysp.template.pojo.dao"/>
        <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
    </bean>

java方式

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        System.out.println("=>>>=======>: " + dataSource.getClass().getName());
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
        bean.setTypeAliasesPackage("com.ysp.template.pojo.dao");
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
        return bean.getObject();
    }

动态数据源切换

重点:ThreadLocal 和 AbstractRoutingDataSource 使用

AbstractRoutingDataSource 源码

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
    // 获取数据库连接
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }
    // 切换数据源
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }
    // 重新这个方法,返回数据源标示Key就可以动态切换
    @Nullable
    protected abstract Object determineCurrentLookupKey();    

代码

public class DataSourceHandler {

    private static ThreadLocal<String> datasourceContext = new ThreadLocal<>();

    public static void switchDataSource(String datasource) {
        System.out.println("switchDataSource: " + datasource);
        datasourceContext.set(datasource);
    }

    public static String getDataSource() {
        return datasourceContext.get();
    }

    public static void clear() {
        datasourceContext.remove();
    }
}
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        //System.out.println(">>>>>>>>>>>>>: determineCurrentLookupKey");
        return DataSourceHandler.getDataSource();
    }
}
@Target({METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSource {
    String value() default "";
}
@Aspect
@Component
@Order(1) // 重点:当使用事务注解(@Transactional)的时候,切换数据源aop要优先事务执行。
public class DataSourceAspect {
    
    @Pointcut("@annotation(com.ysp.template.annotation.DataSource)")
    public void pointcut() {
    }

    @Before(value = "pointcut()")
    public void switchDataSource(JoinPoint joinPoint) {

        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        DataSource annotation = method.getAnnotation(DataSource.class);
        DataSourceHandler.switchDataSource(annotation.value());
    }

    @After(value = "pointcut()")  // 必须加@After
    public void clear(){
        DataSourceHandler.clear();
    }
}

注解动态切换数据源

    @Transactional
    @DataSource(DataSourceType.Master)
    public String addProject(ProjectDo projectDo, List<ProjectMemberDo> projectMembers) {
        return addProjectPrivate(projectDo, projectMembers);
    }

断点跟踪源码验证切换

1、首先是AbstractRoutingDataSource类

2、DataSourceTransactionManager

org.springframework.jdbc.datasource.DataSourceTransactionManager
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        Connection con = null;
    // 中间省略了,主要逻辑,事务获取数据源连接。此处需要断点。
    }

3、DataSourceTransactionManagerAutoConfiguration
1、事务自动配置类:我们在这里打断点查看数据源是否是动态数据源(DynamicDataSource)
2、如果我们代码自己配置事务建议过滤调事务自动配置

@SpringBootApplication(exclude = {DataSourceTransactionManagerAutoConfiguration.class})

源码

    @ConditionalOnSingleCandidate(DataSource.class)
    static class DataSourceTransactionManagerConfiguration {
        DataSourceTransactionManagerConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean({PlatformTransactionManager.class})
        DataSourceTransactionManager transactionManager(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
            // DataSource dataSource 是否为DynamicDataSource
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
            transactionManagerCustomizers.ifAvailable((customizers) -> {
                customizers.customize(transactionManager);
            });
            return transactionManager;
        }
    }

点赞

你可能感兴趣的:(Spring,Boot)