1. 程式人生 > >14.玩轉Spring Boot 多資料來源

14.玩轉Spring Boot 多資料來源

玩轉Spring Boot 多資料來源

      在專案中有的時候需要用到多個數據源,有個問題就是單資料來源的事務是沒有問題的,多資料來源是會存在事務問題的。這裡不做事務講解,事務可以用JTA分散式事務,也可以用MQ。具體不做敘述,接下來說如何實現多資料來源並且使用AOP來切換。

1.在pom中加入以下依賴:

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

2.application.properties內容如下:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

spring.datasource.driver-class-name1=com.mysql.jdbc.Driver
spring.datasource.url1=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf8
spring.datasource.username1=root
spring.datasource.password1=root

#最小連線數量
spring.datasource.minIdle=2
#最大連線數量
spring.datasource.maxActive=5
#獲取連線等待超時的時間
spring.datasource.maxWait=60000
#間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#連線在池中最小生存的時間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#驗證SQL
spring.datasource.validationQuery=SELECT 'x' FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
#開啟PSCache,並且指定每個連線上PSCache的大小如果用Oracle,
#則把poolPreparedStatements配置為true,mysql可以配置為false。分庫分表較多的資料庫,建議配置為false。
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#配置監控統計攔截的filters
spring.datasource.filters=stat

3.MybatisConfig程式碼如下:

package com.chengli.springboot.dynamicds.config;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.alibaba.druid.pool.DruidDataSource;
import com.chengli.springboot.dynamicds.dynmic.DynmicDataSource;

@Configuration
@MapperScan(basePackages = { "com.chengli.springboot.dynamicds" }, annotationClass = Mapper.class) // 定義掃描的ROOT包,以及註解
@EnableTransactionManagement // 開啟註解事務
public class MybatisConfig {
	@Autowired
	private DruidConfigProperties druidConfigProperties;

	@Primary//設定為主要的,當同一個型別存在多個Bean的時候,spring 會預設注入以@Primary註解的bean
	@Bean(initMethod = "init", destroyMethod = "close")
	public DataSource springbootDataSource() throws SQLException {
		DruidDataSource druidDataSource = new DruidDataSource();
		druidDataSource.setDriverClassName(druidConfigProperties.getDriverClassName());
		druidDataSource.setUrl(druidConfigProperties.getUrl());
		druidDataSource.setUsername(druidConfigProperties.getUsername());
		druidDataSource.setPassword(druidConfigProperties.getPassword());
		druidDataSource.setInitialSize(druidConfigProperties.getMinIdle());
		druidDataSource.setMinIdle(druidConfigProperties.getMinIdle());
		druidDataSource.setMaxActive(druidConfigProperties.getMaxActive());
		druidDataSource.setMaxWait(druidConfigProperties.getMaxWait());
		druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigProperties.getTimeBetweenEvictionRunsMillis());
		druidDataSource.setMinEvictableIdleTimeMillis(druidConfigProperties.getMinEvictableIdleTimeMillis());
		druidDataSource.setValidationQuery(druidConfigProperties.getValidationQuery());
		druidDataSource.setTestWhileIdle(druidConfigProperties.getTestWhileIdle());
		druidDataSource.setTestOnBorrow(druidConfigProperties.getTestOnBorrow());
		druidDataSource.setTestOnReturn(druidConfigProperties.getTestOnReturn());
		druidDataSource.setPoolPreparedStatements(druidConfigProperties.getPoolPreparedStatements());
		druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigProperties.getMaxPoolPreparedStatementPerConnectionSize());
		druidDataSource.setFilters(druidConfigProperties.getFilters());
		return druidDataSource;
	}

	@Bean(initMethod = "init", destroyMethod = "close")
	public DataSource eziliaoDataSource() throws SQLException {
		DruidDataSource druidDataSource = new DruidDataSource();
		druidDataSource.setDriverClassName(druidConfigProperties.getDriverClassName1());
		druidDataSource.setUrl(druidConfigProperties.getUrl1());
		druidDataSource.setUsername(druidConfigProperties.getUsername1());
		druidDataSource.setPassword(druidConfigProperties.getPassword1());
		druidDataSource.setInitialSize(druidConfigProperties.getMinIdle());
		druidDataSource.setMinIdle(druidConfigProperties.getMinIdle());
		druidDataSource.setMaxActive(druidConfigProperties.getMaxActive());
		druidDataSource.setMaxWait(druidConfigProperties.getMaxWait());
		druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigProperties.getTimeBetweenEvictionRunsMillis());
		druidDataSource.setMinEvictableIdleTimeMillis(druidConfigProperties.getMinEvictableIdleTimeMillis());
		druidDataSource.setValidationQuery(druidConfigProperties.getValidationQuery());
		druidDataSource.setTestWhileIdle(druidConfigProperties.getTestWhileIdle());
		druidDataSource.setTestOnBorrow(druidConfigProperties.getTestOnBorrow());
		druidDataSource.setTestOnReturn(druidConfigProperties.getTestOnReturn());
		druidDataSource.setPoolPreparedStatements(druidConfigProperties.getPoolPreparedStatements());
		druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigProperties.getMaxPoolPreparedStatementPerConnectionSize());
		druidDataSource.setFilters(druidConfigProperties.getFilters());
		return druidDataSource;
	}

	@Bean
	public DataSource dynmicDataSource() throws SQLException {
		DynmicDataSource dynmicDataSource = new DynmicDataSource();
		Map<Object, Object> targetDataSources = new HashMap<>();
		targetDataSources.put("springbootDataSource", springbootDataSource());
		targetDataSources.put("eziliaoDataSource", eziliaoDataSource());
		dynmicDataSource.setTargetDataSources(targetDataSources);
		
		dynmicDataSource.setDefaultTargetDataSource(springbootDataSource());
		return dynmicDataSource;
	}
	
	@Bean
	public SqlSessionFactory sqlSessionFactory(@Qualifier("dynmicDataSource")DataSource dataSource) throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);
		sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));

		// 新增Mybatis外掛,例如分頁,在之類建立你外掛新增進去即可,這裡我就不做敘述了。
		// sqlSessionFactoryBean.setPlugins(new Interceptor[]{你的外掛});

		return sqlSessionFactoryBean.getObject();
	}

	@Bean
	public PlatformTransactionManager transactionManager(@Qualifier("dynmicDataSource")DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}

}

