如何配置Springboot多数据源

作者: 老老戟 | 来源:发表于2019-06-05 18:13 被阅读3次
avatar

请关注一下订阅号: Rainbow Series

图片.png

介绍

出于安全原因,有时需要连接到多个数据源。一个很好的例子是在多个数据源中存储信用卡信息。如果其中一个数据源被破坏,那么如果没有来自其他数据源的数据,检索到的数据可能是无用的。在本文中,我们将在一个SpringBoot应用程序中为这个信用卡场景配置多个数据源。

项目设置

数据库

我们将使用MySQL作为数据库服务器。介绍部分中描述的信用卡方案将使用以下三个数据库:

1.会员数据库(memberdb):存储持卡人的个人信息,包括其全名和会员ID。

2.持卡人数据库(持卡人数据库):存储持卡人详细信息,包括会员ID和信用卡号码。

3.卡数据库(carddb):存储信用卡信息,包括所有者的全名和信用卡到期日期。 

依赖项

因为我们将使用mysql服务器,所以我们的类路径必须包含mysql数据库连接器依赖项。应用程序的所有依赖项列表如下。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>${commons.dbcp.version}</version>
    </dependency>
</dependencies>

打包

在处理多个数据源时,项目打包结构非常重要。属于某个数据存储的数据模型或实体必须放在其唯一的包中。这种打包策略也适用于JPA存储库。

avatar

如上图所示,我们为每个模型和存储库定义了一个独特的包。

我们还为每个数据源创建了Java配置文件:

guru.springframework.multipledatasources.configuration.CardDataSourceConfiguration

guru.springframework.multipledatasources.configuration.CardHolderDataSourceConfiguration

guru.springframework.multipledatasources.configuration.MemberDataSourceConfiguration

每个数据源配置文件将包含其数据源bean定义,包括实体管理器和事务管理器bean定义。

数据库连接设置

因为我们要配置三个数据源,所以在application.properties文件中需要三组配置。

application.properties文件的代码如下。

    #Store card holder personal details
    app.datasource.member.url=jdbc:mysql://localhost:3306/memberdb?createDatabaseIfNotExist=true
    app.datasource.member.username=root
    app.datasource.member.password=P@ssw0rd#
    app.datasource.member.driverClassName=com.mysql.cj.jdbc.Driver
    #card number  (cardholder id, cardnumber)
    app.datasource.cardholder.url=jdbc:mysql://localhost:3306/cardholderdb?createDatabaseIfNotExist=true
    app.datasource.cardholder.username=root
    app.datasource.cardholder.password=P@ssw0rd#
    app.datasource.cardholder.driverClassName=com.mysql.cj.jdbc.Driver
    #expiration date (card id, expiration month, expiration year)
    app.datasource.card.url=jdbc:mysql://localhost:3306/carddb?createDatabaseIfNotExist=true
    app.datasource.card.username=root
    app.datasource.card.password=P@ssw0rd#
    app.datasource.card.driverClassName=com.mysql.cj.jdbc.Driver
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.generate-ddl=true
    spring.jpa.show-sql=true
    spring.jpa.database=mysql

数据源配置

需要注意的是,在配置多个数据源的过程中,必须将至少一个数据源实例标记为主数据源。否则,应用程序将无法启动,因为Spring将检测到同一类型的多个数据源。

步骤

在本例中,我们将把成员数据源标记为主要数据源。我们将按照以下步骤完成每个数据源配置。

  • 数据源bean定义
  • 实体
  • 实体管理器工厂bean定义
  • 交易管理
  • Spring数据JPA存储库自定义设置

数据源bean定义

要创建数据源bean,我们需要使用application.properties文件中指定的数据源键实例化org.springframework.boot.autoconfigure.jdbc.datasourceproperties类。我们将使用此datasourceproperties对象获取数据源生成器对象。数据源生成器对象使用application.properties文件中的数据库属性创建数据源对象。下面的示例代码显示了数据源的bean定义。

Primary Data Source

@Bean
@Primary
@ConfigurationProperties("app.datasource.member")
public DataSourceProperties memberDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("app.datasource.member.configuration")
public DataSource memberDataSource() {
    return memberDataSourceProperties().initializeDataSourceBuilder()
            .type(HikariDataSource.class).build();
}

Secondary Data Sources

