多数据源配置

前面我们配置过单个数据源了,本节讲解下如何实现多数据源的动态切换(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();
        }
    }

 

posted @ 2020-07-18 20:41  codedot  阅读(1207)  评论(1编辑  收藏  举报