1. Overview

In this article, We will learn Spring JPA Multi Tenancy example or Spring JPA database routing Example. While developing SAAS (Software As A Service) based application we require to change the database at runtime or routing of the database.

Let try to understand what is a requirement of Multi-Tenancy application, Let’s assume that we are developing one application, and we want to sell the same application to 4 clients, In a simple case, we will create 4 web server (i.e tomcat), 4 databases and deploy the same application 4 times for each client. Now if any changes will come in the application then we must need to update at 4 places every time. We need to take care and maintain 4 environments, Now if business growing up and more clients purchase our application then create the same environments and time and cost will be increased.

To overcome the above problem, We can develop multi tenant application where we can select the database at runtime based on the user. For example, when a request comes form client1 then select client1’s database, the request comes from client2 then select client2’s database and so on. but for this, we need a specific identifier for each client’s request identifier also knows as the tenant. So we just need to maintain one web server (i.e. tomcat) and N databases only.

Let’s discuss some ways to discuss maintain tenant.

  1. We can maintain tenant in URL like,
    1. client1.example.com, client2.example.com, Here client1 and client2 are tenant using that we can identify that request comes from which client.
    2. We can pass tenant in as request parameter, like ?tenantId=client1 or ?tentantId=client2
    3. We can pass tenant in request header (More suitable for REST API).
    4. We can store tenant in session once user login and fetch from the session whenever requires.

Above we have discussed the architecture of the application, Now let discuss about it the technical implementation of database routing with Spring boot JPA. If do not have any idea about Spring JPA then here is an example to create web service in spring JPA with MySQL database.

In normal Spring JPA application we create DataSource but while applying to the route we require AbstractRoutingDataSource which will help us database routing at runtime. Let discuss with an example:

If you are using only Spring MVC and hibernate then here is an example for Hibernate Tenancy example.

2. Example : Spring JPA database routing Example

In this example, We have used Spring boot JPA with two database demo_database_1 and demo_database_2 , We will pass tenantId in request URL based on tenantId records will be fetched from the relative database:

 

Spring boot JPA Multi Tenancy example

Spring boot JPA Multi Tenancy example

2.1 pom.xml

  • spring-boot-starter-data-jpa for spring JPA.
  • mysql-connector-java for MySQL connection
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-boot-example</groupId>
    <artifactId>spring-boot-hibernate-example</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>Spring boot JPA Multi Tenancy example</description>
    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
    </parent>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <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>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2 application.properties

Here are some properties related to spring jpa. spring.jpa.hibernate.ddl-auto will not work for routing database, It only works for @Primary database.

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

# Naming strategy

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

2.3 SpringBootConfig.java

Here we have created a bean  DataSource of class CustomRoutingDataSource, TargetDataSources indicate Key and Value pair, where the key is tenantId and value is a data source.

package com.javadeveloperzone;

import com.javadeveloperzone.master.MasterService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;


/**
 * Created by Java Developer Zone on 19-07-2017.
 */

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = "com.javadeveloperzone")
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.javadeveloperzone")
public class SpringBootConfig {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SpringBootConfig.class, args);            // it wil start application
    }

    @Bean
    public DataSource dataSource(){
        CustomRoutingDataSource customDataSource=new CustomRoutingDataSource();
        customDataSource.setTargetDataSources(MasterService.getDataSourceHashMap());
        return customDataSource;
    }

}

2.4 CustomRoutingDataSource.java

When any database operation will be perform then control automatically comes here, This method will return tenantId as String based on that database will be selected to perform the operations. Here we write custom login to find tenantId, We are maintaining tenantId in URL so reading from URL as parameter using ServletRequestAttributes

package com.javadeveloperzone;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class CustomRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();     // get request object
        if(attr!=null) {
            String tenantId = attr.getRequest().getParameter("tenantId");       // find parameter from request
            return tenantId;
        }else{
            return "tenantId2";             // default data source
        }
    }
}

2.5 MasterService.java

Master service will return the Map of data source where key contains tenantId and value contain data source. The datasource object contains a database driver, database URL, username and password.

package com.javadeveloperzone.master;

import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * Created by JavaDeveloperZone on 22-01-2017.
 */

public class MasterService {
    public static Map<Object, Object> getDataSourceHashMap() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/demo_database");
        dataSource.setUsername("root");
        dataSource.setPassword("");

        DriverManagerDataSource dataSource1 = new DriverManagerDataSource();
        dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource1.setUrl("jdbc:mysql://localhost:3306/demo_database_2");
        dataSource1.setUsername("root");
        dataSource1.setPassword("");

        HashMap hashMap = new HashMap();
        hashMap.put("tenantId1", dataSource);
        hashMap.put("tenantId2", dataSource1);
        return hashMap;
    }
}

2.6 EmployeeDAO.java

It employee CrudRepository to preform a database operation.

package com.javadeveloperzone.dao;

import com.javadeveloperzone.model.Employee;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;


/**
 * Created by Java Developer Zone on 03-08-2017.
 */
@Repository
@Transactional
public interface EmployeeDAO extends CrudRepository<Employee,Integer> {

    List<Employee> findAll();                           // fetch all Employee

}

2.7 Employee.java

package com.javadeveloperzone.model;

import javax.persistence.*;

/**
 * Created by Java Developer Zone on 03-08-2017.
 */
@Entity
@Table(name = "employee")
public class Employee {

    @Id                                                     // primary key
    @GeneratedValue(strategy = GenerationType.IDENTITY)     // auto increment
    @Column(name = "employeeId")
    private int employeeId;

    @Column(name = "employeeName")
    private String employeeName;

    @Column(name = "employeeRole")
    private String employeeRole;

    public int getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }

    public String getEmployeeRole() {
        return employeeRole;
    }

    public void setEmployeeRole(String employeeRole) {
        this.employeeRole = employeeRole;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }
}

2.8 EmployeeController.java

package com.javadeveloperzone.controller;

import com.javadeveloperzone.dao.EmployeeDAO;
import com.javadeveloperzone.model.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by Java Developer Zone on 19-07-2017.
 */
@RestController
public class EmployeeController {

    @Autowired
    private EmployeeDAO employeeDAO;

    @RequestMapping(value = "emploeeList")
    public java.util.List<Employee> emploeeList() {
        return employeeDAO.findAll();
    }

}

2.9 Output:

  • http://localhost:8080/emploeeList?tenantId=tenantId1

Here data will fetch from demo_database_1

Spring boot JPA Multi Tenancy example - TenantID1

Spring boot JPA Multi Tenancy example – TenantID1

  • http://localhost:8080/emploeeList?tenantId=tenantId2

Here data will fetch from demo_database_2

Spring boot JPA Multi Tenancy example - TenantID2

Spring boot JPA Multi Tenancy example – TenantID2

3. Conclusion

Here we learned at a way to implements multiple tenant application using Spring boot JPA and Spring JPA ways to implements database routing in SAAS based application.

4. References

Was this post helpful?
Let us know, if you liked the post. Only in this way, we can improve us.
Yes
No

Leave a Reply

Your email address will not be published. Required fields are marked *