Friday, July 3, 2020

Spring Boot Hibernate Example


In this post, we are going to see how to create Spring boot hibernate example.

We will use Spring boot 1.5.3 Release version, it comes with hibernate 5. We will create a Spring boot hibernate application which will have JSP as user interface. It will provide user interface from which you can add, update or delete customer database.We will use controller, services and DAO classes to achieve these functionalities.We will connect to MySQL database using SessionFactory class of hibernate.

Github Source Code

Download

Spring Boot Hibernate Example

Here are steps to create a Spring boot Hibernate example.


Project Structure


Tools used for creating below project:

  1. Spring Boot 1.5.3.RELEASE
  2. Spring 4.3.8.RELEASE
  3. Tomcat Embed 8
  4. Maven 3
  5. Java 8
  6. Eclipse
  7. Hibernate 5.3.5
  8. MySQL 5.7.18

Step 1:  Create a dynamic web project using maven in eclipse named “SpringBootHibernateExample”.
Step 2: Change “pom.xml” as below:

<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 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.arpit.java2blog</groupId>
 <artifactId>SpringBootHibernateExample</artifactId>
 
 <version>0.0.1-SNAPSHOT</version>
 <name>SpringBootHibernateExample Maven Webapp</name>
 <url>http://maven.apache.org</url>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.3.RELEASE</version>
 </parent>
 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <!-- JSTL for JSP -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
  </dependency>
 
  <!-- For JSP compilation -->
  <dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
   <scope>provided</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.threeten/threetenbp -->
  <dependency>
   <groupId>org.threeten</groupId>
   <artifactId>threetenbp</artifactId>
   <version>0.7.2</version>
  </dependency>
 </dependencies>
 <build>
  <finalName>SpringBootHibernateExample</finalName>
 </build>
</project>

The spring-boot-starter-parent provides you all maven defaults required for any spring project.
Since we are developing a web application, we also need to add spring-boot-starter-web dependency and also we need to include pring-boot-starter-data-jpa to run this application with hibernate.You need to also put mysql-connector-java for MySql JDBC driver.If you are using any other database, you need to use different database connector.
Let’s do hibernate configuration first.

Hibernate Configuration

Step 3: Create a file named “HibernateConfiguration.java” in package .org.arpit.java2blog

package org.arpit.java2blog; 
import java.util.Properties;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@EnableTransactionManagement
public class HibernateConfiguration {
 @Value("${db.driver}")
 private String DRIVER;
 
 @Value("${db.password}")
 private String PASSWORD;
 
 @Value("${db.url}")
 private String URL;
 
 @Value("${db.username}")
 private String USERNAME;
 
 @Value("${hibernate.dialect}")
 private String DIALECT;
 
 @Value("${hibernate.show_sql}")
 private String SHOW_SQL;
 
 @Value("${hibernate.hbm2ddl.auto}")
 private String HBM2DDL_AUTO;
 
 @Value("${entitymanager.packagesToScan}")
 private String PACKAGES_TO_SCAN;
 
 @Bean
 public DataSource dataSource() {
  DriverManagerDataSource dataSource = new DriverManagerDataSource();
  dataSource.setDriverClassName(DRIVER);
  dataSource.setUrl(URL);
  dataSource.setUsername(USERNAME);
  dataSource.setPassword(PASSWORD);
  return dataSource;
 }
 
 @Bean
 public LocalSessionFactoryBean sessionFactory() {
  LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
  sessionFactory.setDataSource(dataSource());
  sessionFactory.setPackagesToScan(PACKAGES_TO_SCAN);
  Properties hibernateProperties = new Properties();
  hibernateProperties.put("hibernate.dialect", DIALECT);
  hibernateProperties.put("hibernate.show_sql", SHOW_SQL);
  hibernateProperties.put("hibernate.hbm2ddl.auto", HBM2DDL_AUTO);
  sessionFactory.setHibernateProperties(hibernateProperties);
 
  return sessionFactory;
 }
 
 @Bean
 public HibernateTransactionManager transactionManager() {
  HibernateTransactionManager transactionManager = new HibernateTransactionManager();
  transactionManager.setSessionFactory(sessionFactory().getObject());
  return transactionManager;
 } 
}

Above class is annotated with @Configuration and @Bean annotation. These annotations are used to define bean in Spring.
@Configuration is analogous to <beans> tag in Spring XML configuration and @Bean is analogous to <bean> tag.
@Value annotation is used to inject variables from properties files. In this case, it will read from application.properties which we are going to create in next step.

Step 4: Create a file named “application.properties” in package /src/main/resources

spring.mvc.view.prefix: /WEB-INF/
spring.mvc.view.suffix: .jsp
 
logging.level=DEBUG
# Database
db.driver: com.mysql.jdbc.Driver
db.url: jdbc:mysql://localhost:3306/CustomerData
db.username: root
db.password: admin
 
# Hibernate
hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql: true
hibernate.hbm2ddl.auto: create
entitymanager.packagesToScan: org
 
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Model Class

Step 5: Create a file named “Customer.java” in package .org.arpit.java2blog.model

package org.arpit.java2blog.model;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
/*
 * This is our model class and it corresponds to Customer table in database
 */
@Entity
@Table(name="CUSTOMER")
public class Customer{
 
 @Id
 @Column(name="id")
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 int id;
 
 @Column(name="customerName")
 String customerName; 
 
 @Column(name="email")
 String email;
 
 public Customer() {
  super();
 }
 public Customer(String customerName,String email) {
  super();
  this.customerName=customerName;
  this.email=email;
 }
 public String getCustomerName() {
  return customerName;
 }
 public void setCustomerName(String customerName) {
  this.customerName = customerName;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 
}

@Entity is used for making a persistent pojo class.For this java class,you will have corresponding table in database. @Column is used to map annotated attribute to corresponding column in table.

Create Customer table:

Create customer table in database with following DDL.

CREATE TABLE `CUSTOMER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customerName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)

Controller Class :

Step 6: Create a file named “CustomerController.java” in package .org.arpit.java2blog.controller


package org.arpit.java2blog.controller;
 
import java.util.List;
 
import org.arpit.java2blog.model.Customer;
import org.arpit.java2blog.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
public class CustomerController {
 
 @Autowired
 CustomerService customerService;
 
 @RequestMapping(value = "/getAllCustomers", method = RequestMethod.GET, headers = "Accept=application/json")
 public String getAllCustomers(Model model) {
 
  List<Customer> listOfCustomers = customerService.getAllCustomers();
  model.addAttribute("customer", new Customer());
  model.addAttribute("listOfCustomers", listOfCustomers);
  return "customerDetails";
 }
 
 @RequestMapping(value = "/", method = RequestMethod.GET, headers = "Accept=application/json")
 public String goToHomePage() {
  return "redirect:/getAllCustomers";
 }
 
 @RequestMapping(value = "/getCustomer/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
 public Customer getCustomerById(@PathVariable int id) {
  return customerService.getCustomer(id);
 }
 
 @RequestMapping(value = "/addCustomer", method = RequestMethod.POST, headers = "Accept=application/json")
 public String addCustomer(@ModelAttribute("customer") Customer customer) { 
  if(customer.getId()==0)
  {
   customerService.addCustomer(customer);
  }
  else
  { 
   customerService.updateCustomer(customer);
  }
 
  return "redirect:/getAllCustomers";
 }
 
 @RequestMapping(value = "/updateCustomer/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
 public String updateCustomer(@PathVariable("id") int id,Model model) {
  model.addAttribute("customer", this.customerService.getCustomer(id));
  model.addAttribute("listOfCustomers", this.customerService.getAllCustomers());
  return "customerDetails";
 }
 
 @RequestMapping(value = "/deleteCustomer/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
 public String deleteCustomer(@PathVariable("id") int id) {
  customerService.deleteCustomer(id);
  return "redirect:/getAllCustomers";
 
 } 
}

Service Layer

Step 7: Create a file named “CustomerService.java” in package .org.arpit.java2blog.service

package org.arpit.java2blog.service;
 
import java.util.List;
 
import javax.transaction.Transactional;
 
import org.arpit.java2blog.dao.CustomerDao;
import org.arpit.java2blog.springboot.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
 
@Service("customerService")
public class CustomerService {
 
 @Autowired
 CustomerDao customerDao;
 
 @Transactional
 public List<Customer> getAllCustomers() {
  return customerDao.getAllCustomers();
 }
 
 @Transactional
 public Customer getCustomer(int id) {
  return customerDao.getCustomer(id);
 }
 
 @Transactional
 public void addCustomer(Customer customer) {
  customerDao.addCustomer(customer);
 }
 
 @Transactional
 public void updateCustomer(Customer customer) {
  customerDao.updateCustomer(customer);
 
 }
 
 @Transactional
 public void deleteCustomer(int id) {
  customerDao.deleteCustomer(id);
 }
}

DAO layer

Step 8: Create a interface named “CustomerDao.java” in package .org.arpit.java2blog.dao

package org.arpit.java2blog.dao;
 
import java.util.List;
 
import org.arpit.java2blog.springboot.Customer;
 
public interface CustomerDao {
 public List<Customer> getAllCustomers() ;
 
 public Customer getCustomer(int id) ;
 
 public Customer addCustomer(Customer customer);
 
 public void updateCustomer(Customer customer) ;
 
 public void deleteCustomer(int id) ;
}

Step 9: Create a file named “CustomerDaoImpl.java” in package .org.arpit.java2blog.dao

package org.arpit.java2blog.dao;
 
import java.util.List;
 
import org.arpit.java2blog.springboot.Customer;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
@Repository
public class CustomerDaoImpl implements CustomerDao{
 
 @Autowired
 private SessionFactory sessionFactory;
 
 public void setSessionFactory(SessionFactory sf) {
  this.sessionFactory = sf;
 }
 
 public List<Customer> getAllCustomers() {
  Session session = this.sessionFactory.getCurrentSession();
  List<Customer>  customerList = session.createQuery("from Customer").list();
  return customerList;
 }
 
 public Customer getCustomer(int id) {
  Session session = this.sessionFactory.getCurrentSession();
  Customer customer = (Customer) session.get(Customer.class, id);
  return customer;
 }
 
 public Customer addCustomer(Customer customer) {
  Session session = this.sessionFactory.getCurrentSession();
  session.save(customer);
  return customer;
 }
 
 public void updateCustomer(Customer customer) {
  Session session = this.sessionFactory.getCurrentSession();
  session.update(customer);
 }
 
 public void deleteCustomer(int id) {
  Session session = this.sessionFactory.getCurrentSession();
  Customer p = (Customer) session.load(Customer.class, new Integer(id));
  if (null != p) {
   session.delete(p);
  }
 } 
}

Views

Step 10: Create a file named “customerDetails.jsp” in package /WEB-INF/

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
<head>
<style>           
.blue-button{
 background: #25A6E1;
 filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#25A6E1',endColorstr='#188BC0',GradientType=0);
 padding:3px 5px;
 color:#fff;
 font-family:'Helvetica Neue',sans-serif;
 font-size:12px;
 border-radius:2px;
 -moz-border-radius:2px;
 -webkit-border-radius:4px;
 border:1px solid #1A87B9
}     
table {
  font-family: "Helvetica Neue", Helvetica, sans-serif;
   width: 50%;
}
th {
  background: SteelBlue;
  color: white;
}
 td,th{
                border: 1px solid gray;
                width: 25%;
                text-align: left;
                padding: 5px 10px;
            }
</style>
</head>
<body>
<form:form method="post" modelAttribute="customer" action="${pageContext.request.contextPath}/addCustomer">
<table>
  <tr>
   <th colspan="2">Add Customer</th>
  </tr>
  <tr>
 <form:hidden path="id" />
          <td><form:label path="customerName">Customer Name:</form:label></td>
          <td><form:input path="customerName" size="30" maxlength="30"></form:input></td>
        </tr>
  <tr>
       <td><form:label path="email">Email:</form:label></td>
          <td><form:input path="email" size="30" maxlength="30"></form:input></td>
  </tr>
  <tr>
   <td colspan="2"><input type="submit"
    class="blue-button" /></td>
  </tr>
 </table> 
</form:form>
</br>
<h3>Customer List</h3>
<c:if test="${!empty listOfCustomers}">
 <table class="tg">
 <tr>
  <th width="80">Id</th>
  <th width="120">Customer Name</th>
  <th width="120">Email</th>
  <th width="60">Edit</th>
  <th width="60">Delete</th>
 </tr>
 <c:forEach items="${listOfCustomers}" var="customer">
  <tr>
   <td>{customer.id}</td>
   <td>${customer.customerName}</td>
   <td>${customer.email}</td>
   <td><a href="<c:url value='/updateCustomer/${customer.id}' />" >Edit</a></td>
   <td><a href="<c:url value='/deleteCustomer/${customer.id}' />" >Delete</a></td>
  </tr>
 </c:forEach>
 </table>
</c:if>
</body>
</html>

Spring boot main file

Step 11: Create a file named “SpringBootHibernateApplication.java” in package .org.arpit.java2blog

package org.arpit.java2blog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class SpringBootHibernateApplication {
 
 public static void main(String[] args) 
 {
  SpringApplication.run(SpringBootHibernateApplication.class, args);   
 }
}

We have just added @SpringBootApplication and it does all the work.
Let’s understand more about this annotation.
@SpringBootApplication is an annotation that adds all of the following:

@Configuration makes the class as a source of bean definitions for the application context.
@EnableAutoConfiguration enables Spring boot to add beans present in classpath setting and various property setting.
Normally you would add @EnableWebMvc for a Spring MVC application, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath.
This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.

@ComponentScan tells Spring to look for other components, configurations, and services in the default package, allowing it to find the controllers.
If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.

Run the application

Step 12: It ‘s time to do maven build.

Right click on project -> Run as -> Maven build



Step 13: Provide goals as clean install spring-boot:run (given below) and click on run


Step 14: Once you are done with Maven build, let’s go to the browser and put following URL.

http://localhost:8080/getAllCustomers

You will see below screen.
Add follow details to Customer Name : as “John” and email as “John@gmail.com” and click on submit.
Now I am adding more customers using above method.
Let’s click on edit link corresponding to customer Id :3 whose name is David.
I am changing email address from “david@gmail.com” to “change@gmail.com”

When you click on submit, you will see below screen.
As you can see David’s email address got changed to “change@gmail.com”.
Let’s click on delete link corresponding to customer id :2 whose name is Martin and you will see below screen.

As you can see, Martin got deleted from the list.
That’s all about Spring Boot Hibernate example.




Reference:

Spring Boot Multiple DataSource Configuration Example


Introduction

Often, you will need to connect to more than one data source. Sometimes, this is for security reasons.
An example of this is the storage of credit card information. You may wish to store the data elements in multiple data sources. If one of the data sources is compromised the data retrieved is useless without the data from other data sources.
In this article, we will configure multiple data sources in Spring Boot and JPA.

Project Setup

Databases

We will use MySQL for our database server.
The credit card scenario described above, will use the following three databases:
  1. Member database(memberdb): Stores personal details of cardholders which include their full name and member id.
  2. Cardholder database(cardholderdb): Stores cardholder details which include the member id and credit card number.
  3. Card database(carddb): Stores the credit card information which includes the owner’s full name and the credit card expiration date.
Since we are spreading the credit card data across three databases, all three would need to be compromised for a security risk.
NOTE: This scenario is for an example of using multiple data sources with Spring Boot. This article is not a security recommendation.

Dependencies

To support MySQL, our classpath must include the MySQL database connector dependency.
Here is the list of Maven dependencies.
  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-jpa</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. <scope>runtime</scope>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.projectlombok</groupId>
  13. <artifactId>lombok</artifactId>
  14. <optional>true</optional>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-test</artifactId>
  19. <scope>test</scope>
  20. </dependency>
  21. <dependency>
  22. <groupId>commons-dbcp</groupId>
  23. <artifactId>commons-dbcp</artifactId>
  24. <version>${commons.dbcp.version}</version>
  25. </dependency>
  26. </dependencies>

Packaging

The project packaging structure is very important when dealing with multiple data sources.
The data models or entities belonging to a certain datastore must be placed in their unique packages.
This packaging strategy also applies to the JPA repositories.
Credit Card sample application packaging structure.
As you can see above, we have defined a unique package for each of the models and repositories.
We have also created Java configuration files for each of our data sources:
  • guru.springframework.multipledatasources.configuration.CardDataSourceConfiguration
  • guru.springframework.multipledatasources.configuration.CardHolderDataSourceConfiguration
  • guru.springframework.multipledatasources.configuration.MemberDataSourceConfiguration
Each data source configuration file will contain its data source bean definition including the entity manager and transaction manager bean definitions.

Database Connection Settings

Since we are configuring three data sources we need three sets of configurations in the application.propertiesfile.
Here is the code of the application.properties file.
  1. #Store card holder personal details
  2. app.datasource.member.url=jdbc:mysql://localhost:3306/memberdb?createDatabaseIfNotExist=true
  3. app.datasource.member.username=root
  4. app.datasource.member.password=P@ssw0rd#
  5. app.datasource.member.driverClassName=com.mysql.cj.jdbc.Driver
  6. #card number (cardholder id, cardnumber)
  7. app.datasource.cardholder.url=jdbc:mysql://localhost:3306/cardholderdb?createDatabaseIfNotExist=true
  8. app.datasource.cardholder.username=root
  9. app.datasource.cardholder.password=P@ssw0rd#
  10. app.datasource.cardholder.driverClassName=com.mysql.cj.jdbc.Driver
  11. #expiration date (card id, expiration month, expiration year)
  12. app.datasource.card.url=jdbc:mysql://localhost:3306/carddb?createDatabaseIfNotExist=true
  13. app.datasource.card.username=root
  14. app.datasource.card.password=P@ssw0rd#
  15. app.datasource.card.driverClassName=com.mysql.cj.jdbc.Driver
  16. spring.jpa.hibernate.ddl-auto=update
  17. spring.jpa.generate-ddl=true
  18. spring.jpa.show-sql=true
  19. spring.jpa.database=mysql

Data Source Configuration

It is important to note that during the configuration of multiple data sources, one data source instance must be marked as the primary data source.
Else the application will fail to start-up because Spring will detect more than one data source of the same type.

Steps

In this example, we will mark the member data source as our primary data source.
Here are the data source configuration steps.
  1. Data source bean definition
  2. Entities
  3. Entity Manager Factory bean definition
  4. Transaction Management
  5. Spring Data JPA Repository custom settings

Data Source Bean Definition

To create a data source bean we need to instantiate the org.springframework.boot.autoconfigure.jdbc.DataSourceProperties  class using the data source key specified in the application.properties file. We are going to use this DataSourceProperties object to get a data source builder object.
The data source builder object uses the database properties found in the application.properties file to create a data source object.
The following code shows the bean definitions of our data sources.

Primary Data Source

  1. @Bean
  2. @Primary
  3. @ConfigurationProperties("app.datasource.member")
  4. public DataSourceProperties memberDataSourceProperties() {
  5. return new DataSourceProperties();
  6. }
  7. @Bean
  8. @Primary
  9. @ConfigurationProperties("app.datasource.member.configuration")
  10. public DataSource memberDataSource() {
  11. return memberDataSourceProperties().initializeDataSourceBuilder()
  12. .type(HikariDataSource.class).build();
  13. }

Secondary Data Sources

  1. /*cardholder data source */
  2. @Bean
  3. @ConfigurationProperties("app.datasource.cardholder")
  4. public DataSourceProperties cardHolderDataSourceProperties() {
  5. return new DataSourceProperties();
  6. }
  7. @Bean
  8. @ConfigurationProperties("app.datasource.cardholder.configuration")
  9. public DataSource cardholderDataSource() {
  10. return cardHolderDataSourceProperties().initializeDataSourceBuilder()
  11. .type(BasicDataSource.class).build();
  12. }
  13. /*card data source*/
  14. @Bean
  15. @ConfigurationProperties("app.datasource.card")
  16. public DataSourceProperties cardDataSourceProperties() {
  17. return new DataSourceProperties();
  18. }
  19. @Bean
  20. @ConfigurationProperties("app.datasource.card.configuration")
  21. public DataSource cardDataSource() {
  22. return cardDataSourceProperties().initializeDataSourceBuilder()
  23. .type(BasicDataSource.class).build();
  24. }

Entities

Since we are going to store MemberCard, and Cardholder objects we must declare them as JPA entities using @Entity annotation. These entities will be mapped to relational database tables by JPA.
We must tell Spring which tables belong to a certain data source. There are two ways of achieving this. You can use the ‘schema‘ field of the @Table annotation as indicated in the code snippet below at line 2.
  1. @Entity
  2. @Table(name = "member", schema = "memberdb")
  3. @Data
  4. public class Member {
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.AUTO)
  7. private Long id;
  8. private String name;
  9. private String memberId;
  10. }
Or you may link the entities to their data source is via the org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder class method packages(). We can pass the packages or classes to be scanned for @Entity annotations in this method.
Spring will use this setting to map these entities to tables which will be created in the data source set through the datasource() method of this EMF builder class.
See code snippet in the next section.

Entity Manager Factory Bean Definition

Our application will be using Spring Data JPA for data access through its repository interfaces that abstract us from the EM(Entity Manager). We use the EMF bean to obtain instances of EMs which interact with the JPA entities.
Since, we have three data sources we need to create an EM for each data source.
This is done by providing the EMF builder class with reference to the data source and location of entities.
In our example, we will define this EMF using the org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean class like this.
  1. /*Primary Entity manager*/
  2. @Primary
  3. @Bean(name = "memberEntityManagerFactory")
  4. public LocalContainerEntityManagerFactoryBean memberEntityManagerFactory(EntityManagerFactoryBuilder builder) {
  5. return builder
  6. .dataSource(memberDataSource())
  7. .packages(Member.class)
  8. .build();
  9. }
  10. /*Secondary Entity Managers*/
  11. @Bean(name = "cardHolderEntityManagerFactory")
  12. public LocalContainerEntityManagerFactoryBean cardHolderEntityManagerFactory(
  13. EntityManagerFactoryBuilder builder) {
  14. return builder
  15. .dataSource(cardholderDataSource())
  16. .packages(CardHolder.class)
  17. .build();
  18. }
  19. @Bean(name = "cardEntityManagerFactory")
  20. public LocalContainerEntityManagerFactoryBean cardEntityManagerFactory(
  21. EntityManagerFactoryBuilder builder) {
  22. return builder
  23. .dataSource(cardDataSource())
  24. .packages(Card.class)
  25. .build();
  26. }

Transaction Management

The bean definition of a transaction manager requires a reference to the entity manager factory bean. We will to use the @Qualifier annotation to auto-wire the entity manager specific to the data source’ s transaction manager.
A transaction manager is needed for each data source.
The following is a snippet of code showing the member data source transaction manager bean definition.
  1. @Primary
  2. @Bean
  3. public PlatformTransactionManager memberTransactionManager(
  4. final @Qualifier("memberEntityManagerFactory") LocalContainerEntityManagerFactoryBean memberEntityManagerFactory) {
  5. return new JpaTransactionManager(memberEntityManagerFactory.getObject());
  6. }

JPA Repository Configuration

Since we are going to have multiple data sources we must provide the specific information for each data source repository using Spring’ s @EnableJpaRepositoriesannotation. In this annotation, we are going to set the reference to an entity manager, the repositories location and the reference to the transaction manager.
Below is the ‘member’ data source’s JPA repository settings.
  1. @Configuration
  2. @EnableTransactionManagement
  3. @EnableJpaRepositories(basePackages = "guru.springframework.multipledatasources.repository.member",
  4. entityManagerFactoryRef = "memberEntityManagerFactory",
  5. transactionManagerRef= "memberTransactionManager"
  6. )
  7. public class MemberDataSourceConfiguration { .... }
Line number 3
basePackages: We use this field to set the base package of our repositories. For instance, for the member data source, it must point to the package guru.springframework.multipledatasources.repository.member
Line number 4:
entityManagerFactoryRef: We use this field to reference the entity manager factory bean defined in the data source configuration file. It is important to take note of the fact that the entityManagerFactoryRef value must match the bean name (if specified via the name field of the @Bean annotation else will default to method name) of the entity manager factory defined in the configuration file.
Line number 5:
transactionManagerRef: This field references the transaction manager defined in the data source configuration file. Again it is important to ensure that the transactionManagerRef  value matches with the bean name of the transaction manager factory.

Complete Data Source Configuration File

Below is the complete data source configuration for our primary data source(member database). The complete card and cardholder configuration files are available on GitHub. They are similar to this one except that they are secondary data sources.
  1. @Configuration
  2. @EnableTransactionManagement
  3. @EnableJpaRepositories(basePackages = "guru.springframework.multipledatasources.repository.member",
  4. entityManagerFactoryRef = "memberEntityManagerFactory",
  5. transactionManagerRef= "memberTransactionManager"
  6. )
  7. public class MemberDataSourceConfiguration {
  8. @Bean
  9. @Primary
  10. @ConfigurationProperties("app.datasource.member")
  11. public DataSourceProperties memberDataSourceProperties() {
  12. return new DataSourceProperties();
  13. }
  14. @Bean
  15. @Primary
  16. @ConfigurationProperties("app.datasource.member.configuration")
  17. public DataSource memberDataSource() {
  18. return memberDataSourceProperties().initializeDataSourceBuilder()
  19. .type(HikariDataSource.class).build();
  20. }
  21. @Primary
  22. @Bean(name = "memberEntityManagerFactory")
  23. public LocalContainerEntityManagerFactoryBean memberEntityManagerFactory(EntityManagerFactoryBuilder builder) {
  24. return builder
  25. .dataSource(memberDataSource())
  26. .packages(Member.class)
  27. .build();
  28. }
  29. @Primary
  30. @Bean
  31. public PlatformTransactionManager memberTransactionManager(
  32. final @Qualifier("memberEntityManagerFactory") LocalContainerEntityManagerFactoryBean memberEntityManagerFactory) {
  33. return new JpaTransactionManager(memberEntityManagerFactory.getObject());
  34. }
  35. }
Important Points to note:
entity manager factory bean: Please make sure that you are referencing the correct data source when creating the entity manager factory bean otherwise you will get unexpected results.
transaction manager bean: To ensure that you have provided the correct entity manager factory reference for the transaction manager, you may use the @Qualifier annotation.
For example, the transaction manager of the ‘member’ data source will be using the entity manager factory bean with the name “memberEntityManagerFactory”.

Testing our application

After running the application, the schemas will be updated.
In this example, only one table for each datasource is created.
Credit card Sample Application Databases

Spring Boot Test Class

The test class in the code snippet below contains test methods for each data source.
In each method, we are creating an object and persisting it to the database using the Spring Data JPA repository.
To verify, we check if that data is present in the database.
  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class MultipledatasourcesApplicationTests {
  4. /*
  5. * We will be using mysql databases we configured in our properties file for our tests
  6. * Make sure your datasource connections are correct otherwise the test will fail
  7. * */
  8. @Autowired
  9. private MemberRepository memberRepository;
  10. @Autowired
  11. private CardHolderRepository cardHolderRepository;
  12. @Autowired
  13. private CardRepository cardRepository;
  14. private Member member;
  15. private Card card;
  16. private CardHolder cardHolder;
  17. @Before
  18. public void initializeDataObjects(){
  19. member = new Member();
  20. member.setMemberId("M001");
  21. member.setName("Maureen Mpofu");
  22. cardHolder = new CardHolder();
  23. cardHolder.setCardNumber("4111111111111111");
  24. cardHolder.setMemberId(member.getMemberId());
  25. card = new Card();
  26. card.setExpirationMonth(01);
  27. card.setExpirationYear(2020);
  28. card.setName(member.getName());
  29. }
  30. @Test
  31. public void shouldSaveMemberToMemberDB() {
  32. Member savedMember =memberRepository.save(member);
  33. Optional<Member> memberFromDb= memberRepository.findById(savedMember.getId());
  34. assertTrue(memberFromDb.isPresent());
  35. }
  36. @Test
  37. public void shouldSaveCardHolderToCardHolderDB() {
  38. CardHolder savedCardHolder =cardHolderRepository.save(cardHolder);
  39. Optional<CardHolder> cardHolderFromDb= cardHolderRepository.findById(savedCardHolder.getId());
  40. assertTrue(cardHolderFromDb.isPresent());
  41. }
  42. @Test
  43. public void shouldSaveCardToCardDB() {
  44. Card savedCard = cardRepository.save(card);
  45. Optional<Card> cardFromDb= cardRepository.findById(savedCard.getId());
  46. assertTrue(cardFromDb.isPresent());
  47. }
  48. }
Our test cases passed and the database tables recorded the data persisted via the application(indicated by the screenshots below).

Member Database

Member Database

Card Database

Card database

CardHolder Database

Cardholder database

Conclusion

When dealing with just one datasource and Spring Boot, data source configuration is simple. Spring Boot can provide a lot of auto configuration.
However, if you need to connect to multiple datasources with Spring Boot, additional configuration is needed.
You need to provide configuration data to Spring Boot, customized for each data source.
The source code of our sample application is available on GitHub.  Please update the datasource to your own needs.



Reference: