springboot整合mybatis实现动态数据源(原理分析)

准备数据库

准备两个数据库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组合实现

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值