基于Mybatis实现动态数据源切换的示例代码

 更新时间:2024年09月20日 08:32:19   作者:WheelMouse7788  
在当今的互联网应用中,微服务大行其道,随着业务的发展和扩展,单一的数据库无法满足日益增长的数据需求,本文将基于 JDK17 + Spring Boot 3 和 MyBatis 框架实现动态切换数据源功能,需要的朋友可以参考下

引言

在当今的互联网应用中,微服务大行其道,随着业务的发展和扩展,单一的数据库无法满足日益增长的数据需求,一个业务接口可能需要查询多个数据源的数据组装到一起返回给页面进行呈现,此时就需要考虑使用动态数据源技术。 本文将基于 JDK17 + Spring Boot 3 和 MyBatis 框架实现动态切换数据源功能。

代码开发

  • pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.learning</groupId>
	<artifactId>learning-mybatis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>learning-mybatis</name>
	<description>learning-mybatis</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<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>3.0.3</version>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter-test</artifactId>
			<version>3.0.3</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

  • 启动类 Application.java
@MapperScan("com.learning.**.mapper")
@SpringBootApplication(scanBasePackages = {"com.learning"})
public class LearningMybatisApplication {

	public static void main(String[] args) {
		SpringApplication.run(LearningMybatisApplication.class, args);
	}

}
  • Controller
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    StudentService studentService;

    @GetMapping("/selectAll")
    public String selectAll() {
        List<Student> students = studentService.selectAll();
        for (Student student : students) {
            System.out.println(student);
        }
        return "success";
    }

}
  • Service 和 Impl
public interface IStudentService {

    List<Student> selectAll();

}

@Service
public class StudentServiceImpl implements IStudentService {

    @Autowired
    StudentMapper mapper;

    @Override
    public List<Student> selectAll() {
        List<Student> students = mapper.selectAll();
        List<Student> dataSource2All = mapper.selectDataSource2All();
        return mapper.selectAll();
    }
}
  • Mapper
public interface StudentMapper {

    @DynamicDataSource("dataSource1")
    List<Student> selectAll();

    @DynamicDataSource("dataSource2")
    List<Student> selectDataSource2All();

}
<?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.learning.mybatis.mapper.StudentMapper">

    <resultMap id="BaseResultMap" type="com.learning.mybatis.entities.Student">
            <id property="id" column="id" jdbcType="VARCHAR"/>
            <result property="name" column="name" jdbcType="VARCHAR"/>
            <result property="age" column="age" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,name,age
    </sql>

    <select id="selectAll" resultMap="BaseResultMap">
        select
            id,name,age
        from student
    </select>

    <select id="selectDataSource2All" resultMap="BaseResultMap">
        select
            id,name
        from tx
    </select>
</mapper>

  • 配置文件
spring.application.name=learning-mybatis

# ======================== Mybatis ===============
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

动态数据源切换 AOP 实现

  • 首先,我们需要实现AbstractRoutingDataSource 接口的determineCurrentLookupKey()方法
public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
  • 创建一个数据源上下文持有者,用于保存和获取当前线程的数据源。
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

}
  • 配置你的数据源。这里假设你已经有两个数据源dataSource1dataSource2
@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        DynamicDataSourceRouter routingDataSource = new DynamicDataSourceRouter();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("dataSource1", dataSource1()); // 你的第一个数据源
        dataSourceMap.put("dataSource2", dataSource2()); // 你的第二个数据源
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(dataSource1()); // 默认数据源

        return routingDataSource;
    }

    private DataSource dataSource1() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        // 设置其他HikariDataSource的属性,如连接池大小等
        return dataSource;
    }

    private DataSource dataSource2() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/tx2021?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        // 设置其他HikariDataSource的属性,如连接池大小等
        return dataSource;
    }


}
  • 定义注解,用于标记需要切换数据源的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDataSource {

    String value(); // 数据源名称

}
  • 定义切面,使用AOP来拦截带有@DynamicDataSource注解的方法,并在方法执行前后切换数据源
@Aspect
@Order(-1) // 确保该AOP在事务AOP之前执行
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(dynamicDataSource)")
    public void switchDataSource(JoinPoint point, DynamicDataSource dynamicDataSource) {
        DynamicDataSourceContextHolder.setDataSourceType(dynamicDataSource.value());
    }

    @After("@annotation(dynamicDataSource)")
    public void restoreDataSource(JoinPoint point, DynamicDataSource dynamicDataSource) {
        DynamicDataSourceContextHolder.clearDataSourceType();
    }


}

