java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Mybatis动态数据源切换

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

作者:WheelMouse7788

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

引言

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

代码开发

<?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>

@MapperScan("com.learning.**.mapper")
@SpringBootApplication(scanBasePackages = {"com.learning"})
public class LearningMybatisApplication {

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

}
@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";
    }

}
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();
    }
}
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 实现

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();
    }

}
@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(); // 数据源名称

}
@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动态数据源切换的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文