DruidDataSource 的配置与介绍
在实际工作中,由于 HikariCP 和 Druid 各有千秋,国内的很多开发者都使用 Druid 作为数据源,我们看看都是怎么配置的,每一步都很简单。
第一步:引入依赖。
两种方式,一种是基于Maven的,另外一种是Gradle
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
implementation 'com.alibaba:druid-spring-boot-starter:1.2.1'
第二步:配置数据源。
spring.datasource.druid.url= # 或spring.datasource.url=
spring.datasource.druid.username= # 或spring.datasource.username=
spring.datasource.druid.password= # 或spring.datasource.password=
spring.datasource.druid.driver-class-name= #或 spring.datasource.driver-class-name=
第三步:配置连接池。
spring.datasource.druid.initial-size=
spring.datasource.druid.max-active=
spring.datasource.druid.min-idle=
spring.datasource.druid.max-wait=
spring.datasource.druid.pool-prepared-statements=
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=
spring.datasource.druid.max-open-prepared-statements= #和上面的等价
spring.datasource.druid.validation-query=
spring.datasource.druid.validation-query-timeout=
spring.datasource.druid.test-on-borrow=
spring.datasource.druid.test-on-return=
spring.datasource.druid.test-while-idle=
spring.datasource.druid.time-between-eviction-runs-millis=
spring.datasource.druid.min-evictable-idle-time-millis=
spring.datasource.druid.max-evictable-idle-time-millis=
spring.datasource.druid.filters= #配置多个英文逗号分隔
....//more
通过以上三步就可以完成 Druid 数据源的配置了。
生产环境多数据源的处理方法
第一种方式:多个数据源的 @Configuration 的配置方法
可以参考Mybatis配置多数据源(pgsql和mysql)
第二种方式:利用 AbstractRoutingDataSource 配置多数据源
AbstractRoutingDataSource 帮我们实现了动态获得数据源的可能性。下面还是通过一个例子看一下它是怎么使用的。
第一步:定一个数据源的枚举类,用来标示数据源有哪些。
/**
* 定义一个数据源的枚举类
*/
public enum RoutingDataSourceEnum {
DB1, //实际工作中枚举的语义可以更加明确一点;
DB2;
public static RoutingDataSourceEnum findbyCode(String dbRouting) {
for (RoutingDataSourceEnum e : values()) {
if (e.name().equals(dbRouting)) {
return e;
}
}
return db1;//没找到的情况下,默认返回数据源1
}
}
第二步:新增 DataSourceRoutingHolder,用来存储当前线程需要采用的数据源。
/**
* 利用ThreadLocal来存储,当前的线程使用的数据
*/
public class DataSourceRoutingHolder {
private static ThreadLocal<RoutingDataSourceEnum> threadLocal = new ThreadLocal<>();
public static void setBranchContext(RoutingDataSourceEnum dataSourceEnum) {
threadLocal.set(dataSourceEnum);
}
public static RoutingDataSourceEnum getBranchContext() {
return threadLocal.get();
}
public static void clearBranchContext() {
threadLocal.remove();
}
}
第三步:配置 RoutingDataSourceConfig,用来指定哪些 Entity 和 Repository 采用动态数据源。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
//数据源的repository的包路径,这里我们覆盖db1和db2的包路径
basePackages = {"com.example.jpa.example1"},
entityManagerFactoryRef = "routingEntityManagerFactory",
transactionManagerRef = "routingTransactionManager"
)
public class RoutingDataSourceConfig {
@Autowired
@Qualifier("db1DataSource")
private DataSource db1DataSource;
@Autowired
@Qualifier("db2DataSource")
private DataSource db2DataSource;
/**
* 创建RoutingDataSource,引用我们之前配置的db1DataSource和db2DataSource
*
* @return
*/
@Bean(name = "routingDataSource")
public DataSource dataSource() {
Map<Object, Object> dataSourceMap = Maps.newHashMap();
dataSourceMap.put(RoutingDataSourceEnum.DB1, db1DataSource);
dataSourceMap.put(RoutingDataSourceEnum.DB2, db2DataSource);
RoutingDataSource routingDataSource = new RoutingDataSource();
//设置RoutingDataSource的默认数据源
routingDataSource.setDefaultTargetDataSource(db1DataSource);
//设置RoutingDataSource的数据源列表
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
/**
* 类似db1和db2的配置,唯一不同的是,这里采用routingDataSource
* @param builder
* @param routingDataSource entityManager依赖routingDataSource
* @return
*/
@Bean(name = "routingEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("routingDataSource") DataSource routingDataSource) {
return builder.dataSource(routingDataSource).packages("com.example.jpa.example1") //数据routing的实体所在的路径,这里我们覆盖db1和db2的路径
.persistenceUnit("db-routing")// persistenceUnit的名字采用db-routing
.build();
}
/**
* 配置数据的事务管理者,命名为routingTransactionManager依赖routtingEntityManagerFactory
*
* @param routingEntityManagerFactory
* @return
*/
@Bean(name = "routingTransactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("routingEntityManagerFactory") EntityManagerFactory routingEntityManagerFactory) {
return new JpaTransactionManager(routingEntityManagerFactory);
}
}
路由数据源配置与 DataSource1Config 和 DataSource2Config 有相互覆盖关系,这里我们直接覆盖 db1 和 db2 的包路径,以便于我们的动态数据源生效。
第四步:写一个 MVC 拦截器,用来指定请求分别采用什么数据源。
新建一个类 DataSourceInterceptor,用来在请求前后指定数据源:
/**
* 动态路由的实现逻辑,我们通过请求里面的db-routing,来指定此请求采用什么数据源
*/
@Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {
/**
* 请求处理之前更改线程里面的数据源
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String dbRouting = request.getHeader("db-routing");
DataSourceRoutingHolder.setBranchContext(RoutingDataSourceEnum.findByCode(dbRouting));
return super.preHandle(request, response, handler);
}
/**
* 请求结束之后清理线程里面的数据源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
DataSourceRoutingHolder.clearBranchContext();
}
}
同时我们需要在实现 WebMvcConfigurer 的配置里面,把我们自定义拦截器 dataSourceInterceptor 加载进去,代码如下:
/**
* 实现WebMvcConfigurer
*/
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private DataSourceInterceptor dataSourceInterceptor;
//添加自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
...//其他不变的代码省略}
此处我们采用的是 MVC 的拦截器机制动态改变的数据配置,你也可以使用自己的 AOP 任意的拦截器,如事务拦截器、Service 的拦截器等,都可以实现。需要注意的是,要在开启事务之前配置完毕。
第五步:启动测试。
我们在 Http 请求头里面加上 db-routing:DB2,那么本次请求就会采用数据源 2 进行处理,请求代码如下:
POST /user/info HTTP/1.1
Host: 127.0.0.1:8089
Content-Type: application/json
db-routing: DB2
Cache-Control: no-cache
Postman-Token: 56d8dc02-7f3e-7b95-7ff1-572a4bb7d102
{"ages":10}
由于实际问题可能更为复杂,需要考虑多线程、线程安全等问题,因此对于我工作中的项目使用较少。只是简单学习下~