准备数据库
准备两个数据库test1和test2,并分别创建一个表stu,加入初始化数据
引入依赖
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
代码实现
配置: application.yml
server:
port: 8080
spring:
datasource:
db1:
jdbcUrl: jdbc:mysql://192.168.31.128:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
db2:
jdbcUrl: jdbc:mysql://192.168.31.128:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
mybatis:
mapper-locations: classpath:*.xml
DBType.java
public enum DBType {
DB1("数据源1"),
DB2("数据源2");
private String name;
DBType(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
DBSelector.java
package com.cyz.config;
/**
* @author cyz
* @since 2023/12/1 16:56
*/
public class DBSelector {
private static final ThreadLocal<DBType> current = new ThreadLocal<>();
public static DBType getCurrentDBType() {
return current.get();
}
public static void setCurrentDBType(DBType dbType) {
System.out.println("切换数据源=》 " + dbType.getName());
current.set(dbType);
}
public static void clear() {
current.remove();
}
}
DynamicDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DBType currentDBType = DBSelector.getCurrentDBType();
return currentDBType;
}
}
注解MyDataSource
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
DBType value() default DBType.DB1;
}
DataSourceConfig.java
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource getDateSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource getDateSource2() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource(@Qualifier("db1") DataSource db1,
@Qualifier("db2") DataSource db2) {
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DBType.DB1, db1);
targetDataSource.put(DBType.DB2, db2);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
dataSource.setDefaultTargetDataSource(db1);
return dataSource;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:*.xml"));
return bean.getObject();
}
}
DataSourceAop.java
@Aspect
@Component
public class DataSourceAop {
// @Before("@annotation(com.cyz.config.MyDataSource)")
@Before("execution(* com.cyz.mapper.*..*(..))")
public void setDataSource2test01(JoinPoint pointcut) {
MethodSignature signature = (MethodSignature) pointcut.getSignature();
Method method = signature.getMethod();
MyDataSource annotation = method.getAnnotation(MyDataSource.class);
if (Objects.nonNull(annotation)){
DBType value = annotation.value();
DBSelector.setCurrentDBType(value);
return;
}
MyDataSource classAnnotation = (MyDataSource) pointcut.getSignature().getDeclaringType().getAnnotation(MyDataSource.class);
if (Objects.nonNull(classAnnotation)){
DBType value = classAnnotation.value();
DBSelector.setCurrentDBType(value);
return;
}
}
@After("execution(* com.cyz.mapper.*..*(..))")
public void after() {
DBSelector.clear();
}
}
Stu.java
package com.cyz.bean;
/**
* @author cyz
* @date 2023/9/1 9:46
*/
public class Stu {
String name;
String gender;
Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Stu{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
StuMapper.java
@Repository
@Mapper
@MyDataSource(DBType.DB1)
public interface StuMapper {
List<Stu> query1();
@MyDataSource(DBType.DB2)
List<Stu> query2();
}
stuMapper.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.cyz.mapper.StuMapper">
<select id="query1" resultType="com.cyz.bean.Stu">
select * from stu
</select>
<select id="query2" resultType="com.cyz.bean.Stu">
select * from stu
</select>
</mapper>
Main.java
@MapperScan("com.cyz.mapper")
@SpringBootApplication
public class Main {
@Autowired
StuMapper stuMapper;
@PostConstruct
void test(){
System.out.println(stuMapper.query1());
System.out.println(stuMapper.query2());
System.out.println(stuMapper.query1());
System.out.println(stuMapper.query2());
}
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
结果
原理分析
预分析:要想实现动态数据源,就需要切换数据,而数据源接口中只有两个getConnection接口
那只需要修改返回的connection即可实现,先写个方法打断点进入查看,看哪边获取了连接
如下断点
一直到如下代码
AbstractRoutingDataSource
重点在这个determineCurrentLookupKey方法,这边可以决定使用哪个数据源
aop功能(主要是设置数据)
总结
动态数据源通过AbstractRoutingDataSource和aop组合实现