SpringBoot2 + Druid + Mybatis 多數據源動態配置


在大數據高並發的應用場景下,為了更快的響應用戶請求,讀寫分離是比較常見的應對方案。讀寫分離會使用多數據源的使用。下面記錄如何搭建SpringBoot2 + Druid + Mybatis  多數據源配置以及在使用過程遇到的問題。

一、先從pom.xml入手(使用springboot 2的版本)

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
</dependencies>

inject是java依賴注入標准。spring默認支持識別。spring自帶的@Autowired的缺省情況等價於JSR-330的@Inject注解;@Qualifier的缺省的根據Bean名字注入情況等價於JSR-330的@Named注解。

二、添加讀取DB的Mapper

@Mapper
public interface AssetMapper {

@Select("select * from Asset where account = #{account}")
Asset queryName(String account);
}

此處使用mybatis的注解功能,因此可以少省去*.xml等配置文件。

三、添加多數據源的配置參數

spring.datasource.druid.write.url=jdbc:mysql://192.168.0.110:3306/master?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.druid.write.username=root
spring.datasource.druid.write.password=123456
spring.datasource.druid.write.driver-class-name=com.mysql.cj.jdbc.Driver
#
spring.datasource.druid.read.url=jdbc:mysql://192.168.0.110:3306/slave1?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.druid.read.username=root
spring.datasource.druid.read.password=123456
spring.datasource.druid.read.driver-class-name=com.mysql.cj.jdbc.Driver

新版本mysql的url后面必需要添加serverTimezone=。 不然會報以下異常:
2019-06-05 18:47:24.058 ERROR 17804 --- [-Create-6910184] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://localhost:3306/master, errorCode 0, state 01S00

java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
   

四、配置數據源

@Configuration
public class DataSourceConfig {

@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.write")
@Primary
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}

@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.read")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}

@Inject
@Named("masterDataSource")
private DataSource masterDataSource;

@Inject
@Named("slaveDataSource")
private DataSource slaveDataSource;

/**
* 根據數據源創建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
}

SqlSessionFactory必需要重新創建,若不創建會報循環調用異常

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'masterDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

因為SqlSessionFactory還是走默認創建的方式 。

上下文中如何得知使用那個數據源,可使用ThreadLocal來處理。

五、數據源路由


public class DataSourceContextRouting implements AutoCloseable {

static final ThreadLocal<String> dataSourceKeyThreadLocal = new ThreadLocal<>();

public String getDataSourceName(){
String key = dataSourceKeyThreadLocal.get();
return StringUtils.isBlank(key) ?"masterDataSource":key;
}

public DataSourceContextRouting(String key){
dataSourceKeyThreadLocal.set(key);
}

@Override
public void close() throws Exception {
dataSourceKeyThreadLocal.remove();
}
}

spring的提供動態源實現功能。只需要繼承AbstractRoutingDataSource,並重寫protected Object determineCurrentLookupKey()

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextRouting.getDataSourceName();
}
}

 //此為核心代碼

@Bean

public DynamicDataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("masterDataSource", masterDataSource);
targetDataSources.put("slaveDataSource", slaveDataSource);

DynamicDataSource dataSource = new DynamicDataSource();
//設置數據源映射
dataSource.setTargetDataSources(targetDataSources);
//設置默認數據源,當無法映射到數據源時會使用默認數據源
dataSource.setDefaultTargetDataSource(slaveDataSource);
dataSource.afterPropertiesSet();
return dataSource;
}

 六、controller路由切換

 @RequestMapping("master")
public String master(String account){
String key = "masterDataSource";
new DataSourceContextRouting(key);
//TODO .....
}
@RequestMapping("slave")
public String slave(String account){
String key = "slaveDataSource";
new DataSourceContextRouting(key);
      //TODO......
}

 到此為止,整個多數據源配置完成了。

但這種對代碼侵入比較多,可以使用注解的方式來處理。先定義注解標識

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

 使用注解那需要對此進行解析切入,因此就需要用上spring AOP的功能。

 首先添加maven依賴

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 然后添加對其解析Aspect

@Aspect
@Named
public class DataSourceRoutingAspect {
@Around("@annotation(targetDataSource)")
public Object routingWithDataSource(ProceedingJoinPoint joinPoint, TargetDataSource targetDataSource) throws Throwable {
String key = targetDataSource.value();
try (DataSourceContextRouting ctx = new DataSourceContextRouting(key)) {
return joinPoint.proceed();
}
}
}

 @RequestMapping("master")
@TargetDataSource("masterDataSource")
public String master(String account){
TODO:.....
}
@RequestMapping("slave")
@TargetDataSource("slaveDataSource")
public String slave(String account){
TODO:.....
}

 


免責聲明!

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



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