✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。
🍎个人主页:Hhzzy99
🍊个人信条:坚持就是胜利!
💞当前专栏:项目实践
🥭本文内容:多数据源如何切换。
Java多数据源如何切换
文章目录
在现代企业级应用中,使用多个数据源来满足不同的需求是非常常见的。无论是为了提高性能、实现负载均衡、支持多租户架构,还是为了实现灾备恢复,动态切换数据源都是一项必备的技能。本文将详细介绍如何在Java中实现动态切换数据源,并通过具体的代码示例来讲解其实现原理。
前言
什么是数据源?
数据源(DataSource)是数据库连接的抽象,通常用于管理数据库连接池。通过数据源,应用程序可以轻松地获得数据库连接,而不必关心底层的数据库驱动和连接细节。在Java中,数据源接口由javax.sql.DataSource定义,常见的实现包括HikariCP、C3P0、DBCP等。
为什么需要动态切换数据源
动态切换数据源的需求来源于以下几个方面:
- 读写分离:在高并发环境中,通过将读操作和写操作分离到不同的数据库实例上,可以有效提高系统的性能和稳定性。
- 多租户支持:在多租户应用中,每个租户可能有独立的数据库实例,通过动态切换数据源,可以实现对不同租户数据的隔离。
- 故障恢复:在灾备场景下,当主数据库出现故障时,可以快速切换到备份数据库,确保系统的高可用性。
实现步骤
1. 引入依赖
在Maven项目的pom.xml文件中添加MyBatis、Spring Boot和数据源连接池的依赖:
<dependencies> <!-- MyBatis-Spring-Boot-Starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- HikariCP connection pool --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <!-- MySQL driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies>
2. 配置数据源
在application.yml文件中配置多个数据源的信息:
spring: datasource: primary: url: jdbc:mysql://localhost:3306/primary_db username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver secondary: url: jdbc:mysql://localhost:3306/secondary_db username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath*:mapper/*.xml type-aliases-package: com.example.entity
3 创建数据源配置类
创建一个配置类,定义两个数据源以及一个动态数据源:
import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @MapperScan(basePackages = "com.example.mapper", sqlSessionFactoryRef = "sqlSessionFactory") public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return new HikariDataSource(); } @Bean @ConfigurationProperties("spring.datasource.secondary") public DataSource secondaryDataSource() { return new HikariDataSource(); } @Bean public DataSource dynamicDataSource() { DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(DataSourceType.PRIMARY, primaryDataSource()); dataSourceMap.put(DataSourceType.SECONDARY, secondaryDataSource()); dynamicRoutingDataSource.setDefaultTargetDataSource(primaryDataSource()); dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); return dynamicRoutingDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dynamicDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml")); return sessionFactory.getObject(); } }
4 定义数据源类型枚举
创建一个枚举类,用于标识不同的数据源:
public enum DataSourceType { PRIMARY, SECONDARY }
5 实现动态路由数据源
创建一个继承AbstractRoutingDataSource的类,实现动态路由逻辑:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(DataSourceType dataSourceType) { contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } @Override protected Object determineCurrentLookupKey() { return getDataSourceType(); } }
6 配置数据源切换的AOP切面
使用AOP切面在方法调用前后进行数据源的切换:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class DataSourceAspect { @Before("@annotation(dataSource)") public void changeDataSource(JoinPoint joinPoint, DataSource dataSource) { DataSourceType dataSourceType = dataSource.value(); DynamicRoutingDataSource.setDataSourceType(dataSourceType); } @After("@annotation(dataSource)") public void clearDataSource(JoinPoint joinPoint, DataSource dataSource) { DynamicRoutingDataSource.clearDataSourceType(); } }
7 定义注解
创建一个自定义注解,用于标识需要切换数据源的方法:
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { DataSourceType value() default DataSourceType.PRIMARY; }
8 配置MyBatis Mapper扫描
在数据源配置类中配置MyBatis的Mapper扫描:
@MapperScan(basePackages = "com.example.mapper") public class MyBatisConfig { }
9 使用示例
在实际使用中,可以在需要切换数据源的方法上添加@DataSource注解。例如:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { @Autowired private UserMapper userMapper; @DataSource(DataSourceType.SECONDARY) public List<User> getUsersFromSecondary() { return userMapper.findAll(); } @DataSource(DataSourceType.PRIMARY) public List<User> getUsersFromPrimary() { return userMapper.findAll(); } }
在Mapper接口中定义数据库操作:
import com.example.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface UserMapper { @Select("SELECT * FROM user") List<User> findAll(); }
在XML映射文件中配置SQL语句(如果你选择使用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.example.mapper.UserMapper"> <select id="findAll" resultType="com.example.entity.User"> SELECT * FROM user </select> </mapper>
结语
通过上述步骤,我们使用MyBatis和Spring Boot实现了动态切换数据源的功能。MyBatis的灵活性和强大的SQL映射功能,使得我们可以在复杂的企业级应用中轻松地管理和操作数据库。同时,结合Spring的AbstractRoutingDataSource和AOP切面,我们可以实现更加灵活和高效的数据源切换方案。
希望本文对您有所帮助!如果有任何问题或建议,欢迎在评论区留言。