下面我将详细讲解Spring配置多数据源切换的完整攻略。处理多数据源切换的核心是通过动态切换数据源来实现。实现这一点的最简单、最常用的方法是使用AOP切面,这也是本文的重点。
1. 添加依赖
以下是maven引用多数据源相关依赖的代码:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
添加完依赖后,需要在application.yml文件中配置相应的数据库信息。
2. 配置多数据源
在application.yml中添加数据源配置,例如:
spring:
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: root
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: root
上面的配置中定义了两个数据源,一个是master,一个是slave。
3. 动态切换数据源
这里的动态指的是在编写代码时不知道使用哪个数据源,运行时动态选择。需要通过AOP来实现动态切换数据源。
首先,新建一个切面类,如下所示:
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.multipledatasource.config.DataSource)")
public void pointCut() {
}
@Before("pointCut() && @annotation(ds)")
public void doBefore(DataSource ds) throws Throwable {
DataSourceEnum value = ds.value();
if (value == DataSourceEnum.Master) {
DynamicDataSourceContextHolder.setDataSourceKey("master");
} else if (value == DataSourceEnum.Slave) {
DynamicDataSourceContextHolder.setDataSourceKey("slave");
}
}
@After("pointCut()")
public void doAfter() {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
上述代码中的切面会拦截所有的@DataSource注解的方法调用,并根据该注解中给定的数据源类型,动态切换数据源。
下一步是创建一个注解@DataSource,如下所示:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceEnum value() default DataSourceEnum.Master;
}
该注解指定了被拦截方法应该使用哪个数据源。DataSourceEnum是一个枚举类型,包含了所有的数据源。例如:
public enum DataSourceEnum {
Master("master"),
Slave("slave");
private String key;
public String getKey() {
return key;
}
DataSourceEnum(String key) {
this.key = key;
}
}
当我们需要在方法调用中使用不同的数据源时,只需要在方法前加上@DataSource注解,这样就可以动态切换数据源了。
以下是一个示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@DataSource(value = DataSourceEnum.Master)
public User addUser(User user) {
userMapper.insert(user);
return user;
}
@Override
@DataSource(value = DataSourceEnum.Slave)
public User getUserById(int id) {
return userMapper.selectByPrimaryKey(id);
}
}
上面的示例中,addUser方法使用的是master数据源,getUserById方法使用的是slave数据源。
示例一
下面是一个简单的基于Spring Boot的多数据源切换示例,示例中使用的是JPA和Hibernate。
首先,我们需要定义两个数据源:
spring.datasource.master.url=jdbc:mysql://localhost:3306/master
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave
spring.datasource.slave.username=root
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
然后在代码中注入多数据源配置,如下所示:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryMaster",
transactionManagerRef = "transactionManagerMaster",
basePackages = {"com.example.multipledatasource.repository.master"})
public class MasterDataSourceConfig {
@Primary
@Bean(name = "dataSourceMaster")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource dataSourceMaster() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "entityManagerFactoryMaster")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryMaster(
EntityManagerFactoryBuilder builder,
@Qualifier("dataSourceMaster") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.multipledatasource.entity.master")
.persistenceUnit("master")
.build();
}
@Primary
@Bean(name = "transactionManagerMaster")
public PlatformTransactionManager transactionManagerMaster(
@Qualifier("entityManagerFactoryMaster") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
上述代码中定义了一个@Primary的数据源,这意味着这个数据源是主数据源。在JPA相关注解中,通过使用EntityManagerFactoryBuilder来指定特定的包和持久化单元。
接下来,我们定义另一个数据源:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactorySlave",
transactionManagerRef = "transactionManagerSlave",
basePackages = {"com.example.multipledatasource.repository.slave"})
public class SlaveDataSourceConfig {
@Bean(name = "dataSourceSlave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource dataSourceSlave() {
return DataSourceBuilder.create().build();
}
@Bean(name = "entityManagerFactorySlave")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySlave(
EntityManagerFactoryBuilder builder,
@Qualifier("dataSourceSlave") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.multipledatasource.entity.slave")
.persistenceUnit("slave")
.build();
}
@Bean(name = "transactionManagerSlave")
public PlatformTransactionManager transactionManagerSlave(
@Qualifier("entityManagerFactorySlave") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
配置相似,不同的是这个数据源没有设置@Primary,表示这个数据源是从数据源。
DataSourceAspect的实现如下所示:
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.multipledatasource.annotation.DataSource) ")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut() ")
public void dataSourceBefore(JoinPoint joinPoint) throws Exception {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?> clazz = joinPoint.getTarget().getClass();
DataSource.DS dataSource = clazz.isAnnotationPresent(DataSource.DS.class)
? clazz.getAnnotation(DataSource.DS.class) : null;
if (dataSource == null) {
dataSource = signature.getMethod().isAnnotationPresent(
DataSource.DS.class) ? signature.getMethod()
.getAnnotation(DataSource.DS.class) : null;
}
String dataSourceKey = DataSourceType.MASTER;
if (dataSource != null) {
dataSourceKey = dataSource.value().getKey();
}
DynamicDataSourceContextHolder.setDataSourceKey(dataSourceKey);
}
@After("dataSourcePointCut() ")
public void dataSourceAfter() {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
示例中的@DataSource注解被定义为一个@Qualifier:
@Qualifier
@Target({ElementType.FIELD,METHOD,PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
public static final String MASTER = "master";
public static final String SLAVE = "slave";
DS value() default DS.MASTER;
@Scope("prototype")
@Component
public static class DS {
/**
* The value
*/
private String value;
/**
* Instantiates a new Ds.
*
* @param value the value
*/
public DS(final String value) {
this.value = value;
}
/**
* Gets key.
*
* @return the key
*/
public String getKey() {
return this.value;
}
/**
* Master ds.
*
* @return the ds
*/
public static DS MASTER() {
return new DS(DataSource.MASTER);
}
/**
* Slave ds.
*
* @return the ds
*/
public static DS SLAVE() {
return new DS(DataSource.SLAVE);
}
}
}
在repository类中,通过使用@DataSource注解来指定数据源:
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
@DataSource(DataSource.DS.SLAVE)
List<User> findAllByName(String name);
@DataSource(DataSource.DS.SLAVE)
User findByName(String name);
}
示例二
下面是另一个示例,示例中使用的是mybatis作为持久层框架。示例中定义了两个数据源,一个是master,一个是slave。
首先,我们定义了一个DynamicDataSource类,该类继承了AbstractRoutingDataSource,并且实现了determineCurrentLookupKey方法:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDateSourceKey();
}
}
接下来,我们需要定义两个数据源的bean:
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
然后,我们需要配置方式RoutingDataSource,如下所示:
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnum.MASTER.getName(), masterDataSource());
targetDataSources.put(DataSourceEnum.SLAVE.getName(), slaveDataSource());
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
上述代码中,我们创建了DynamicDataSource bean,并且将数据源加入到了数据源映射表中。
在service类的方法中使用@DataSource注解指定数据源,示例如下:
@DataSource(DataSourceEnum.SLAVE)
@Override
public List<User> selectAll() {
return userDao.selectAll();
}
最后,我们需要在所有涉及到DataSource标签的方法上添加一个切面,来动态的切换数据源:
@Aspect
@Component
public class DynamicDataSourceAspect {
@After("@annotation(ds)")
public void afterSwitchDS(JoinPoint point, DataSource ds) {
DynamicDataSourceContextHolder.resetDataSource();
}
@Before("@annotation(ds)")
public void switchDS(JoinPoint point, DataSource ds) {
if (ds == null || ds.value() == DataSourceEnum.MASTER) {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceEnum.MASTER.getName());
} else {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceEnum.SLAVE.getName());
}
}
}
上述代码中的切面检查了当前的数据源,如果没有@DataSource注解,或者注解指定的数据源为master,就会将数据源切换到master,否则就切换到slave。
这样,在使用MySQL等数据库时,我们就可以通过切换数据源的方式来实现多数据源切换了。
在使用中,我们只需要在调用来自与需要使用其他数据源的dao或service方法时,加上相应的@DataSource注解即可。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring配置多数据源切换 - Python技术站