Spring Boot2.x 多数据源配置

1 为什么需要多数据源

在比较大型的项目中,数据库可能会分布在多台服务器上,例如有若干个数据库服务是专门存放日志数据的,又有若干个数据库服务是专门存放业务数据的等等....这时候应用程序如果需要对这两种类型的数据进行处理的话,就需要配置多数据源了。

本文将使用的Spring Boot的版本是2.1.1.RELEASE,其他的版本配置可能会略有不同,具体的建议参考Spring Boot官方文档,文档中有多数据源配置的相关介绍。并且本文仅针对JPA的多数据源配置,其他的ORM例如MyBaties不在本文的讨论范围。

2 项目结构

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── top
│   │   │       └── yeonon
│   │   │           └── multidatasource
│   │   │               ├── controller
│   │   │               │   └── HelloController.java
│   │   │               ├── dbconfig
│   │   │               │   ├── DataSourceConfig.java
│   │   │               │   ├── DockerDatasourceConfig.java
│   │   │               │   └── LocalDataSourceConfig.java
│   │   │               ├── entity
│   │   │               │   ├── docker
│   │   │               │   │   └── User.java
│   │   │               │   └── local
│   │   │               │       └── Course.java
│   │   │               ├── MultiDatasourceApplication.java
│   │   │               └── repository
│   │   │                   ├── docker
│   │   │                   │   └── UserRepository.java
│   │   │                   └── local
│   │   │                       └── CourseRepository.java
│   │   └── resources
│   │       ├── application.properties
│   │       ├── static
│   │       └── templates

上面是项目的包结构,和单一数据源的最大区别就是在entity包和repository包中细分了不同的数据源,例如我这里的是local以及docker,这样区分的好处后面会看到。其他的就没什么了,真有需要注意的地方,会穿插的介绍。

3 数据源的配置

首先是DataSourceConfig类,该类是一个配置类(有@Configuration注解的类),我们在这里配置数据源DataSource:

@Configuration
public class DataSourceConfig {

    @Bean(name = "localDatasource")
    @Qualifier(value = "localDatasource")
    @ConfigurationProperties(prefix = "spring.datasource.local")
    @Primary
    public DataSource localDatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dockerDatasource")
    @Qualifier(value = "dockerDatasource")
    @ConfigurationProperties(prefix = "spring.datasource.docker")
    public DataSource dockerDatasource() {
        return DataSourceBuilder.create().build();
    }
}
  1. @Bean注解就表示该方法是用来配置Bean的,name属性指示了该Bean的名字。
  2. @Qualifier,因为我们这里为DataSource这个类型配置了两个Bean,如果不做特殊处理,Spring在对Bean进行自动注入的时候,将无法知道使用哪个Bean,好的情况是直接报错,令人郁闷的情况是Spring不保错,但是仅仅有一个Bean生效,即另一个被覆盖了,这种情况下的BUG排查是非常麻烦的。
  3. @ConfigurationProperties,该注解是为该Bean配置一个与之对应的属性,prefix即前缀,所以在配置local数据源的时候,我们可以这样配置:spring.datasource.local.dirver-class-name:XXX(其他的同理)。
  4. @Primary,用来标识主库,这个注解是必须的,但是也只能有一个。各位可以自行尝试一下不加或者多加会出现什么错误,加深理解。

重要的就是这几个注解了,DataSource实例就直接用DataSourceBuilder来创建就行了,如果你有自己定制好的DataSource实例也可以不用DataSourceBuilder来创建。

光配置数据源的Bean还没完,在单数据源的情况下,Spring Boot会通过自动配置将数据源DataSource(Spring Boot也会自动的完成对数据源Bean的配置),然后将其注入到LocalContainerEntityManagerFactoryBean实体类工厂以及PlatformTransactionManager事务管理对象里,以此完成对JPA的自动配置。但在多数据源的情况下,Spring Boot自动配置还不能帮我们完成这样的注入,所以需要我们手动配置LocalContainerEntityManagerFactoryBean以及PlatformTransactionManager。下面来看代码:

@Configuration
//开启事务管理
@EnableTransactionManagement
//开启JpaRepositories
@EnableJpaRepositories(
        //在这个JpaRepositories中用到的事务管理器,这里是Ref,即引用
        transactionManagerRef = "localTransactionManager",
        //实体类工厂
        entityManagerFactoryRef = "localEntityManagerFactory",
        //JpaRepositories所在的包名,这也是为什么我们要细分包名的原因
        basePackages = "top.yeonon.multidatasource.repository.local"
)
public class LocalDataSourceConfig {

