原理
通过spring的AOP获取方法上的注解判断需要哪个数据源,给ThreadLocal绑定数据源的key,动态数据源类DynamicDataSource继承AbstractRoutingDataSource,构造方法里面配置需要路由的所有的数据源,以key-value形式,并重写路由方法,路由规则为根据ThreadLocal来获取数据源的key来决定选择哪个数据源
代码
包结构
pom
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- druid依赖的日志包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</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.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--aop 配置动态数据源使用-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!--aop实现-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
yml
spring:
datasource:
local:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/law?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
remote:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/law_file?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
config-location: classpath:/config/mybatis-config.xml
启动类
排除数据源自动配置类,以免启动时报错(依赖循环注入)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringbootMybatisDatadourceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisDatadourceApplication.class, args);
}
}
mybatis全局配置文件mybatis-config.xml
此处配置了别名,主要是因为mapper.xml报红,虽然不影响运行
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<typeAliases>
<package name="com.example.domain"/>
</typeAliases>
</configuration>
UserMapper.xml
<?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.example.dao.UserDao">
<select id="selectAll" resultType="user">
select * from user
</select>
</mapper>
UserDao
推荐写两个注解
@Mapper将实现类注入spring,可以在启动类上面写@MapperScan代替
@Repository 可以不写,主要是因为其他类注入dao会报红
@Mapper
@Repository
public interface UserDao {
List<User> selectAll();
}
动态数据源
新建config包,下面的类全部放入其中
public enum DataSourceType {
REMOTE,
LOCAL
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.REMOTE;
}
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.config.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.remote")
public DataSource remoteDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.local")
public DataSource localDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary //代表默认选项
public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
return new DynamicDataSource(remoteDataSource, targetDataSources);
}
}
动态数据源类,配置路由规则,上面的代码DataSourceConfig 已经将其注入
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}
/**
* 根据Key获取数据源的信息
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
threadLocal类
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源变量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
System.out.printf("切换到{%s}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源变量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
mybatis配置类
/**
* mybatis configuration(建议使用配置类的方式重新声明相关bean)
**/
@Configuration
@Slf4j
public class MybatisConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Value("${mybatis.config-location}")
private String configLocation;
@Autowired
private DynamicDataSource dynamicDataSource;
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dynamicDataSource);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] mapperResources = resourcePatternResolver.getResources(mapperLocations);
sqlSessionFactory.setMapperLocations(mapperResources);
sqlSessionFactory.setConfigLocation(resourcePatternResolver.getResource(configLocation));
log.info("config location: {},mapper locations: {}", configLocation, mapperLocations);
return sqlSessionFactory.getObject();
}
}
业务代码
user
@Data
public class User {
private String id;
private String password;
private String username;
}
controller
@RestController
public class UserController {
@Autowired
private UserDao userDao;
@GetMapping("/law")
@DataSource(DataSourceType.LOCAL)
public List<User> findAll(){
return userDao.selectAll();
}
@GetMapping("/lawfile")
@DataSource(DataSourceType.REMOTE)
public List<User> findAll1(){
return userDao.selectAll();
}
}