多数据源配置
前面我们配置过单个数据源了,本节讲解下如何实现多数据源的动态切换(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(); } }
时刻与技术进步,每天一点滴,日久一大步!!!
本博客只为记录,用于学习,如有冒犯,请私信于我。