Thursday, August 18, 2022

Spring Data JPA, Multiple Data Source, MariaDB and YAML file configuration.


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
















Source Code


Here on GitHub.






References.

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

SAML 2.0 With Spring Boot - Security and Keycloak 18.0.1

Overview: For creating this post, I guided by two E.g. One and second , are examples helpful for creating this post. I set the references at...