1. 前言
在日常生活中,我们不可避免要在工程中配置多个数据源,下面我就给大家讲一下怎么在spring boot里面配置多数据源,并且在文章结尾给出一个github的demo,希望对大家有所帮助
2. application.yml 配置多个数据库
代码语言:yaml复制spring:
datasource:
write:
url: jdbc:mysql://192.168.31.155:3306/test_1?characterEncoding=UTF-8&useSSL=false
username: root
password: root
read:
url: jdbc:mysql://192.168.31.155:3306/test_2?characterEncoding=UTF-8&useSSL=false
username: root
password: root
3. spring读取配置文件
代码语言:shell复制package com.multi.datasource.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @create: 2021-01-14 14:56
**/
@ConfigurationProperties(prefix = "spring.datasource.read")
@Data
public class DataSourceReadProperties {
private String url;
private String username;
private String password;
}
代码语言:shell复制package com.multi.datasource.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @create: 2021-01-14 14:56
**/
@ConfigurationProperties(prefix = "spring.datasource.write")
@Data
public class DataSourceWriteProperties {
private String url;
private String username;
private String password;
}
4. 数据源配置
代码语言:shell复制package com.multi.datasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @create: 2021-01-14 14:55
**/
@Configuration
public class DataSourceConfig {
@Bean("dataSourceReadProperties")
@ConditionalOnProperty(prefix = "spring.datasource.read", name = {"url", "username", "password"})
public DataSourceReadProperties dataSourceReadProperties() {
DataSourceReadProperties dataSourceReadProperties = new DataSourceReadProperties();
return dataSourceReadProperties;
}
@Bean("dataSourceWriteProperties")
@ConditionalOnProperty(prefix = "spring.datasource.write", name = {"url", "username", "password"})
public DataSourceWriteProperties dataSourceWriteProperties() {
DataSourceWriteProperties dataSourceWriteProperties = new DataSourceWriteProperties();
return dataSourceWriteProperties;
}
@Bean("dataSourceRead")
@ConditionalOnBean(name = "dataSourceReadProperties")
public DataSource getDataSourceRead(@Qualifier("dataSourceReadProperties") DataSourceReadProperties dataSourceReadProperties) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dataSourceReadProperties.getUrl());
dataSource.setUsername(dataSourceReadProperties.getUsername());
dataSource.setPassword(dataSourceReadProperties.getPassword());
return dataSource;
}
@Bean("dataSourceWrite")
@ConditionalOnBean(name = "dataSourceWriteProperties")
public DataSource getDataSourceWrite(@Qualifier("dataSourceWriteProperties") DataSourceWriteProperties dataSourceWriteProperties) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dataSourceWriteProperties.getUrl());
dataSource.setUsername(dataSourceWriteProperties.getUsername());
dataSource.setPassword(dataSourceWriteProperties.getPassword());
return dataSource;
}
/**
* 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源
*/
@Bean("routingDataSource")
public AbstractRoutingDataSource routingDataSource(@Qualifier("dataSourceWrite") DataSource dataSourceWrite,
@Qualifier("dataSourceRead") DataSource dataSourceRead) {
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DbContextHolder.WRITE, dataSourceWrite);
targetDataSources.put(DbContextHolder.READ, dataSourceRead);
proxy.setDefaultTargetDataSource(dataSourceWrite);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
/**
* 多数据源需要自己设置sqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("routingDataSource") AbstractRoutingDataSource routingDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(routingDataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// mybatis的XML的配置
bean.setMapperLocations(resolver.getResources("classpath*:mapper/*Mapper.xml"));
return bean.getObject();
}
/**
* 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理
*/
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("routingDataSource") AbstractRoutingDataSource routingDataSource) {
return new DataSourceTransactionManager(routingDataSource);
}
}
5.多数据源切换配置
代码语言:shell复制package com.multi.datasource.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* 这里切换读/写模式
* 原理是利用ThreadLocal保存当前线程是否处于读模式(通过开始READ_ONLY注解在开始操作前设置模式为读模式,
* 操作结束后清除该数据,避免内存泄漏,同时也为了后续在该线程进行写操作时任然为读模式
*
* @author zxliuyu
*/
@Slf4j
public class DbContextHolder {
public static final String WRITE = "write";
public static final String READ = "read";
private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDbType(String dbType) {
if (StringUtils.isBlank(dbType)) {
log.error("DbContextHolder dbType is null");
}
contextHolder.set(dbType);
}
public static String getDbType() {
return StringUtils.isBlank(contextHolder.get()) ? WRITE : contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
代码语言:shell复制package com.multi.datasource.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@Slf4j
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DbContextHolder.getDbType();
if (typeKey.equals(DbContextHolder.WRITE)) {
log.info("dataSource is use write");
return typeKey;
}
log.info("dataSource is use read");
return DbContextHolder.READ;
}
}
6. 通过注解指定数据源
代码语言:shell复制package com.multi.datasource.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @create: 2021-01-14 17:22
**/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}
代码语言:shell复制package com.multi.datasource.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class ReadOnlyInterceptor implements Ordered {
@Around("@annotation(readOnly)")
public Object setRead(ProceedingJoinPoint joinPoint, ReadOnly readOnly) throws Throwable {
try {
DbContextHolder.setDbType(DbContextHolder.READ);
return joinPoint.proceed();
} finally {
// 清楚DbType一方面为了避免内存泄漏,更重要的是避免对后续在本线程上执行的操作产生影响
DbContextHolder.clearDbType();
log.info("remove threadLocal");
}
}
@Override
public int getOrder() {
return 0;
}
}
7. github地址
https://github.com/constantRAIN/multi-data-source