    //这里必须要要加 @Qualifier(value = "localDatasource")注解
    //否则Spring将不会知道用哪个Bean
    @Autowired
    @Qualifier(value = "localDatasource")
    private DataSource localDatasource;

    //JpaProperties,Spring自动配置会自动加载该Bean
    @Autowired
    private JpaProperties jpaProperties;

    //配置实体类工厂
    @Bean(name = "localEntityManagerFactory")
    //在数据源的配置中,我们将Local配置成了主库,所以必须在这里有@Primary注解
    @Primary
    public LocalContainerEntityManagerFactoryBean localEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        //创建一个工厂
        return builder
                .dataSource(localDatasource)        //数据源实例,在这里指的就是localDatasource这个实例
                .properties(jpaProperties.getProperties()) //加Jpa的属性配置加入进来
                .packages("top.yeonon.multidatasource.entity.local") //实体类包名
                .persistenceUnit("localPersistenceUnit") 
                .build();
    }

    @Bean(name = "localTransactionManager")
    //和上面一样,必须要有该注解
    @Primary
    public PlatformTransactionManager localTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(localEntityManagerFactory(builder).getObject());
    }
}

这样就完成了LocalDatasource的配置,下面直接来看docker数据源的配置吧,和LocalDataSourceConfig非常类似,就不多说了,关键是注意名称:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        transactionManagerRef = "dockerTransactionManager",
        entityManagerFactoryRef = "dockerEntityManagerFactory",
        basePackages = {"top.yeonon.multidatasource.repository.docker"}
)
public class DockerDatasourceConfig {

    @Autowired
    @Qualifier(value = "dockerDatasource")
    private DataSource dockerDatasource;

    @Autowired
    private JpaProperties jpaProperties;

    @Bean(name = "dockerEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean dockerEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dockerDatasource)
                .properties(jpaProperties.getProperties())
                .packages("top.yeonon.multidatasource.entity.docker")
                .persistenceUnit("dockerPersistenceUnit")
                .build();
    }

    @Bean
    public PlatformTransactionManager dockerTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(dockerEntityManagerFactory(builder).getObject());
    }
}

除了名称不同之外,还有就是这里不需要也不应该加入@Primary注解。配置完了吗?其实还没有,别忘了在属性配置文件中配置url,username等...下面是一个示例:

spring.datasource.local.jdbc-url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.local.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.local.username=root
spring.datasource.local.password=124563

spring.datasource.docker.jdbc-url=jdbc:mysql://XXX.XXX.XXX.XXX:4306/test?useSSL=false
spring.datasource.docker.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.docker.username=root
spring.datasource.docker.password=124563

spring.jpa.hibernate.ddl-auto=none

发现了吗?这里url配置和以往不太一样,以前仅仅是url即可,现在需要使用的属性名字是jdbc-url,这是新版的Spring Boot修改的,大家可以尝试一下沿用原来的url,看看错误堆栈,加深理解。

至此,就算是完成配置了,下面是一个简单的测试:

@RestController
@RequestMapping("/hello")
public class HelloController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private CourseRepository courseRepository;

    @GetMapping("user")
    public User user(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @GetMapping("course")
    public Course course(Long id) {
        return courseRepository.findById(id).orElse(null);
    }
}

运行后,往数据库里加点测试数据,访问这里配置的路径,应该就能得到期望的结果了。

终于完成了,其实这个多数据源的配置说难也不难,但也不简单,关键在于要“细心”!!!实体类工厂的Bean以及事务管理器的Bean的名字不要写错,还有就是自动注入以及配置DataSource时候一定要有@Qualifier注解!!!而且@Primary也一定要有,不要多配,也不可少配。

4 小结

最后,总结一下整个配置的流程:

  1. 先配置数据源DataSouce的Bean,因为要为DataSouce这个类型配置多个Bean,所以要有@Qualifier注解来标识定位具体是哪个Bean。还要有属性配置@ConfigurationProperties,否则无法读取属性,就只能在代码中配置url,username等属性了。
  2. 之后为多个不同的数据源配置transactionManager以及entityManagerFactory,核心是将数据源注入到这两个玩意儿内部,然后生成与特定数据源配套的transactionManager和entityManagerFactory。也还要注意在主库的配置中加上@Primary注解。
  3. 在属性配置文件中进行属性配置即可。

5 参考资料

Spring Boot多数据源配置与使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容