springboot多数据源&动态数据源(主从)
多数据源
使用Spring Boot时,默认情况下,配置DataSource非常容易。Spring Boot会自动为我们配置好一个DataSource。
如果在application.yml中指定了spring.datasource的相关配置,Spring Boot就会使用该配置创建一个DataSource。如果在application.yml中没有指定任何spring.datasource的相关配置,Spring Boot会在classpath中搜索H2、hsqldb等内存数据库的jar包,如果找到了,就会自动配置一个内存数据库的DataSource,所以,我们只要引入jar包即可。例如,配置一个hsqldb数据源:
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
但是,在某些情况下,如果我们需要配置多个数据源,应该如何在Spring Boot中配置呢?
我们以JDBC为例,演示如何在Spring Boot中配置两个DataSource。对应的,我们会创建两个JdbcTemplate的Bean,分别使用这两个数据源。
首先,我们必须在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:
这两个DataSource都使用hsqldb,但是数据库是不同的。此外,在使用多数据源的时候,所有必要配置都不能省略。
其次,我们需要自己创建两个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();
}
}
对于每一个DataSource
,我们都必须通过@ConfigurationProperties(prefix = "xxx")
指定配置项的前缀。
紧接着,我们创建两个``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);
}
注意到secondJdbcTemplate在创建时,传入的DataSource必须用@Qualifier("secondDatasource")
声明,这样,才能使用第二个DataSource。
现在,我们就创建了两个JdbcTemplate的Bean。在需要使用第一个JdbcTemplate的地方,我们直接注入:
@Component
public class SomeService {
@Autowired
JdbcTemplate jdbcTemplate;
}
在需要使用第二个JdbcTemplate的地方,我们注入时需要用@Qualifier("secondJdbcTemplate")标识:
@Component
public class AnotherService {
@Autowired
@Qualifier("secondJdbcTemplate")
JdbcTemplate secondJdbcTemplate;
}
这样,我们就可以针对不同的数据源,用不同的JdbcTemplate进行操作。
注意事项
当存在多个相同类型的Bean,例如,多个DataSource,多个JdbcTemplate时,强烈建议总是使用@Primary把其中某一个Bean标识为“主要的”,使用@Autowired注入时会首先使用被标记为@Primary的Bean。
相同类型的其他Bean,每一个都需要用@Bean(name="xxx")标识名字,并且,在使用@Autowired注入时配合@Qualifier("xxx")指定注入的Bean的名字。
完整的示例工程源码请参考:
https://github.com/michaelliao/springcloud/tree/master/data-multidatasource
动态数据源
在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式。在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持。
Spring内置了一个AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源。因为AbstractRoutingDataSource也是一个DataSource接口,因此,应用程序可以先设置好key, 访问数据库的代码就可以从AbstractRoutingDataSource拿到对应的一个真实的数据源,从而访问指定的数据库。它的结构看起来像这样:
第一步:配置多数据源
首先,我们在SpringBoot中配置两个数据源,其中第二个数据源是ro-datasource:
spring:
datasource:
jdbc-url: jdbc:mysql://localhost/test
username: rw
password: rw_password
driver-class-name: com.mysql.jdbc.Driver
hikari:
pool-name: HikariCP
auto-commit: false
...
ro-datasource:
jdbc-url: jdbc:mysql://localhost/test
username: ro
password: ro_password
driver-class-name: com.mysql.jdbc.Driver
hikari:
pool-name: HikariCP
auto-commit: false
...
在开发环境下,没有必要配置主从数据库。只需要给数据库设置两个用户,一个rw具有读写权限,一个ro只有SELECT权限,这样就模拟了生产环境下对主从数据库的读写分离。
在SpringBoot的配置代码中,我们初始化两个数据源:
@SpringBootApplication
public class MySpringBootApplication {
/**
* Master data source.
*/
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
DataSource masterDataSource() {
logger.info("create master datasource...");
return DataSourceBuilder.create().build();
}
/**
* Slave (read only) data source.
*/
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "spring.ro-datasource")
DataSource slaveDataSource() {
logger.info("create slave datasource...");
return DataSourceBuilder.create().build();
}
...
}
第二步:编写RoutingDataSource
然后,我们用Spring内置的RoutingDataSource,把两个真实的数据源代理为一个动态数据源:
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return "masterDataSource";
}
}
对这个RoutingDataSource
,需要在SpringBoot中配置好并设置为主数据源:
@SpringBootApplication
public class MySpringBootApplication {
@Bean
@Primary
DataSource primaryDataSource(
@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource
) {
logger.info("create routing datasource...");
Map<Object, Object> map = new HashMap<>();
map.put("masterDataSource", masterDataSource);
map.put("slaveDataSource", slaveDataSource);
RoutingDataSource routing = new RoutingDataSource();
routing.setTargetDataSources(map);
routing.setDefaultTargetDataSource(masterDataSource);
return routing;
}
...
}
现在,RoutingDataSource
配置好了,但是,路由的选择是写死的,即永远返回"masterDataSource",
- 现在问题来了:如何存储动态选择的key以及在哪设置key?
在Servlet的线程模型中,使用ThreadLocal存储key最合适,因此,我们编写一个RoutingDataSourceContext
,来设置并动态存储key:
public class RoutingDataSourceContext implements AutoCloseable {
// holds data source key in thread local:
static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();
public static String getDataSourceRoutingKey() {
String key = threadLocalDataSourceKey.get();
return key == null ? "masterDataSource" : key;
}
public RoutingDataSourceContext(String key) {
threadLocalDataSourceKey.set(key);
}
public void close() {
threadLocalDataSourceKey.remove();
}
}
然后,修改RoutingDataSource
,获取key的代码如下:
public class RoutingDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
这样,在某个地方,例如一个Controller的方法内部,就可以动态设置DataSource的Key:
@Controller
public class MyController {
@Get("/")
public String index() {
String key = "slaveDataSource";
try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
// TODO:
return "html... www.liaoxuefeng.com";
}
}
}
到此为止,我们已经成功实现了数据库的动态路由访问。
这个方法是可行的,但是,需要读从数据库的地方,就需要加上一大段try (RoutingDataSourceContext ctx = ...) {}代码,使用起来十分不便。有没有方法可以简化呢?
有!
我们仔细想想,Spring提供的声明式事务管理,就只需要一个@Transactional()
注解,放在某个Java方法上,这个方法就自动具有了事务。
我们也可以编写一个类似的@RoutingWith("slaveDataSource")
注解,放到某个Controller的方法上,这个方法内部就自动选择了对应的数据源。代码看起来应该像这样:
@Controller
public class MyController {
@Get("/")
@RoutingWith("slaveDataSource")
public String index() {
return "html... www.liaoxuefeng.com";
}
}
这样,完全不修改应用程序的逻辑,只在必要的地方加上注解,自动实现动态数据源切换,这个方法是最简单的。
想要在应用程序中少写代码,我们就得多做一点底层工作:必须使用类似Spring实现声明式事务的机制,即用AOP实现动态数据源切换。
实现这个功能也非常简单,编写一个RoutingAspect
,利用AspectJ实现一个Around
拦截:
@Aspect
@Component
public class RoutingAspect {
@Around("@annotation(routingWith)")
public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {
String key = routingWith.value();
try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
return joinPoint.proceed();
}
}
}
注意方法的第二个参数RoutingWith是Spring传入的注解实例,我们根据注解的value()获取配置的key。编译前需要添加一个Maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
到此为止,我们就实现了用注解动态选择数据源的功能。最后一步重构是用字符串常量替换散落在各处的"masterDataSource"和"slaveDataSource"。
使用限制
受Servlet线程模型的局限,动态数据源不能在一个请求内设定后再修改,也就是@RoutingWith不能嵌套。此外,@RoutingWith和@Transactional混用时,要设定AOP的优先级。
本文代码需要SpringBoot支持,JDK 1.8编译并打开-parameters编译参数。
另外动态数据源可以参kao https://github.com/baomidou/dynamic-datasource-spring-boot-starter
springboot多数据源&动态数据源(主从)的更多相关文章
- SpringBoot和Mycat动态数据源项目整合
SpringBoot项目整合动态数据源(读写分离) 1.配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理 ...
- SpringBoot+Mybatis 实现动态数据源切换方案
背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...
- 记springboot + MP +Hikari动态数据源配置
环境准备: springboot 2.1.6 mybatis-plus 数据库驱动 boot 自带hikari驱动 步骤1: 导入多数据源启动工具类 <!-- 多数据源支持 -->< ...
- SpringBoot(十一)-- 动态数据源
SpringBoot中使用动态数据源可以实现分布式中的分库技术,比如查询用户 就在用户库中查询,查询订单 就在订单库中查询. 一.配置文件application.properties # 默认数据源 ...
- SpringBoot之多数据源动态切换数据源
原文:https://www.jianshu.com/p/cac4759b2684 实现 1.建库建表 首先,我们在本地新建三个数据库名分别为master,slave1,slave2,我们的目前就是写 ...
- Spring 注解动态数据源设计实践
Spring 动态数据源 动态数据源是什么?解决了什么问题? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过Spr ...
- Spring主从数据库的配置和动态数据源切换原理
原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配 ...
- springboot+mybatis实现动态切换数据源
前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源.我上网收集了一些资料整合了一下,虽然最后这个需求不了了之了,但是多数据源动态切换还是蛮好用的,所以记录一下,或许以 ...
- SpringBoot整合MyBatisPlus配置动态数据源
目录 SpringBoot整合MyBatisPlus配置动态数据源 SpringBoot整合MyBatisPlus配置动态数据源 推文:2018开源中国最受欢迎的中国软件MyBatis-Plus My ...
随机推荐
- element和iView初步研究(-)
element 1.可以通过npm 和使用cdn 2,支持多种语言组件 3.基本组件还是可以的 iView iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产 ...
- 面试题常考&必考之--js中的call()和apply()
apply: 接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组.当第一个参数为null.undefined的时候,默认指向window. call: 第一个参数是要绑定给thi ...
- phpstudy升级5.7
1:备份当前数据库数据. 最好是导成 SQL 文件 2:备份 PhpStudy 下的 MySQL 文件夹.以防升级失败.还可以使用旧版本的数据库 3:下载MySQL5.7.解压.然后放在 PhpStu ...
- Web开发者易犯的五大严重错误
无论你是编程高手,还是技术爱好者,在进行Web开发过程中,总避免不了犯各种各样的错误. 犯了错误,可以改正.但如果犯了某些错误,则会带来重大损失.遗憾.令人惊讶的是,这些错误往往是最普通,最容易避免. ...
- [CSP-S模拟测试]:寿司(暴力)
题目描述 小$c$是一名$oier$.最近,他发现他的数据结构好像学傻了.因为他在刷题时碰到了一道傻逼数据结构题,强行使用了平衡树来解决,卡着时间$AC$.为此,他被狠狠地嘲讽了一番.于是,小$c$找 ...
- Kaggle 房价预测问题参考资料
作者的 Kaggle 主页:https://www.kaggle.com/pavansanagapati Tutorial - Housing Prices Model Prediction http ...
- WIN7系统JavaEE(java+tomcat7+Eclipse)环境配置
https://jingyan.baidu.com/article/3a2f7c2e62d25e26afd611fa.html WIN7系统JavaEE(java+tomcat7+Eclipse)环境 ...
- sqlserver 查询当前阻塞进程 并杀掉
select * from master.dbo.sysprocesses where DB_NAME(dbid)=’test’ and spid<>@@SPID 看看阻塞的进程 然后ki ...
- SourceTree跳过初始设置
转载https://www.cnblogs.com/xiofee/p/sourcetree_pass_initialization_setup.html 如何跳过初始设置,只需要在安装路径中添加一个a ...
- tar 打包文件
tar支持通配符, 可以用* ?等来指定多个文件 在指明压缩文件名的时候, 一定要带上 -f选项 压缩文件名中间 最好不要带特殊符号, 如& ? * +等, shell bash 会作一些特殊 ...