14.玩轉Spring Boot 多資料來源
阿新 • • 發佈:2019-02-12
玩轉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