至此,完活,拿去测试看效果。

动态切换数据源实现原理分析

核心代码:AbstractRoutingDataSource AbstractRoutingDataSource 是 Spring 框架提供的一个抽象类,它实现了 DataSource 接口,内部维护了一个用来存储数据源和它们对应的 key的Map,这个 Map 是在构造函数或者配置方法(如 setTargetDataSources)中设置的。

// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
@Nullable
private Map<Object, Object> targetDataSources;

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
    this.targetDataSources = targetDataSources;
}

determineCurrentLookupKey() 方法是 AbstractRoutingDataSource 的核心。它是一个抽象方法,子类必须实现它来提供当前的 key。

// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
@Nullable
protected abstract Object determineCurrentLookupKey();

当 AbstractRoutingDataSource 的 getConnection() 方法被调用时,它会调用 determineCurrentLookupKey() 来获取当前的数据源 key,然后使用这个 key 从 Map 中获取对应的数据源。

// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

public Connection getConnection() throws SQLException {
    return this.determineTargetDataSource().getConnection();
}

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }

    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    } else {
        return dataSource;
    }
}

一旦 determineTargetDataSource() 方法返回了合适的数据源,AbstractRoutingDataSource 就会使用这个数据源来获取数据库连接。 由于 determineCurrentLookupKey() 方法在每个数据库操作之前都会被调用,所以只要在适当的地方修改 determineCurrentLookupKey() 的实现,就可以实现在不同的数据库操作间切换数据源。

总结

通过实现 AbstractRoutingDataSource.determineCurrentLookupKey() 方法,并结合 Spring 框架内部的 AbstractRoutingDataSource 逻辑,我们可以实现在运行时根据不同的条件动态地选择和切换数据源。这种机制允许应用程序在处理不同的请求或事务时使用不同的数据库连接,从而提供了极大的灵活性和扩展性。

以上就是基于Mybatis实现动态数据源切换的示例代码的详细内容,更多关于Mybatis动态数据源切换的资料请关注脚本之家其它相关文章!

相关文章

  • 简单了解Java关键字throw和throws的区别

    简单了解Java关键字throw和throws的区别

    这篇文章主要介绍了简单了解Java关键字throw和throws的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java中fastjson生成和解析json数据(序列化和反序列化数据)

    java中fastjson生成和解析json数据(序列化和反序列化数据)

    本篇文章主要介绍了java中fastjson生成和解析json数据(序列化和反序列化数据),具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-02-02
  • springmvc整合ssm配置的详细代码

    springmvc整合ssm配置的详细代码

    今天通过实例代码给大家介绍了springmvc整合ssm配置的详细方法,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-11-11
  • mybatis中的setting配置详解

    mybatis中的setting配置详解

    这篇文章主要给大家介绍了关于mybatis中setting配置的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-06-06
  • 浅谈Java之Map 按值排序 (Map sort by value)

    浅谈Java之Map 按值排序 (Map sort by value)

    下面小编就为大家带来一篇浅谈Java之Map 按值排序 (Map sort by value)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • 实例解析Java单例模式编程中对抽象工厂模式的运用

    实例解析Java单例模式编程中对抽象工厂模式的运用

    这篇文章主要介绍了实例解析Java单例模式编程中对抽象工厂模式的运用,抽象工厂模式可以看作是工厂方法模式的升级版,本需要的朋友可以参考下
    2016-02-02
  • 利用json2POJO with Lombok 插件自动生成java类的操作

    利用json2POJO with Lombok 插件自动生成java类的操作

    这篇文章主要介绍了利用json2POJO with Lombok 插件自动生成java类的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Java类和成员上的一些方法实例代码

    Java类和成员上的一些方法实例代码

    这篇文章主要介绍了Java类和成员上的一些方法实例代码,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Java SpringBoot自动装配原理详解及源码注释

    Java SpringBoot自动装配原理详解及源码注释

    SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提。其实它并不那么神秘,我在这之前已经写过最基本的实现了,大家可以参考这篇文章,来看看它是怎么样实现的,我们透过源代码来把握自动装配的来龙去脉
    2021-10-10
  • Java之BigDecimal的坑及解决

    Java之BigDecimal的坑及解决

    这篇文章主要介绍了Java之BigDecimal的坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11

最新评论