4.DruidConfigProperties程式碼修改,程式碼如下:

@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidConfigProperties {
	private String driverClassName;
	private String url;
	private String username;
	private String password;

	private String driverClassName1;
	private String url1;
	private String username1;
	private String password1;

	private Integer minIdle;
	private Integer maxActive;
	private Integer maxWait;
	private Long timeBetweenEvictionRunsMillis;
	private Long minEvictableIdleTimeMillis;
	private String validationQuery;
	private Boolean testWhileIdle;
	private Boolean testOnBorrow;
	private Boolean testOnReturn;
	private Boolean poolPreparedStatements;
	private Integer maxPoolPreparedStatementPerConnectionSize;
	private String filters;
.........get set 方法省略
}

5.定義類,實現AbstractRoutingDataSource,程式碼如下:

package com.chengli.springboot.dynamicds.dynmic;

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

public class DynmicDataSource extends AbstractRoutingDataSource {

	/**
	 * 返回的內容是targetDataSources 的Key
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return DynmicDataSourceContextHolder.getDataSourceKey();
	}

}

6.自定義註解UseDataSource

package com.chengli.springboot.dynamicds.dynmic;

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

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
	String value();
}


7.建立DynmicDataSourceContextHolder,用於設定當前使用的資料來源Key

package com.chengli.springboot.dynamicds.dynmic;

public class DynmicDataSourceContextHolder {
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

	public static String getDataSourceKey() {
		return contextHolder.get();
	}

	public static void setDataSourceKey(String dataSourcekey) {
		contextHolder.set(dataSourcekey);
	}
        public static void clear() {
               contextHolder.remove();
        }
}

8.自定義AOP,設定資料來源DynamicDataSourceAspect

package com.chengli.springboot.dynamicds.dynmic;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(-1) //spring order排序後執行順序是從小到大,目的是確保在事務管理器執行前先執行
@Component
public class DynamicDataSourceAspect {

	@Before("@annotation(useDataSource)")//攔截註解 UseDataSource
	public void setDataSourceType(JoinPoint point, UseDataSource useDataSource) throws Throwable {
		DynmicDataSourceContextHolder.setDataSourceKey(useDataSource.value());
	}
        @After("@annotation(useDataSource)")
        public void clearDataSourceType(JoinPoint point, UseDataSource useDataSource) {
                DynmicDataSourceContextHolder.clear();
        }
}

到這裡就完成啦,主要程式碼就上面這些,測試以及完整示例程式碼在QQ交流群中:springboot-dynamic-ds.zip 有興趣的朋友可以加群探討相互學習: Spring Boot QQ交流群:599546061