Multiple Data Source:
Spring data JPA with multiple data source, Docker MariaDB, Sealed Interfaces, Record Class and YAML file configuration.
Technology
- Spring Boot 2.7.2
- Java 17 (Zulu)
- Docker
- Maven
- IntelliJ IDEA
- Postman
Project Structure
Configuration Spring Boot project
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>multiple-data-sources-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>multiple-data-sources-jpa</name>
<description>Demo project multiple data sources jpa</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 9000
servlet:
context-path: /
datasource:
user:
url: jdbc:mariadb://localhost:3306/test1
username: root
password: mypass
driverClassName: org.mariadb.jdbc.Driver
ddlAuto: create-drop
dialect: org.hibernate.dialect.MySQL5Dialect
company:
url: jdbc:mariadb://localhost:3306/test2
username: root
password: mypass
driverClassName: org.mariadb.jdbc.Driver
ddlAuto: create-drop
dialect: org.hibernate.dialect.MySQL5Dialect
brand:
url: jdbc:mariadb://localhost:3306/test3
username: root
password: mypass
driverClassName: org.mariadb.jdbc.Driver
ddlAuto: create-drop
dialect: org.hibernate.dialect.MySQL5Dialect
Record
- UserRecord
package com.henry.record;
public record UserRecord(String url, String username, String password, String driverClassName,
String ddlAuto, String dialect) {
}
- CompanyRecord
package com.henry.record;
public record CompanyRecord(String url, String username, String password, String driverClassName,
String ddlAuto, String dialect) {
}
- BrandRecord
package com.henry.record;
public record BrandRecord(String url, String username, String password, String driverClassName,
String ddlAuto, String dialect) {
}
Model
- user
package com.henry.model.user;
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(name = "last_name")
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
- company
package com.henry.model.company;
import javax.persistence.*;
@Entity
@Table(name = "companies")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- brand
package com.henry.model.brand;
import javax.persistence.*;
@Entity
@Table(name = "brands")
public class Brand {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Repository
- user
package com.henry.repository.user;
import com.henry.model.user.User;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User,Long> {
}
- company
package com.henry.repository.company;
import com.henry.model.company.Company;
import org.springframework.data.repository.CrudRepository;
public interface CompanyRepository extends CrudRepository<Company,Long> {
}
- brand
package com.henry.repository.brand;
import com.henry.model.brand.Brand;
import org.springframework.data.repository.CrudRepository;
public interface BrandRepository extends CrudRepository<Brand,Long> {
}
Service
- DefaultService
package com.henry.service;
public sealed interface DefaultService<T, G> permits UserServiceImpl, CompanyServiceImpl, BrandServiceImpl {
T save(T obj);
Iterable<T> findAll();
T findById(G id);
}
- UserServiceImpl
package com.henry.service;
import com.henry.model.user.User;
import com.henry.repository.user.UserRepository;
import org.springframework.stereotype.Service;
@Service
public final class UserServiceImpl implements DefaultService<User,Long> {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User save(User obj) {
return userRepository.save(obj);
}
@Override
public Iterable<User> findAll() {
return userRepository.findAll();
}
@Override
public User findById(Long id) {
return userRepository.findById(id).get();
}
}
- CompanyServiceImpl
package com.henry.service;
import com.henry.model.company.Company;
import com.henry.repository.company.CompanyRepository;
import org.springframework.stereotype.Service;
@Service
public final class CompanyServiceImpl implements DefaultService<Company, Long> {
private final CompanyRepository companyRepository;
public CompanyServiceImpl(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
@Override
public Company save(Company obj) {
return companyRepository.save(obj);
}
@Override
public Iterable<Company> findAll() {
return companyRepository.findAll();
}
@Override
public Company findById(Long id) {
return companyRepository.findById(id).get();
}
}
- BrandServiceImpl
package com.henry.service;
import com.henry.model.brand.Brand;
import com.henry.repository.brand.BrandRepository;
import org.springframework.stereotype.Service;
@Service
public final class BrandServiceImpl implements DefaultService<Brand, Long> {
private final BrandRepository brandRepository;
public BrandServiceImpl(BrandRepository brandRepository) {
this.brandRepository = brandRepository;
}
@Override
public Brand save(Brand obj) {
return brandRepository.save(obj);
}
@Override
public Iterable<Brand> findAll() {
return brandRepository.findAll();
}
@Override
public Brand findById(Long id) {
return brandRepository.findById(id).get();
}
}
Controller
- UserController
package com.henry.controller;
import com.henry.model.user.User;
import com.henry.service.DefaultService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1")
public class UserController {
private final DefaultService<User,Long> defaultService;
public UserController(DefaultService<User, Long> defaultService) {
this.defaultService = defaultService;
}
@PostMapping("/users")
public User createEmployee(@RequestBody User user) {
return defaultService.save(user);
}
}
- CompanyController
package com.henry.controller;
import com.henry.model.company.Company;
import com.henry.service.DefaultService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v2")
public class CompanyController {
private final DefaultService<Company,Long> defaultService;
public CompanyController(DefaultService<Company, Long> defaultService) {
this.defaultService = defaultService;
}
@PostMapping("/companies")
public Company createEmployee(@RequestBody Company company) {
return defaultService.save(company);
}
}
- BrandController
package com.henry.controller;
import com.henry.model.brand.Brand;
import com.henry.service.DefaultService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v3")
public class BrandController {
private final DefaultService<Brand, Long> defaultService;
public BrandController(DefaultService<Brand, Long> defaultService) {
this.defaultService = defaultService;
}
@PostMapping("/brands")
public Brand createEmployee(@RequestBody Brand brand) {
return defaultService.save(brand);
}
}
Configuration
- DataSourceProperties
package com.henry.configuration;
import com.henry.record.BrandRecord;
import com.henry.record.CompanyRecord;
import com.henry.record.UserRecord;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("datasource")
public class DataSourceProperties {
private UserRecord user;
private CompanyRecord company;
private BrandRecord brand;
public UserRecord getUser() {
return user;
}
public void setUser(UserRecord user) {
this.user = user;
}
public CompanyRecord getCompany() {
return company;
}
public void setCompany(CompanyRecord company) {
this.company = company;
}
public BrandRecord getBrand() {
return brand;
}
public void setBrand(BrandRecord brand) {
this.brand = brand;
}
}
- UserConfig - primary bean
package com.henry.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(
basePackages = "com.henry.repository.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager"
)
public class UserConfig {
@Autowired
private DataSourceProperties dsProperties;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean userEntityManager()
throws NamingException {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(userDataSource());
em.setPackagesToScan("com.henry.model.user");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(userHibernateProperties());
return em;
}
@Bean
@Primary
public DataSource userDataSource() throws IllegalArgumentException, NamingException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dsProperties.getUser().driverClassName());
dataSource.setUrl(dsProperties.getUser().url());
dataSource.setUsername(dsProperties.getUser().username());
dataSource.setPassword(dsProperties.getUser().password());
return dataSource;
}
private Properties userHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect",dsProperties.getUser().dialect());
properties.put("hibernate.hbm2ddl.auto",dsProperties.getUser().ddlAuto());
return properties;
}
@Primary
@Bean
public PlatformTransactionManager userTransactionManager() throws NamingException {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(userEntityManager().getObject());
return transactionManager;
}
}
- CompanyConfig - secondary bean
package com.henry.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(
basePackages = "com.henry.repository.company",
entityManagerFactoryRef = "companyEntityManager",
transactionManagerRef = "companyTransactionManager"
)
public class CompanyConfig {
@Autowired
private DataSourceProperties dsProperties;
@Bean
public LocalContainerEntityManagerFactoryBean companyEntityManager()
throws NamingException {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(companyDataSource());
em.setPackagesToScan("com.henry.model.company");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(companyHibernateProperties());
return em;
}
@Bean
public DataSource companyDataSource() throws IllegalArgumentException, NamingException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dsProperties.getCompany().driverClassName());
dataSource.setUrl(dsProperties.getCompany().url());
dataSource.setUsername(dsProperties.getCompany().username());
dataSource.setPassword(dsProperties.getCompany().password());
return dataSource;
}
private Properties companyHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", dsProperties.getCompany().dialect());
properties.put("hibernate.hbm2ddl.auto", dsProperties.getCompany().ddlAuto());
return properties;
}
@Bean
public PlatformTransactionManager companyTransactionManager() throws NamingException {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(companyEntityManager().getObject());
return transactionManager;
}
}
- BrandConfig - secondary bean
package com.henry.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(
basePackages = "com.henry.repository.brand",
entityManagerFactoryRef = "brandEntityManager",
transactionManagerRef = "brandTransactionManager"
)
public class BrandConfig {
@Autowired
private DataSourceProperties dsProperties;
@Bean
public LocalContainerEntityManagerFactoryBean brandEntityManager()
throws NamingException {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(brandDataSource());
em.setPackagesToScan("com.henry.model.brand");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(brandHibernateProperties());
return em;
}
@Bean
public DataSource brandDataSource() throws IllegalArgumentException, NamingException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dsProperties.getBrand().driverClassName());
dataSource.setUrl(dsProperties.getBrand().url());
dataSource.setUsername(dsProperties.getBrand().username());
dataSource.setPassword(dsProperties.getBrand().password());
return dataSource;
}
private Properties brandHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", dsProperties.getBrand().dialect());
properties.put("hibernate.hbm2ddl.auto", dsProperties.getBrand().ddlAuto());
return properties;
}
@Bean
public PlatformTransactionManager brandTransactionManager() throws NamingException {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(brandEntityManager().getObject());
return transactionManager;
}
}
Downloading and installing MariaDB
docker run --name mariadbtest -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mypass -d mariadb/server:10.3
sudo docker exec -it mariadbtest /bin/bash
mysql -u root -p
create database test1;
create database test2;
create database test3;
Run & Test
Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.
POST
http://localhost:9000/api/v1/users
POST
http://localhost:9000/api/v2/companies
POST
http://localhost:9000/api/v3/brands
https://springframework.guru/how-to-configure-multiple-data-sources-in-a-spring-boot-application/
https://www.baeldung.com/java-sealed-classes-interfaces
https://www.baeldung.com/spring-data-jpa-multiple-databases
https://stackoverflow.com/questions/23234379/installing-mysql-in-docker-fails-with-error-message-cant-connect-to-local-mysq
https://hevodata.com/learn/docker-mysql/
No comments:
Post a Comment