SpringBoot 多數據源配置


主要利用AOP+ThreadLocal+自定義注釋實現注釋切換

 

pom.xml

<!-- springboot-aop包,AOP切面注解,Aspectd等相關注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AbstractRoutingDataSource
只支持單庫事務,也就是說切換數據源要在開啟事務之前執行。 
spring DataSourceTransactionManager進行事務管理,開啟事務,會將數據源緩存到DataSourceTransactionObject對象中進行后續的commit rollback等事務操作。

這里主要看AbstractRoutingDataSource.java源碼:

配置的多個數據源會放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通過afterPropertiesSet()方法將數據源分別進行復制到resolvedDataSources和resolvedDefaultDataSource中(AbstractAutowireCapableBeanFactory-->InitializingBean.afterPropertiesSet()  在創建bean的時候反射調用)
AbstractRoutingDataSource的getConnection()的方法的時候,先調用determineTargetDataSource()方法返回DataSource
(JdbcTemplate.execute()->DataSourceUtils.getConnection->dataSource.getConnection()->AbstractRoutingDataSource實現了了DataSource)

 

標黃部分:根據Key獲取對應數據源。

這里新建類繼承AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDB();
    }
}

 

 這里新建類,ThreadLocal用於存放數據源

public class DataSourceContextHolder {

    /**
     * 默認數據源
     */
    public static final String DEFAULT_DS = "chenDataSource";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 設置數據源名
    public static void setDB(String dbType) {
        System.out.println("切換到{" + dbType + "}數據源");
        contextHolder.set(dbType);
    }

    // 獲取數據源名
    public static String getDB() {
        return (contextHolder.get());
    }

    // 清除數據源名
    public static void clearDB() {
        contextHolder.remove();
    }
}

說明:

此處,會疑惑會不會線程不安全啊,那么就要看看ThreadLocal.java的源碼實現了。
ThreadLocal:

用於保存某個線程共享變量:對於同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變量,而不會影響其他線程的變量。

1、ThreadLocal.get: 獲取ThreadLocal中當前線程共享變量的值。

2、ThreadLocal.set: 設置ThreadLocal中當前線程共享變量的值。

3、ThreadLocal.remove: 移除ThreadLocal中當前線程共享變量的值。

4、ThreadLocal.initialValue: ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法后調用get方法,返回此方法值。

工作原理:

1、Thread類中有一個成員變量屬於ThreadLocalMap類(一個定義在ThreadLocal類中的內部類),它是一個Map,他的key是ThreadLocal實例對象。

2、當為ThreadLocal類的對象set值時,首先獲得當前線程的ThreadLocalMap類屬性,然后以ThreadLocal類的對象為key,設定value。get值時則類似。

3、ThreadLocal變量的活動范圍為某線程,是該線程“專有的,獨自霸占”的,對該變量的所有操作均由該線程完成!也就是說,ThreadLocal 不是用來解決共享對象的多線程訪問的競爭問題的,因為ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。當線程終止后,這些值會作為垃圾回收。

4、由ThreadLocal的工作原理決定了:每個線程獨自擁有一個變量,並非是共享的。

 

import com.paic.phssp.springtest.dataSource.DynamicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean(name = "chenDataSource")
    @Qualifier("chenDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.chen")
    public DataSource chenDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "quartzds")
    @Qualifier("quartzds")
    @ConfigurationProperties(prefix = "spring.datasource.quartzds")
    public DataSource quartzDSDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 動態數據源: 通過AOP在不同數據源之間動態切換
     *
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默認數據源
        dynamicDataSource.setDefaultTargetDataSource(chenDataSource());
        // 配置多數據源
        Map<Object, Object> dsMap = new HashMap();
        dsMap.put("chenDataSource", chenDataSource());
        dsMap.put("quartzds", quartzDSDataSource());

        dynamicDataSource.setTargetDataSources(dsMap); return dynamicDataSource;
    }

    /**
     * 配置@Transactional注解事物
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

自定義注釋

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface IDataSource {
    String value() default "chenDataSource";
}

AOP從注釋中獲取數據源key,然后通過AbstractRoutingDataSource.determineTargetDataSource()獲取數據源

 

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.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 自定義注解 + AOP的方式實現數據源動態切換。
 * Created by pure on 2018-05-06.
 */
    
@Component
public class DynamicDataSourceAspect {
    
    @Before("@annotation(IDataSource)")
    public void beforeSwitchDS(JoinPoint point) {
        //獲得當前訪問的class
        Class<?> className = point.getTarget().getClass();
        //獲得訪問的方法名
        String methodName = point.getSignature().getName();
        //得到方法的參數的類型
        Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
        String dataSource = DataSourceContextHolder.DEFAULT_DS;
        try {
            // 得到訪問的方法對象
            Method method = className.getMethod(methodName, argClass);
            // 判斷是否存在@IDataSource注解
            if (method.isAnnotationPresent(IDataSource.class)) {
                IDataSource annotation = method.getAnnotation(IDataSource.class);
                // 取出注解中的數據源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 切換數據源
        DataSourceContextHolder.setDB(dataSource);
    }

    @After("@annotation(IDataSource)")
    public void afterSwitchDS(JoinPoint point) {
        //清楚數據源
        DataSourceContextHolder.clearDB();
    }
}

應用:

import com.paic.phssp.springtest.dao.UserMapper;
import com.paic.phssp.springtest.dataSource.IDataSource;
import com.paic.phssp.springtest.dto.User;
import com.paic.phssp.springtest.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class IUserServiceImpl implements IUserService {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private UserMapper userMapper;

    @Override
    @IDataSource(value="chenDataSource")
    public List<User> findAll() {
        return userMapper.getAll();
    }
}

運行結果:

 

總結:

上面叨叨了那么多,其實重點:AOP+注解 結構。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM