请关注一下订阅号: 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。
测试我们的应用程序
在运行应用程序之后,模式将被更新,在我们的例子中,每个数据存储区只创建了一个表。
avatarSpringboot测试类
下面代码段中的测试类包含每个数据源的测试方法。在每个方法中,我们都创建一个对象,并使用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上可用,您必须更新属性文件中的数据库连接设置,以避免数据库连接错误。
网友评论