Spring Datasource
开启本篇话题之前,先说下 spring 数据源的配置。
JDBC 直接配置
<!-- 配置数据源dataSource jdbc方式连接数据源 -->
<beanid="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>
<property name="url"value="jdbc:mysql://localhost:3306/mydatabase" />
<propertyname="username" value="root"/>
<propertyname="password" value="root"/>
</bean>
当然,处于性能考虑,我们一般会使用连接池。
连接池
DBCP
<!-- 配置数据源dataSource dbcp连接池方式连接数据源 -->
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource">
<propertyname="url"
value="jdbc:mysql://localhost:3306/mydatabase"/>
<propertyname="driverClassName"value="com.mysql.jdbc.Dirver" />
<propertyname="username" value="root" />
<propertyname="password" value="root" />
<!--配置初始化大小、最小、最大-->
<property name="initialSize"value="1"/>
<propertyname="minIdle" value="1"/>
<propertyname="maxActive" value="30"/>
</bean>
Driud
druid 作为一名后起之秀,凭借其出色的性能,也逐渐印入了大家的眼帘。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
更多配置,参考 Driud
SpringBoot 多数据源
某些情况下,如果我们需要配置多个数据源,应该如何在Spring Boot中配置呢?
我们以JDBC为例,演示如何在Spring Boot中配置两个DataSource。
对应的,我们会创建两个JdbcTemplate的Bean,分别使用这两个数据源。
配置
- application.yml
首先,我们必须在application.yml中声明两个数据源的配置,一个使用spring.datasource,另一个使用spring.second-datasource:
spring:
application:
name: data-multidatasource
datasource:
driver-class-name: org.hsqldb.jdbc.JDBCDriver
url: jdbc:hsqldb:mem:db1
username: sa
password:
second-datasource:
driver-class-name: org.hsqldb.jdbc.JDBCDriver
url: jdbc:hsqldb:mem:db2
username: sa
password:
在使用多数据源的时候,所有必要配置都不能省略。
dao 层
其次,我们需要自己创建两个 DataSource 的 Bean,其中一个标记为 @Primary
,另一个命名为secondDatasource:
@Configuration
public class SomeConfiguration {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondDatasource")
@ConfigurationProperties(prefix = "spring.second-datasource")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
}
紧接着,我们创建两个JdbcTemplate的Bean,其中一个标记为 @Primary
,另一个命名为secondJdbcTemplate,分别使用对应的DataSource:
@Bean
@Primary
public JdbcTemplate primaryJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "secondJdbcTemplate")
public JdbcTemplate secondJdbcTemplate(@Qualifier("secondDatasource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
使用
在需要使用第一个 JdbcTemplate 的地方,我们直接注入:
@Component
public class SomeService {
@Autowired
JdbcTemplate jdbcTemplate;
}
在需要使用第二个 JdbcTemplate 的地方,我们注入时需要用 @Qualifier("secondJdbcTemplate")
标识:
@Component
public class AnotherService {
@Autowired
@Qualifier("secondJdbcTemplate")
JdbcTemplate secondJdbcTemplate;
}
手动实现
看了 spring-boot 的实现,其实我们也可以自己实现一个简化版。
思路
spring 提供了 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
类,可以动态切换数据源。
我们自定义注解,通过 aop 的方式,切换数据源。
注解
- DataSource.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
public @interface DataSource {
/**
* 指定一个数据源的值
* @return 数据源名称
*/
String value() default "";
}
辅助类
- 动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
- 动态数据源 Holder
使用 ThreadLocal 保证各线程互不干扰。
public final class DynamicDataSourceHolder {
private DynamicDataSourceHolder(){}
/**
* 保证线程间互不干涉
*/
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
/**
* 设置数据源类型
* @param datasource 数据库类型
*/
public static void setDataSource(String datasource) {
CONTEXT.set(datasource);
}
/**
* 获取数据源类型
* @return 数据源类型
*/
public static String getDataSource() {
return CONTEXT.get();
}
/**
* 清空数据源
*/
public static void clearDataSource() {
CONTEXT.remove();
}
}
AOP
- DynamicDataSourceAspect.java
@Component
@Aspect
public class DynamicDataSourceAspect {
private Log log = LogFactory.getLog(DynamicDataSourceAspect.class);
@Pointcut("@annotation(com.github.houbb.paradise.spring.datasource.dynamic.annotation.DataSource)")
public void myPointcut() {
}
@Around("myPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = getCurrentMethod(point);
//1. 当前方法是否有注解
boolean methodFlag = method.isAnnotationPresent(DataSource.class);
if(methodFlag) {
DataSource datasource = method.getAnnotation(DataSource.class);
setDataSource(datasource);
} else {
//2. 当前类是否有注解
Class clazz = getClass(point);
if(clazz.isAnnotationPresent(DataSource.class)) {
//IDEA BUG
DataSource datasource = (DataSource) clazz.getAnnotation(DataSource.class);
setDataSource(datasource);
}
}
Object result = point.proceed();
DynamicDataSourceHolder.clearDataSource();
return result;
}
private Class getClass(ProceedingJoinPoint point) {
return point.getTarget().getClass();
}
/**
* 设置数据源的值
* @param dataSource 数据源注解
*/
private void setDataSource(DataSource dataSource) {
if(ObjectUtil.isNull(dataSource)) {
return;
}
String value = dataSource.value();
if(StringUtil.isNotEmpty(value)) {
DynamicDataSourceHolder.setDataSource(value);
log.debug("Set datasource with value: {}", value);
}
}
/**
* 获取当前代理的方法
* @param point 切面
* @return 方法
*/
private Method getCurrentMethod(ProceedingJoinPoint point) {
try {
Signature sig = point.getSignature();
MethodSignature msig = (MethodSignature) sig;
Object target = point.getTarget();
return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new DynamicDataSourceException(e);
}
}
}
参考资料
- spring 数据源
https://my.oschina.net/u/1020238/blog/509152
http://zyc1006.iteye.com/blog/1339719
https://www.baeldung.com/spring-data-jpa-multiple-databases
- springboot 数据源
https://blog.csdn.net/qq_35760213/article/details/73863252
https://www.liaoxuefeng.com/article/001484212576147b1f07dc0ab9147a1a97662a0bd270c20000