AbstractRoutingDataSource动态路由方式:
利用AbstractRoutingDataSource实现动态切换数据源的功能
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.3</version>
</dependency>
</dependencies>
</project>
配置数据源
application.properties
#主数据源
spring.datasource.primary.url=jdbc:mysql://localhost:3306/test
spring.datasource.primary.username=test
spring.datasource.primary.password=test
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
#second数据源
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=test2
spring.datasource.secondary.password=test2
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
# MyBatis
mybatis.type-aliases-package=com.durid.model
mybatis.mapper-locations=classpath:mappers/*.xml
@Primary注解在哪个ds,默认使用那个ds
package com.durid.config;
import com.durid.dds.DRoutingDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
/**
* @primary 设置在哪个ds,就优先读取那个ds
*
* @return
*/
@Bean(name = "primary")
@Primary
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondary")
@ConfigurationProperties(prefix="spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源设置
* @return
*/
@Bean(name = "ddataSource")
public DataSource ddataSource(){
DRoutingDataSource dRoutingDataSource = new DRoutingDataSource();
//设置默认的数据源
dRoutingDataSource.setDefaultTargetDataSource(primaryDataSource());
//配置多数据源
Map<Object, Object> ddsMap = new HashMap<>(2);
ddsMap.put("primary", primaryDataSource());
ddsMap.put("secondary", secondaryDataSource());
dRoutingDataSource.setTargetDataSources(ddsMap);
return dRoutingDataSource;
}
/**
* mybatis前缀必需
* 参考application.properties的配置
* 用来映射xml和model
* mybatis.type-aliases-package
* mybatis.mapper-locations
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(ddataSource());
return sqlSessionFactoryBean;
}
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(ddataSource());
}
}
继承AbstractRoutingDataSource,配合ThreadLocal存储数据源key
package com.durid.dds;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DdsHolder.getDS();
}
}
package com.durid.dds;
public class DdsHolder {
private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();
public static void setDS(String ddsName) {
if (ddsName == null){
throw new RuntimeException("set ddsMode failed");
}
HOLDER.set(ddsName);
}
public static void release(){
HOLDER.remove();
}
public static String getDS(){
return HOLDER.get();
}
}
通过注解和切面的方式实现数据源的动态切换:
package com.durid.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
package com.durid.aop;
import com.durid.annotation.TargetDataSource;
import com.durid.dds.DdsHolder;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DdsAspect {
@Before("@annotation(targetDataSource))")
public void switchDataSource(TargetDataSource targetDataSource) {
DdsHolder.setDS(targetDataSource.value());
}
@After("@annotation(targetDataSource))")
public void restoreDataSource(TargetDataSource targetDataSource) {
DdsHolder.release();
}
}
model类:
package com.durid.model;
import lombok.Data;
/**
* Created by hao.g on 18/3/12.
*/
@Data
public class User {
private Long id;
private String name;
private int age;
}
mapper类:
package com.durid.mapper;
import com.durid.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User selectById(long id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.durid.mapper.UserMapper">
<resultMap id="baseResultMap" type="com.durid.model.User">
<id column="id" property="id" javaType="java.lang.Long" jdbcType="BIGINT" />
<result column="name" property="name" javaType="java.lang.String" jdbcType="VARCHAR" />
<result column="age" property="age" javaType="java.lang.Integer" jdbcType="BIGINT" />
</resultMap>
<select id="selectById" resultMap="baseResultMap" parameterType="java.lang.Long">
SELECT *
FROM user
WHERE id = #{id}
</select>
</mapper>
Service类,可以使用注解在目标service方法完成数据源切换:
package com.durid.service;
import com.durid.annotation.TargetDataSource;
import com.durid.mapper.UserMapper;
import com.durid.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 动态切换数据源
* @param id
* @return
*/
// @TargetDataSource("primary")
@TargetDataSource("secondary")
public User selectById(long id){
return userMapper.selectById(id);
}
}
测试类:
package com.test;
import com.durid.App;
import com.durid.aop.DdsAspect;
import com.durid.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class AppTests {
@Autowired
private UserService userService;
@Test
public void test(){
System.out.println(userService.selectById(1).getName());
}
}