/*cardholder data source */
@Bean
@ConfigurationProperties("app.datasource.cardholder")
public DataSourceProperties cardHolderDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.cardholder.configuration")
public DataSource cardholderDataSource() {
    return cardHolderDataSourceProperties().initializeDataSourceBuilder()
            .type(BasicDataSource.class).build();
}

/*card data source*/
@Bean
@ConfigurationProperties("app.datasource.card")
public DataSourceProperties cardDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.card.configuration")
public DataSource cardDataSource() {
    return cardDataSourceProperties().initializeDataSourceBuilder()
            .type(BasicDataSource.class).build();
}

实体类

由于我们要存储会员、卡和持卡人对象,因此必须使用@entity annotation将其声明为JPA实体。这些实体将由JPA映射到关系数据库表。但我们必须告诉Spring哪些表属于某个数据源。实现这一目标有两种方法。您可以使用@table注释的“schema”字段,如下面第2行代码段所示。

@Entity
@Table(name = "member", schema = "memberdb")
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String memberId;
}

将实体链接到其数据源的另一种方法是通过org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder类方法包()。我们可以在这个方法中传递要扫描@entity注释的包或类。然后,Spring将使用此设置将这些实体映射到表,这些表将通过EMF Builder类的data source()方法在数据源集中创建。请参见下一节中的代码段。

实体管理器工厂bean定义

我们的应用程序将使用SpringDataJPA通过它的存储库接口进行数据访问,该存储库接口将我们从EM(实体管理器)中抽象出来。我们使用EMFbean获取与JPA实体交互的ems实例。因为我们有三个数据源,所以我们需要通过引用实体的数据源和位置来提供EMF构建器类来为每个数据源创建一个em。在我们的示例中,我们将使用这样的org.springframework.orm.jpa.localContainerEntityManagerFactoryBean类定义这个EMF。

   /*Primary Entity manager*/
   @Primary
   @Bean(name = "memberEntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean memberEntityManagerFactory(EntityManagerFactoryBuilder builder) {
       return builder
               .dataSource(memberDataSource())
               .packages(Member.class)
               .build();
   }
   
   /*Secondary Entity Managers*/
   @Bean(name = "cardHolderEntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean cardHolderEntityManagerFactory(
           EntityManagerFactoryBuilder builder) {
       return builder
               .dataSource(cardholderDataSource())
               .packages(CardHolder.class)
               .build();
   }

   @Bean(name = "cardEntityManagerFactory")
   public LocalContainerEntityManagerFactoryBean cardEntityManagerFactory(
           EntityManagerFactoryBuilder builder) {
       return builder
               .dataSource(cardDataSource())
               .packages(Card.class)
               .build();
   }

交易管理

事务管理器的bean定义需要引用实体管理器工厂bean。我们将使用@qualifier注释自动连接特定于数据源事务管理器的实体管理器。我们将在每个数据源配置文件中定义事务管理器。下面是显示成员数据源事务管理器bean定义的代码片段。

    @Primary
    @Bean
    public PlatformTransactionManager memberTransactionManager(
            final @Qualifier("memberEntityManagerFactory") LocalContainerEntityManagerFactoryBean memberEntityManagerFactory) {
        return new JpaTransactionManager(memberEntityManagerFactory.getObject());
    }

JPA存储库配置

因为我们要有多个数据源,所以必须使用spring的@enablejparepositoresAnnotation为每个数据源存储库提供特定的信息。在这个注释中,我们将设置对实体管理器的引用、存储库位置和对事务管理器的引用。下面是成员数据源的JPA存储库设置。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "guru.springframework.multipledatasources.repository.member",
        entityManagerFactoryRef = "memberEntityManagerFactory",
        transactionManagerRef= "memberTransactionManager"
)
public class MemberDataSourceConfiguration { .... }

第3行:

basepackages:我们使用这个字段来设置存储库的基本包。例如,对于成员数据源,它必须指向包guru.springframework.multipledatasources.repository.member

第4行:

EntityManagerFactoryRef:我们使用此字段引用数据源配置文件中定义的实体管理器工厂bean。需要注意的是,EntityManagerFactoryRef值必须与配置文件中定义的实体管理器工厂的bean名称(如果通过@bean annotation的name字段指定,则默认为method name)匹配。

