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:.....
}

 

猜你喜欢

转载自www.cnblogs.com/song27/p/10977241.html