什么时候需要多数据源切换
①、项目中存储的数据量很大,一个服务器扛不住,需要将一部分数据存储在另外一个服务器里,然而我们还要每天去访问这些存储的数据。
②、项目与其他项目对接,需要连接另外一个项目的数据库(这里可能有人会提议使用API,但这是相对于第三方公司,人家不会把数据库信息暴露给你的,所以才提供的api。)
实现多数据源切换实现(springboot+aop)
添加相关pom
<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>5.1.34</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- alibaba durid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置yml相关数据源
假如我们有两个数据库 readDate,wirteDate
spring:
datasource:
readDate:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://XXXX:XXX/readDate?useUnicode=true&characterEncoding=utf-8
username: xxx
password: xxxx
type: com.alibaba.druid.pool.DruidDataSource
wirteDate:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://XXXX:XXX/wirteDate?useUnicode=true&characterEncoding=utf-8
username: xxx
password: xxxx
type: com.alibaba.druid.pool.DruidDataSource
创建数据源枚举
@AllArgsConstructor
@Getter
public enum DataSourceEnum {
DEFAULT("wirteDate"),
WRITEDATE("wirteDate"),
READDATE("readDate");
private String name;
}
创建多数据源自定义注解
/**
* 数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSource {
//枚举
DataSourceEnum value();
}
创建DataSourceHolder用来存储数据源
public class DataSourceHolder {
private static final ThreadLocal<String> DS_HOLDER = new ThreadLocal<>();
public static void setDataSource(DataSourceEnum dataSource) {
DS_HOLDER.set(dataSource.getName());
}
public static String getDataSource() {
return DS_HOLDER.get();
}
public static void clearDataSource() {
DS_HOLDER.remove();
}
}
创建DynamicDataSource
继承AbstractRoutingDataSource用来切换数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//基于aop切换数据源
System.out.println("dynamic datasource 切换数据源" + DataSourceHolder.getDataSource());
return DataSourceHolder.getDataSource();
}
}
创建数据源,连接数据库db的config
@Configuration
public class DataSourceConfig {
//连接数据库writeDate
@Bean("writeDate")
@ConfigurationProperties("spring.datasource.writeDate")
public DataSource writeDateDb() {
return new DruidDataSource();
}
//连接数据库readDate
@Bean("readDate")
@ConfigurationProperties("spring.datasource.readDate")
public DataSource readDateDb() {
return new DruidDataSource();
}
@Bean("dynamicDataSource")
@Primary
public DataSource dynamicDataSource() {
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setDefaultTargetDataSource(writeDateDb());
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceEnum.TEST1.getName(), writeDateDb());
dataSourceMap.put(DataSourceEnum.TEST2.getName(), readDateDb());
dataSource.setTargetDataSources(dataSourceMap);
return dataSource ;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
创建基于自定义注解的切面
@Aspect
@Component
@Order(1)
public class DataSourceAsepct {
//加载数据源
@Pointcut("@annotation(com.xxxx.config.DataSource)")
public void pointCut(){ }
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
DataSourceEnum dataSource = DataSourceEnum.DEFAULT;
try {
Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
if (method.isAnnotationPresent(DataSource.class)) {
DataSource annotation = method.getAnnotation(DataSource.class);
dataSource = annotation.value();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//实现aop切换数据源
System.out.println("aop切换数据源:" + dataSource.getName());
DataSourceHolder.setDataSource(dataSource);
}
@After("pointCut()")
public void after() {
DataSourceHolder.clearDataSource();
}
}
启动类上记得加@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
最后使用自定义注解
对数据库的操作这里不做描述,认为都写好了mapper层
@Service
public class TestServiceImpl {
@Autowired
private WriteMapper mapper1;
@Autowired
private ReadMapper mapper2;
@Override
public void addUser(User user) {
// 不指定,则默认使用数据源1
return mapper1.save(user);
}
// 覆盖类上指定的数据源2
@DataSource(DataSourceEnum.READDATE)
@Override
public List<Map<String, User>> findUser() {
return mapper2.findAll();
}
/**
* 如果切面不加@Order,添加Transactional注解会导致切换数据源失效
*/
@Transactional
@DataSource(DataSourceEnum.WRITEDATE)
@Override
public void insert(User user) {
mapper1.insert(user);
}
}