多數據源配置


前面我們配置過單個數據源了,本節講解下如何實現多數據源的動態切換(c3p0和druid)。

修改下數據源的連接,使其不屬於同一個數據庫:

# c3p0.properties
c3p0.jdbc.jdbcUrl=jdbc:mysql://localhost:3305/spring?useSSL=false&characterEncoding=UTF-8

# druiddb.properties
druid.jdbc.url=jdbc:mysql://127.0.0.1:3305/db_link?characterEncoding=utf-8&useSSL=false
 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

AbstractRoutingDataSource本身實現了javax.sql.DataSource接口(由其父類抽象類AbstractDataSource實現),因此其實際上也是一個標准數據源的實現類。該類是Spring專為多數據源管理而增加的一個接口層。它根據一個數據源唯一標識key來尋找已經配置好的數據源隊列,它通常是與當前線程綁定在一起的。

AbstractRoutingDataSource的內部維護了一個名為targetDataSources的Map,並提供的setter方法用於設置數據源關鍵字與數據源的關系,實現類被要求實現其determineCurrentLookupKey()方法,由此方法的返回值決定具體從哪個數據源中獲取連接。

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

/**
 * DynamicDataSource類繼承了Spring的抽象類AbstractRoutingDataSource,
 * 而AbstractRoutingDataSource本身實現了javax.sql.DataSource接口(由其父類抽象類AbstractDataSource實現),
 * 因此其實際上也是一個標准數據源的實現類。該類是Spring專為多數據源管理而增加的一個接口層。
 * 它根據一個數據源唯一標識key來尋找已經配置好的數據源隊列,它通常是與當前線程綁定在一起的。
 *
 * AbstractRoutingDataSource的內部維護了一個名為targetDataSources的Map,
 * 並提供的setter方法用於設置數據源關鍵字與數據源的關系,實現類被要求實現其determineCurrentLookupKey()方法,
 * 由此方法的返回值決定具體從哪個數據源中獲取連接。
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DynamicDataSourceContextHolder.getDataSourceType();
        System.out.println("當前數據源:" + dataSource);
        return dataSource;
    }

}
設置數據源的工具類DynamicDataSourceContextHolder 
public class DynamicDataSourceContextHolder {

    //存放當前線程使用的數據源類型信息
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    //存放數據源id
    public static List<String> dataSourceIds = new ArrayList();

    //設置數據源
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

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

    //清除數據源
    public static void clearDataSourceType() {
        contextHolder.remove();
        System.out.println("清除數據源:" + contextHolder.get());
    }

    //判斷當前數據源是否存在
    public static boolean isContainsDataSource(String dataSourceId) {
        return dataSourceIds.contains(dataSourceId);
    }
}
創建動態數據源和JdbcTemplate

在Spring根容器(SpringConfig)中使用@Bean創建動態數據源和JdbcTemplate。

/**
 * 這個是Spring容器,相當於ApplicationContext.xml,負責掃描相關的service和dao,排除controller的掃描
 * 數據源、事務等均在這里配置
 */
@ComponentScan(value = {"com.codedot"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {RestController.class})})
@Import({C3p0DBConfig.class, DruidDBConfig.class})
@PropertySource(value = {"classpath:c3p0.properties", "classpath:druiddb.properties"}) //讀取resources下的配置文件
@Configuration
public class SpringConfig {


    @Bean
    public JdbcTemplate jdbcTemplate(DynamicDataSource dynamicDataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dynamicDataSource);
        return jdbcTemplate;
    }
    
    // 前提:需要將DruidDataSource和ComboPooledDataSource的單數據源,可以查看單數據源的配置方式
    @Bean
    public DynamicDataSource dynamicDataSource(DruidDataSource druidDataSource, ComboPooledDataSource c3p0DataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(c3p0DataSource);
        Map<Object, Object> targetDataSourceMap = new HashMap();
        targetDataSourceMap.put("defaultTargetDataSource", c3p0DataSource);
        targetDataSourceMap.put("druidTargetDataSource", druidDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSourceMap);
        return dynamicDataSource;
    }
}
使用AOP攔截JdbcTemplate方法

注意:需要在Spring根容器(SpringConfig)添加類注解@EnableAspectJAutoProxy(proxyTargetClass = true)來開啟aop。

/**
 * 這里使用JdbcTemplate操作數據庫,所以選擇JdbcTemplate的方法作為切入點
 */
@Component
@Order(-1)//這里一定要保證在@Transactional之前執行
@Aspect
public class TemplateDbAspect {

    @Before("execution(* org.springframework.jdbc.core.JdbcTemplate.update*(..)) || execution(* org.springframework.jdbc.core.JdbcTemplate.batchUpdate(..))")
    public void setMasterDb(){
        System.out.println("master db");
        DynamicDataSourceContextHolder.setDataSourceType("defaultTargetDataSource");
    }

    @Before("execution(* org.springframework.jdbc.core.JdbcTemplate.query*(..)) || execution(* org.springframework.jdbc.core.JdbcTemplate.execute(..))")
    public void setSlaveDb(){
        System.out.println("slave db");
        DynamicDataSourceContextHolder.setDataSourceType("druidTargetDataSource");
    }
}
單元測試
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
        @ContextConfiguration(classes = SpringConfig.class),
        @ContextConfiguration(classes = SpringMVCConfig.class)
})
public class DbTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void test() throws SQLException {
        //DynamicDataSourceContextHolder.setDataSourceType("defaultTargetDataSource");
        System.out.println(jdbcTemplate.getDataSource());
        jdbcTemplate.update("update sp_user set name = 'codedot' where id = 5");
        //DynamicDataSourceContextHolder.setDataSourceType("druidTargetDataSource");
        Map<String, Object> userMap = jdbcTemplate.queryForMap("select * from fm_user where id = 1");
        System.out.println(jdbcTemplate.getDataSource());
        System.out.println(userMap.get("name"));
    }
}

有時我們需要在自定義的方法上動態切入數據源,可以定義一個注解,使用aop在方法進入前解析注解,獲取注解中指定的數據源key,並進行設置,來達到動態切換的效果。

如果你是使用JdbcTemplate操作數據庫,請注意:在存在不同數據源切換的方法中添加了事務注解(即使有加了分布式事務),也只能管理默認的數據源的事務,因為在事務開啟后,JdbcTemplate會緩存連接,獲取到的都是前面的連接。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
            logger.debug("Fetching JDBC Connection from DataSource");
            Connection con = fetchConnection(dataSource);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                logger.debug("Registering transaction synchronization for JDBC Connection");
                ConnectionHolder holderToUse = conHolder;
                if (conHolder == null) {
                    holderToUse = new ConnectionHolder(con);
                } else {
                    conHolder.setConnection(con);
                }

                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            }

            return con;
        } else {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }

            return conHolder.getConnection();
        }
    }

 


免責聲明!

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



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