数据源的配置过程
- 引入依赖:在
pom.xml
中添加Spring Boot JDBC和数据库连接相关依赖,如spring-boot-starter-jdbc
,以及具体数据库的驱动依赖(如mysql-connector-java
)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 配置数据源:在
application.yml
中配置主从数据源的连接信息。
spring:
datasource:
master:
url: jdbc:mysql://master-url:3306/master_db
username: master_user
password: master_password
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://slave-url:3306/slave_db
username: slave_user
password: slave_password
driver-class-name: com.mysql.cj.jdbc.Driver
- 创建数据源Bean:创建
DataSourceConfig
类,用于创建主从数据源的DataSource
Bean。
import org.apache.commons.dbcp2.BasicDataSource;
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.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return new BasicDataSource();
}
@Bean
public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return new BasicDataSource();
}
@Bean
public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
动态切换的实现逻辑
- 定义数据源注解:创建一个自定义注解
@DataSource
,用于标记需要使用特定数据源的方法或类。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSource {
String value() default "master";
}
- 创建数据源上下文:创建
DataSourceContextHolder
类,用于存储和获取当前线程使用的数据源。
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
- 实现数据源路由:创建
DynamicDataSource
类,继承AbstractRoutingDataSource
,重写determineCurrentLookupKey
方法来确定当前使用的数据源。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
- 切面实现动态切换:创建切面类
DataSourceAspect
,在方法执行前根据@DataSource
注解设置数据源。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
try {
DataSourceContextHolder.setDataSource(dataSource.value());
return point.proceed();
} finally {
DataSourceContextHolder.clearDataSource();
}
}
}
- 使用注解切换数据源:在Service层方法上使用
@DataSource
注解,如:
import org.springframework.stereotype.Service;
@Service
public class UserService {
@DataSource("slave")
public void readUser() {
// 读操作,使用从库
}
@DataSource("master")
public void writeUser() {
// 写操作,使用主库
}
}
可能遇到的问题和解决方案
- 事务管理问题:在多数据源环境下,事务管理需要特别注意。如果一个事务涉及多个数据源,需要使用分布式事务解决方案,如Seata。对于单数据源事务,可以使用Spring的
@Transactional
注解,确保事务在正确的数据源上进行。
- 性能问题:频繁切换数据源可能会带来性能开销。可以通过优化数据源连接池配置,减少连接创建和销毁的次数,以及合理设置缓存来缓解性能问题。
- 线程安全问题:
DataSourceContextHolder
使用ThreadLocal
来存储数据源,确保线程安全。但在使用线程池等多线程场景时,要注意数据源的正确传递和清理,避免线程间的数据污染。可以通过自定义线程池,在任务提交前设置数据源,任务完成后清理数据源。
- 数据源一致性问题:在读写分离场景下,可能会出现数据一致性问题,从库数据可能存在延迟。可以通过设置合适的缓存策略,或者在某些关键读操作时强制使用主库,来保证数据的一致性。