In some special cases, sometimes we have to connect to multi databases to handle the business logic. In this post I will show you step by step configuration for spring boot service.
- As you can see in the picture above. We have two sub packages (primary, secondary) in entity and repository package respectively, one is primary package which contains all repositories and entities for the primary datasource. The second one is the secondary package which contains all repositories and entities for the secondary datasource.
Then we also have configurations package which contains all datasource configuration.
to connect to two database schemas we need to add some configuration in the application.yml as below:
application.yml
1 2 3 4 5 6 7 8 910111213141516
spring:datasource:driverClassName:com.mysql.jdbc.Driverurl:jdbc:mysql://localhost:3306/primaryDb?useSSL=false#This is the primary DB connection urlusername:rootpassword:passwordsecond-datasource:driverClassName:com.mysql.jdbc.Driverurl:jdbc:mysql://localhost:3306/secondaryDb?useSSL=false#This is the secondary DB connection urlusername:rootpassword:passwordjpa:hibernate.ddl-auto:updatehibernate.dialect:org.hibernate.dialect.MySQL5Dialectgenerate-ddl:trueshow-sql:true
Step 4: Create And Config Configuration File For Primary Schema#
To config for primary schema we need to create a configuration file named PrimarySchemaConfiguration.java in the package configurations
The configuration for the primary datasource is showed as below:
packagecom.springboot.data.jpa.multi.datasource.configuration;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Primary;importorg.springframework.core.env.Environment;importorg.springframework.data.jpa.repository.config.EnableJpaRepositories;importorg.springframework.jdbc.datasource.DriverManagerDataSource;importorg.springframework.orm.jpa.JpaTransactionManager;importorg.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;importorg.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;importorg.springframework.transaction.PlatformTransactionManager;importjavax.sql.DataSource;importjava.util.HashMap;importjava.util.Objects;@Configuration/** use this anotation to define the base package repository for primary data source, the EntityFactory and the transactionManager **/@EnableJpaRepositories(basePackages="com.springboot.data.jpa.multi.datasource.repository.primary",entityManagerFactoryRef="primaryEntityManager",transactionManagerRef="primaryTransactionManager")publicclassPrimarySchemaConfiguration{@AutowiredprivateEnvironmentenv;@Bean(name="primaryDataSource")@PrimarypublicDataSourceprimaryDataSourceConfiguration(){DriverManagerDataSourcedataSource=newDriverManagerDataSource();dataSource.setDriverClassName(Objects.requireNonNull(env.getProperty("spring.datasource.driverClassName")));dataSource.setUrl(env.getProperty("spring.datasource.url"));dataSource.setUsername(env.getProperty("spring.datasource.username"));dataSource.setPassword(env.getProperty("spring.datasource.password"));returndataSource;}@Bean(name="primaryEntityManager")@PrimarypublicLocalContainerEntityManagerFactoryBeanprimaryEntityManager(){LocalContainerEntityManagerFactoryBeanem=newLocalContainerEntityManagerFactoryBean();em.setDataSource(primaryDataSourceConfiguration());/** this is the package url which contains entities of primary datasource **/em.setPackagesToScan("com.springboot.data.jpa.multi.datasource.entity.primary");HibernateJpaVendorAdaptervendorAdapter=newHibernateJpaVendorAdapter();em.setJpaVendorAdapter(vendorAdapter);HashMap<String,Object>properties=newHashMap<String,Object>();properties.put("hibernate.hbm2ddl.auto",env.getProperty("spring.jpa.hibernate.ddl-auto"));properties.put("hibernate.dialect",env.getProperty("spring.jpa.hibernate.hibernate.dialect"));em.setJpaPropertyMap(properties);returnem;}@Bean(name="primaryTransactionManager")@PrimarypublicPlatformTransactionManagerprimaryTransactionManager(){JpaTransactionManagerjpaTransactionManager=newJpaTransactionManager();jpaTransactionManager.setEntityManagerFactory(primaryEntityManager().getObject());returnjpaTransactionManager;}}
Step 5: Create And Config Configuration File For Secondary Schema#
To config for secondary schema we need to create a configuration file named secondaryDataSourceConfiguration.java in the package configurations
The configuration for the secondary datasource is showed as below:
packagecom.springboot.data.jpa.multi.datasource.configuration;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.env.Environment;importorg.springframework.data.jpa.repository.config.EnableJpaRepositories;importorg.springframework.jdbc.datasource.DriverManagerDataSource;importorg.springframework.orm.jpa.JpaTransactionManager;importorg.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;importorg.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;importorg.springframework.transaction.PlatformTransactionManager;importjavax.sql.DataSource;importjava.util.HashMap;importjava.util.Objects;@Configuration/** use this anotation to define the base package repository for secondary data source, the EntityFactory and the transactionManager **/@EnableJpaRepositories(basePackages="com.springboot.data.jpa.multi.datasource.repository.secondary",entityManagerFactoryRef="secondaryEntityManager",transactionManagerRef="secondaryTransactionManager")publicclassSecondarySchemaConfiguration{@AutowiredprivateEnvironmentenv;@Bean(name="secondaryDataSource")publicDataSourcesecondaryDataSourceConfiguration(){DriverManagerDataSourcedataSource=newDriverManagerDataSource();dataSource.setDriverClassName(Objects.requireNonNull(env.getProperty("spring.second-datasource.driverClassName")));dataSource.setUrl(env.getProperty("spring.second-datasource.url"));dataSource.setUsername(env.getProperty("spring.second-datasource.username"));dataSource.setPassword(env.getProperty("spring.second-datasource.password"));returndataSource;}@Bean(name="secondaryEntityManager")publicLocalContainerEntityManagerFactoryBeansecondaryEntityManager(){LocalContainerEntityManagerFactoryBeanem=newLocalContainerEntityManagerFactoryBean();em.setDataSource(secondaryDataSourceConfiguration());/** this is the package which contains entities of primary datasource **/em.setPackagesToScan("com.springboot.data.jpa.multi.datasource.entity.secondary");HibernateJpaVendorAdapterjpaVendorAdapter=newHibernateJpaVendorAdapter();em.setJpaVendorAdapter(jpaVendorAdapter);HashMap<String,Object>properties=newHashMap<>();properties.put("hibernate.hbm2ddl.auto",env.getProperty("spring.jpa.hibernate.ddl-auto"));properties.put("hibernate.dialect",env.getProperty("spring.jpa.hibernate.hibernate.dialect"));em.setJpaPropertyMap(properties);returnem;}@Bean(name="secondaryTransactionManager")publicPlatformTransactionManagersecondaryTransactionManager(){JpaTransactionManagertransactionManager=newJpaTransactionManager();transactionManager.setEntityManagerFactory(secondaryEntityManager().getObject());returntransactionManager;}}
Step 6: Create And Config Configuration File For Transaction Chain#
Sometime we will work with two/multiple databases in a single request, and if we have an error in any transaction of two schema while executing. We will need to revert all data which are saved in one of two databases. Thus we need to configure the transaction chain to handle this issue.
Assume that we save a data into DB A successfully then we continous saving data to DB B. Unfortunally, saving data to DB B is failed and we want to revert the data which is saved into DB A.
We need to create a configuration file named TransactionChainConfiguration.java in configurations package. The contents of file are showed as below:
TransactionChainConfiguration.java
1 2 3 4 5 6 7 8 9101112131415161718192021222324
packagecom.springboot.data.jpa.multi.datasource.configuration;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.transaction.ChainedTransactionManager;importorg.springframework.transaction.PlatformTransactionManager;@ConfigurationpublicclassTransactionChainConfiguration{/** we will create ChainedTransactionManager by two PlatformTransactionManager which are `primaryTransactionManager` and `secondaryTransactionManager` because we want to revert transaction if one of them happens issue in saving data. **/@Bean(name="chainedTransactionManager")publicChainedTransactionManagertransactionChainConfiguration(@Qualifier("primaryTransactionManager")PlatformTransactionManagerprimaryTransaction,@Qualifier("secondaryTransactionManager")PlatformTransactionManagersecondaryTransaction){returnnewChainedTransactionManager(primaryTransaction,secondaryTransaction);}}
Step 7: Declare Entity In Using With Multi Datasources#
For delaration entities, please look at the examples belows:
packagecom.springboot.data.jpa.multi.datasource.entity.primary;importorg.hibernate.annotations.Type;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;importjavax.persistence.Table;importjava.util.Date;importjava.util.UUID;@Entity/** This entity is used for primary DB with the schema = "primaryDb" **/@Table(schema="primaryDb",name="customers")publicclassPrimaryCustomerEntity{@Id@GeneratedValue@Type(type="uuid-char")privateUUIDid;privateStringfullName;privateStringemail;privateStringaddress;privateStringphone;privateDatedob;publicUUIDgetId(){returnid;}publicvoidsetId(UUIDid){this.id=id;}publicStringgetFullName(){returnfullName;}publicvoidsetFullName(StringfullName){this.fullName=fullName;}publicStringgetEmail(){returnemail;}publicvoidsetEmail(Stringemail){this.email=email;}publicStringgetAddress(){returnaddress;}publicvoidsetAddress(Stringaddress){this.address=address;}publicStringgetPhone(){returnphone;}publicvoidsetPhone(Stringphone){this.phone=phone;}publicDategetDob(){returndob;}publicvoidsetDob(Datedob){this.dob=dob;}}
Step 8: Using Transaction Of Primary And Secondary Datasource In Service Class#
To use Transactional for primary or secondary DB when we need to get/update data we need to declare which Transactional (belong to which DB) that we want to use. Let's see the example below
We will create a service class named GeneralService. Then we Autowired two repositories, one for primary datasource and one for secondary datasource.
packagecom.springboot.data.jpa.multi.datasource.service;importcom.springboot.data.jpa.multi.datasource.entity.primary.PrimaryCustomerEntity;importcom.springboot.data.jpa.multi.datasource.entity.secondary.CustomerEntity;importcom.springboot.data.jpa.multi.datasource.model.Customer;importcom.springboot.data.jpa.multi.datasource.repository.primary.PrimaryCustomerRepository;importcom.springboot.data.jpa.multi.datasource.repository.secondary.CustomerRepository;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.EnableTransactionManagement;importorg.springframework.transaction.annotation.Transactional;importjava.util.Arrays;importjava.util.List;importjava.util.UUID;@Service/** use @EnableTransactionManagement to enable using Transactional Management **/@EnableTransactionManagementpublicclassGeneralService{@AutowiredprivatePrimaryCustomerRepositoryprimaryCustomerRepository;@AutowiredprivateCustomerRepositorycustomerRepository;/** use @Transactional with value (bean name of primaryTransactionManager) so the Jpa will know which transaction that we want to use. **/@Transactional(value="primaryTransactionManager")publicUUIDsaveDataToPrimaryCustomerSchema(Customercustomer){PrimaryCustomerEntityprimaryCustomerEntity=newPrimaryCustomerEntity();primaryCustomerEntity.setAddress(customer.getAddress());primaryCustomerEntity.setEmail(customer.getEmail());primaryCustomerEntity.setFullName(customer.getFullName());primaryCustomerEntity.setPhone(customer.getPhone());primaryCustomerEntity.setDob(customer.getDob());returnprimaryCustomerRepository.save(primaryCustomerEntity).getId();}/** use @Transactional with value (bean name of secondaryTransactionManager) so the Jpa will know which transaction that we want to use. **/@Transactional(value="secondaryTransactionManager")publicUUIDsaveDataToSecondaryCustomerSchema(Customercustomer){CustomerEntitycustomerEntity=newCustomerEntity();customerEntity.setAddress(customer.getAddress());customerEntity.setEmail(customer.getEmail());customerEntity.setFullName(customer.getFullName());customerEntity.setPhone(customer.getPhone());customerEntity.setDob(customer.getDob());returncustomerRepository.save(customerEntity).getId();}/** use @Transactional with value (bean name of chainedTransactionManager) so the Jpa will know which transaction that we want to use. If saving data to secondary is failed, so the data which is saved in primary DB will be reverted. **/@Transactional(value="chainedTransactionManager")publicList<UUID>saveDataToAllSchemas(Customercustomer){UUIDprimaryCustomerId=saveDataToPrimaryCustomerSchema(customer);UUIDcustomerId=saveDataToSecondaryCustomerSchema(customer);/** this comment function is used for revert all data in DB when this function is executed successfully **/// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();returnArrays.asList(primaryCustomerId,customerId);}}