第5行:

TransactionManagerRef:此字段引用数据源配置文件中定义的事务管理器。同样,确保TransactionManagerRef值与事务管理器工厂的bean名称匹配也是很重要的。

完整的数据源配置文件

下面是主数据源(成员数据库)的完整数据源配置。完整的卡和持卡人配置文件可在GitHub上获得。它们与此类似,只是它们是辅助数据源。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "guru.springframework.multipledatasources.repository.member",
        entityManagerFactoryRef = "memberEntityManagerFactory",
        transactionManagerRef= "memberTransactionManager"
)
public class MemberDataSourceConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.member")
    public DataSourceProperties memberDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.member.configuration")
    public DataSource memberDataSource() {
        return memberDataSourceProperties().initializeDataSourceBuilder()
                .type(HikariDataSource.class).build();
    }

    @Primary
    @Bean(name = "memberEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean memberEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(memberDataSource())
                .packages(Member.class)
                .build();
    }

    @Primary
    @Bean
    public PlatformTransactionManager memberTransactionManager(
            final @Qualifier("memberEntityManagerFactory") LocalContainerEntityManagerFactoryBean memberEntityManagerFactory) {
        return new JpaTransactionManager(memberEntityManagerFactory.getObject());
    }

}

需要注意的要点:

实体管理器工厂bean:请确保在创建实体管理器工厂bean时引用了正确的数据源,否则将得到意外的结果。

事务管理器bean:为了确保为事务管理器提供了正确的实体管理器工厂引用,可以使用@qualifier注释。例如,成员数据源的事务管理器将使用名为“memberEntityManagerFactory”的实体管理器工厂bean。

测试我们的应用程序

在运行应用程序之后,模式将被更新,在我们的例子中,每个数据存储区只创建了一个表。

avatar

Springboot测试类

下面代码段中的测试类包含每个数据源的测试方法。在每个方法中,我们都创建一个对象,并使用JPA存储库将其持久化到数据库中。最后,我们检查数据库中是否存在这些数据。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MultipledatasourcesApplicationTests {

    /*
    * We will be using mysql databases we configured in our properties file for our tests
    * Make sure your datasource connections are correct otherwise the test will fail
    * */

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private CardHolderRepository cardHolderRepository;

    @Autowired
    private CardRepository cardRepository;

    private Member member;
    private Card card;
    private CardHolder cardHolder;

    @Before
    public void initializeDataObjects(){

        member = new Member();
        member.setMemberId("M001");
        member.setName("Maureen Mpofu");

        cardHolder = new CardHolder();
        cardHolder.setCardNumber("4111111111111111");
        cardHolder.setMemberId(member.getMemberId());

        card = new Card();
        card.setExpirationMonth(01);
        card.setExpirationYear(2020);
        card.setName(member.getName());

    }

    @Test
    public void shouldSaveMemberToMemberDB() {
        Member savedMember =memberRepository.save(member);
        Optional<Member> memberFromDb= memberRepository.findById(savedMember.getId());
        assertTrue(memberFromDb.isPresent());
    }

    @Test
    public void shouldSaveCardHolderToCardHolderDB() {
        CardHolder savedCardHolder =cardHolderRepository.save(cardHolder);
        Optional<CardHolder> cardHolderFromDb= cardHolderRepository.findById(savedCardHolder.getId());
        assertTrue(cardHolderFromDb.isPresent());
    }

    @Test
    public void shouldSaveCardToCardDB() {
        Card savedCard = cardRepository.save(card);
        Optional<Card> cardFromDb= cardRepository.findById(savedCard.getId());
        assertTrue(cardFromDb.isPresent());
    }
}

我们的测试用例通过了,数据库表记录了通过应用程序持久化的数据(由下面的屏幕截图指示)。

成员数据库

avatar

卡片数据库

avatar

持卡人数据库

avatar

结论

我们在Springboot应用程序中配置了多个数据源。如果您希望获得相同的结果,请注意我们确定的步骤,这一点很重要。我们的示例应用程序的源代码在GitHub上可用,您必须更新属性文件中的数据库连接设置,以避免数据库连接错误。

相关文章

网友评论

    本文标题:如何配置Springboot多数据源

    本文链接:https://www.haomeiwen.com/subject/xqbsxctx.html