├── .gitignore
├── 00_init.sql
├── README.md
├── docker-compose.yml
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── ehsaniara
│ │ └── multidatasource
│ │ ├── DemoApplication.java
│ │ ├── configurations
│ │ ├── DataSourceConfigRead.java
│ │ ├── DataSourceConfigWrite.java
│ │ ├── HikariConfigRead.java
│ │ ├── HikariConfigWrite.java
│ │ ├── HikariReadProperties.java
│ │ └── HikariWriteProperties.java
│ │ ├── controller
│ │ └── CustomerControllerImpl.java
│ │ ├── handler
│ │ ├── ExceptionHandlerControllerAdvice.java
│ │ ├── ExceptionResponse.java
│ │ └── ResourceNotFoundException.java
│ │ ├── model
│ │ └── Customer.java
│ │ ├── repository
│ │ ├── CustomerRepository.java
│ │ ├── CustomerRepositoryCombo.java
│ │ ├── readRepository
│ │ │ └── CustomerReadRepository.java
│ │ └── writeRepository
│ │ │ └── CustomerWriteRepository.java
│ │ └── service
│ │ ├── CustomerService.java
│ │ └── CustomerServiceImpl.java
└── resources
│ └── application.yml
└── test
└── java
└── com
└── ehsaniara
└── multidatasource
└── DemoApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | /target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 |
5 | ### STS ###
6 | .apt_generated
7 | .classpath
8 | .factorypath
9 | .project
10 | .settings
11 | .springBeans
12 | .sts4-cache
13 |
14 | ### IntelliJ IDEA ###
15 | .idea
16 | *.iws
17 | *.iml
18 | *.ipr
19 |
20 | ### NetBeans ###
21 | /nbproject/private/
22 | /nbbuild/
23 | /dist/
24 | /nbdist/
25 | /.nb-gradle/
26 | /build/
27 |
28 | mvnw
29 | mvnw.cmd
30 |
31 | .mvn/**
--------------------------------------------------------------------------------
/00_init.sql:
--------------------------------------------------------------------------------
1 | create user replicator with replication encrypted password 'postgres_user_for_db_read';
2 | select pg_create_physical_replication_slot('replication_slot');
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot 2 with Multiple DataSource
2 |
3 | There are times that even having the best DataBase (PostgresSql, Oracle, MySql,... ) Tuning can not be as help-full as Application Level separating Read and Writes
4 |
5 | Spring Boot 2.2.2 with Multiple DataSource
6 | ## Postgres Setup
7 | For This Demo you need 2.2.2 separate Postgres DataBase where one as Master and the other re one as a Replica.
8 |
9 |
10 | for simplicity just run:
11 | ```docker-compose up --force-recreate```
12 |
13 | the docker-compose.yml is already in the project which contains 2 PostgresSql in 2 different ports, with ```demo``` DataBase
14 |
15 | > you can always uninstall it as: ```docker-compose down``` if you needed to.
16 |
17 |
18 | Now if run this line you create customer in **postgres_primary**:
19 | ```
20 | curl -H "Content-Type: application/json" --request POST --data '{"name":"Jay"}' http://localhost:8080/customer
21 | ```
22 | OR
23 | ```
24 | curl -H "Content-Type: application/json" --request PUT --data '{"id":1 , "name":"Jay ehsaniara"}' http://localhost:8080/customer
25 | ```
26 |
27 | But if you run this line you getting data from **postgres_replica**:
28 | ```
29 | curl --request GET http://localhost:8080/customer/1
30 | ```
31 |
32 |
33 | ---
34 | ### Spring Boot Setup
35 | From https://start.spring.io/ select **web**, **data-jpa**, **lombok**, **postgresDriver**
36 | Or Select the following share link:
37 | Spring Initializr generates spring boot project with just what you need to start quickly!start.spring.io
38 |
39 | Once you Generate and download the zip file, you should have similar POM file as:
40 | ```xml
41 |
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-web
47 |
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-data-jpa
52 |
53 |
54 |
55 | org.springframework.boot
56 | spring-boot-configuration-processor
57 |
58 |
59 |
60 | org.projectlombok
61 | lombok
62 | true
63 |
64 |
65 |
66 | org.postgresql
67 | postgresql
68 | runtime
69 |
70 |
71 |
72 |
73 |
74 | ```
75 |
76 | for this demo I use HikariDataSource as a default connection pool library by Spring Boot 2.2.2
77 | we need to have 2 separate DataSource and EntityManager one for the Writes(Master/Primary) and one for Reads(Slave/Secondary).
78 | (application.yml)
79 | ```yaml
80 |
81 | spring:
82 | datasource-write:
83 | driver-class-name: org.postgresql.Driver
84 | jdbc-url: jdbc:postgresql://localhost:5432/demo
85 | username: postgres
86 | password: password
87 | platform: postgresql
88 | hikari:
89 | idle-timeout: 10000
90 | maximum-pool-size: 10
91 | minimum-idle: 5
92 | pool-name: WriteHikariPool
93 |
94 | datasource-read:
95 | driver-class-name: org.postgresql.Driver
96 | jdbc-url: jdbc:postgresql://localhost:5433/demo
97 | username: postgres
98 | password: password
99 | platform: postgresql
100 | hikari:
101 | idle-timeout: 10000
102 | maximum-pool-size: 10
103 | minimum-idle: 5
104 | pool-name: ReadHikariPool
105 | ```
106 |
107 | as you see I have 2 data-source as: datasource-write and datasource-read with their own credentials.
108 |
109 | DataSource Configurations for WriteDB:
110 | ```java
111 | @Configuration
112 | @ConfigurationProperties("spring.datasource-write")
113 | @EnableTransactionManagement
114 | @EnableJpaRepositories(
115 | entityManagerFactoryRef = "entityManagerFactoryWrite",
116 | transactionManagerRef = "transactionManagerWrite",
117 | basePackages = {"com.ehsaniara.multidatasource.repository.writeRepository"}
118 | )
119 | public class DataSourceConfigWrite extends HikariConfig {
120 |
121 | @Bean
122 | public HikariDataSource dataSourceWrite() {
123 | return new HikariDataSource(this);
124 | }
125 |
126 | @Bean
127 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryWrite(
128 | final HikariDataSource dataSourceWrite) {
129 |
130 | LocalContainerEntityManagerFactoryBean entityManagerFactory
131 | = new LocalContainerEntityManagerFactoryBean();
132 |
133 | entityManagerFactory.setDataSource(dataSourceWrite);
134 | entityManagerFactory.setDataSource(dataSourceWrite);
135 | entityManagerFactory.setPersistenceProviderClass(HibernatePersistenceProvider.class);
136 | entityManagerFactory.setPersistenceUnitName("write");
137 | entityManagerFactory.setPackagesToScan(MODEL_PACKAGE);
138 | entityManagerFactory.setJpaProperties(JPA_PROPERTIES);
139 | return entityManagerFactory;
140 | }
141 |
142 | @Bean
143 | public PlatformTransactionManager transactionManagerWrite(EntityManagerFactory entityManagerFactoryWrite) {
144 | return new JpaTransactionManager(entityManagerFactoryWrite);
145 | }
146 | }
147 | ```
148 |
149 | DataSource Configurations for ReadDB:
150 |
151 | ```java
152 | @Configuration
153 | @ConfigurationProperties("spring.datasource-read")
154 | @EnableTransactionManagement
155 | @EnableJpaRepositories(
156 | entityManagerFactoryRef = "entityManagerFactoryRead",
157 | transactionManagerRef = "transactionManagerRead",
158 | basePackages = {"com.ehsaniara.multidatasource.repository.readRepository"}
159 | )
160 | public class DataSourceConfigRead extends HikariConfig {
161 |
162 | @Bean
163 | public HikariDataSource dataSourceRead() {
164 | return new HikariDataSource(this);
165 | }
166 |
167 | @Bean
168 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryRead(
169 | final HikariDataSource dataSourceRead) {
170 |
171 | LocalContainerEntityManagerFactoryBean factoryBean
172 | = new LocalContainerEntityManagerFactoryBean();
173 | factoryBean.setDataSource(dataSourceRead);
174 | factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
175 | factoryBean.setPersistenceUnitName("read");
176 | factoryBean.setPackagesToScan(MODEL_PACKAGE);
177 | factoryBean.setJpaProperties(JPA_PROPERTIES);
178 |
179 | return factoryBean;
180 | }
181 |
182 | @Bean
183 | public PlatformTransactionManager transactionManagerRead(EntityManagerFactory entityManagerFactoryRead) {
184 | return new JpaTransactionManager(entityManagerFactoryRead);
185 | }
186 | }
187 | ```
188 |
189 | Read and Write repositories should be in a separated packages:
190 |
191 | + Write: ```com.ehsaniara.multidatasource.repository.writeRepository```
192 |
193 | + Read: ```com.ehsaniara.multidatasource.repository.readRepository```
194 |
195 | you also need to set:
196 | (write)
197 | ```java
198 | Properties properties = new Properties();
199 | properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
200 | properties.put("hibernate.hbm2ddl.auto", "update");
201 | properties.put("hibernate.ddl-auto", "update");
202 | properties.put("show-sql", "true");
203 | ```
204 |
205 | on read config
206 | ```java
207 | Properties properties = new Properties();
208 | properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
209 | properties.put("show-sql", "true");
210 | ```
211 | Note: tables are automatically replicated
212 |
213 | and the actual logic are in the service layer:
214 |
215 | ```java
216 | @Service
217 | public class CustomerServiceImpl implements CustomerService {
218 |
219 | private final CustomerReadRepository customerReadRepository;
220 | private final CustomerWriteRepository customerWriteRepository;
221 |
222 | public CustomerServiceImpl(CustomerReadRepository customerReadRepository, CustomerWriteRepository customerWriteRepository) {
223 | this.customerReadRepository = customerReadRepository;
224 | this.customerWriteRepository = customerWriteRepository;
225 | }
226 |
227 | public Optional getCustomer(Long id) {
228 | return customerReadRepository.findById(id);
229 | }
230 |
231 | public Customer createCustomer(Customer customer) {
232 |
233 | return customerWriteRepository.save(customer);
234 | }
235 |
236 | public Customer updateCustomer(Customer customer) {
237 |
238 | return customerWriteRepository.save(customer);
239 | }
240 | }
241 | ```
242 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | x-common:
2 | &x-common
3 | image: postgres:15
4 | user: postgres
5 | restart: always
6 | networks:
7 | - my-network
8 | healthcheck:
9 | test: 'pg_isready -U user --dbname=postgres'
10 | interval: 10s
11 | timeout: 5s
12 | retries: 5
13 |
14 | services:
15 |
16 | postgres-primary:
17 | <<: *x-common
18 | ports:
19 | - 5432:5432
20 | environment:
21 | POSTGRES_USER: postgres
22 | POSTGRES_DB: demo
23 | POSTGRES_PASSWORD: password
24 | POSTGRES_HOST_AUTH_METHOD: "scram-sha-256\nhost replication all 0.0.0.0/0 md5"
25 | POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
26 | command: |
27 | postgres
28 | -c wal_level=replica
29 | -c hot_standby=on
30 | -c max_wal_senders=10
31 | -c max_replication_slots=10
32 | -c hot_standby_feedback=on
33 | volumes:
34 | - ./00_init.sql:/docker-entrypoint-initdb.d/00_init.sql
35 |
36 | postgres-replica:
37 | <<: *x-common
38 | ports:
39 | - 5433:5432
40 | environment:
41 | PGUSER: replicator
42 | PGPASSWORD: postgres_user_for_db_read
43 | command: |
44 | bash -c "
45 | until pg_basebackup --pgdata=/var/lib/postgresql/data -R --slot=replication_slot --host=postgres-primary --port=5432
46 | do
47 | echo 'Waiting for primary to connect...'
48 | sleep 1s
49 | done
50 | echo 'Backup done, starting replica...'
51 | chmod 0700 /var/lib/postgresql/data
52 | postgres
53 | "
54 | depends_on:
55 | - postgres-primary
56 |
57 | networks:
58 | my-network:
59 | driver: bridge
60 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.2.RELEASE
9 |
10 |
11 | com.example
12 | demo
13 | 0.0.1-SNAPSHOT
14 | Spring Boot Multiple DataSource
15 | Demo project for Spring Boot
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-data-jpa
31 |
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-configuration-processor
36 |
37 |
38 |
39 | org.projectlombok
40 | lombok
41 | 1.18.34
42 | provided
43 |
44 |
45 |
46 | org.postgresql
47 | postgresql
48 | runtime
49 |
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-starter-test
54 | test
55 |
56 |
57 | org.junit.vintage
58 | junit-vintage-engine
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | org.springframework.boot
68 | spring-boot-maven-plugin
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/DemoApplication.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class DemoApplication {
8 |
9 | public static final String MODEL_PACKAGE = "com.ehsaniara.multidatasource.model";
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(DemoApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/DataSourceConfigRead.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import com.ehsaniara.multidatasource.DemoApplication;
4 | import com.zaxxer.hikari.HikariDataSource;
5 | import org.hibernate.jpa.HibernatePersistenceProvider;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10 | import org.springframework.orm.jpa.JpaTransactionManager;
11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12 | import org.springframework.transaction.PlatformTransactionManager;
13 | import org.springframework.transaction.annotation.EnableTransactionManagement;
14 |
15 | import javax.persistence.EntityManagerFactory;
16 |
17 |
18 | /**
19 | * @author Jay Ehsaniara, Dec 30 2019
20 | */
21 | @Configuration
22 | @ConfigurationProperties("spring.datasource-read")
23 | @EnableTransactionManagement
24 | @EnableJpaRepositories(
25 | entityManagerFactoryRef = "entityManagerFactoryRead",
26 | transactionManagerRef = "transactionManagerRead",
27 | basePackages = {"com.ehsaniara.multidatasource.repository.readRepository"}
28 | )
29 | public class DataSourceConfigRead extends HikariConfigRead {
30 |
31 | public DataSourceConfigRead(HikariReadProperties hikariReadProperties) {
32 | super(hikariReadProperties);
33 | }
34 |
35 | @Bean
36 | public HikariDataSource dataSourceRead() {
37 | return new HikariDataSource(this);
38 | }
39 |
40 | @Bean
41 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryRead(
42 | final HikariDataSource dataSourceRead) {
43 |
44 | LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
45 | factoryBean.setDataSource(dataSourceRead);
46 | factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
47 | factoryBean.setPersistenceUnitName("read");
48 | factoryBean.setPackagesToScan(DemoApplication.MODEL_PACKAGE);
49 | factoryBean.setJpaProperties(jpaReadProperties);
50 |
51 | return factoryBean;
52 | }
53 |
54 | @Bean
55 | public PlatformTransactionManager transactionManagerRead(EntityManagerFactory entityManagerFactoryRead) {
56 | return new JpaTransactionManager(entityManagerFactoryRead);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/DataSourceConfigWrite.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import com.ehsaniara.multidatasource.DemoApplication;
4 | import com.zaxxer.hikari.HikariDataSource;
5 | import org.hibernate.jpa.HibernatePersistenceProvider;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10 | import org.springframework.orm.jpa.JpaTransactionManager;
11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12 | import org.springframework.transaction.PlatformTransactionManager;
13 | import org.springframework.transaction.annotation.EnableTransactionManagement;
14 |
15 | import javax.persistence.EntityManagerFactory;
16 |
17 |
18 | /**
19 | * @author Jay Ehsaniara, Dec 30 2019
20 | */
21 | @Configuration
22 | @ConfigurationProperties("spring.datasource-write")
23 | @EnableTransactionManagement
24 | @EnableJpaRepositories(
25 | entityManagerFactoryRef = "entityManagerFactoryWrite",
26 | transactionManagerRef = "transactionManagerWrite",
27 | basePackages = {"com.ehsaniara.multidatasource.repository.writeRepository"}
28 | )
29 | public class DataSourceConfigWrite extends HikariConfigWrite {
30 |
31 | public DataSourceConfigWrite(HikariWriteProperties hikariWriteProperties) {
32 | super(hikariWriteProperties);
33 | }
34 |
35 | @Bean
36 | public HikariDataSource dataSourceWrite() {
37 | return new HikariDataSource(this);
38 | }
39 |
40 | @Bean
41 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryWrite(
42 | final HikariDataSource dataSourceWrite) {
43 |
44 | LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
45 | factoryBean.setDataSource(dataSourceWrite);
46 | factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
47 | factoryBean.setPersistenceUnitName("write");
48 | factoryBean.setPackagesToScan(DemoApplication.MODEL_PACKAGE);
49 | factoryBean.setJpaProperties(jpaWriteProperties);
50 | return factoryBean;
51 | }
52 |
53 | @Bean
54 | public PlatformTransactionManager transactionManagerWrite(EntityManagerFactory entityManagerFactoryWrite) {
55 | return new JpaTransactionManager(entityManagerFactoryWrite);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/HikariConfigRead.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import com.zaxxer.hikari.HikariConfig;
4 |
5 | import java.util.Properties;
6 |
7 | public class HikariConfigRead extends HikariConfig {
8 |
9 | protected final HikariReadProperties hikariReadProperties;
10 | protected final Properties jpaReadProperties;
11 |
12 | protected HikariConfigRead(HikariReadProperties hikariReadProperties) {
13 | this.hikariReadProperties = hikariReadProperties;
14 | setPoolName(this.hikariReadProperties.getPoolName());
15 | setMinimumIdle(this.hikariReadProperties.getMinimumIdle());
16 | setMaximumPoolSize(this.hikariReadProperties.getMaximumPoolSize());
17 | setIdleTimeout(this.hikariReadProperties.getIdleTimeout());
18 |
19 | Properties properties = new Properties();
20 | properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
21 | properties.put("show-sql", "true");
22 | this.jpaReadProperties = properties;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/HikariConfigWrite.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import com.zaxxer.hikari.HikariConfig;
4 |
5 | import java.util.Properties;
6 |
7 | public class HikariConfigWrite extends HikariConfig {
8 |
9 | protected final HikariWriteProperties hikariWriteProperties;
10 | protected final Properties jpaWriteProperties;
11 |
12 | protected HikariConfigWrite(HikariWriteProperties hikariWriteProperties) {
13 | this.hikariWriteProperties = hikariWriteProperties;
14 | setPoolName(this.hikariWriteProperties.getPoolName());
15 | setMinimumIdle(this.hikariWriteProperties.getMinimumIdle());
16 | setMaximumPoolSize(this.hikariWriteProperties.getMaximumPoolSize());
17 | setIdleTimeout(this.hikariWriteProperties.getIdleTimeout());
18 |
19 | Properties properties = new Properties();
20 | properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
21 | properties.put("hibernate.hbm2ddl.auto", "update");
22 | properties.put("hibernate.ddl-auto", "update");
23 | properties.put("show-sql", "true");
24 | this.jpaWriteProperties = properties;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/HikariReadProperties.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.context.annotation.PropertySource;
8 |
9 | @Setter
10 | @Getter
11 | @Configuration
12 | @PropertySource("classpath:application.yml")
13 | @ConfigurationProperties("spring.datasource-read.hikari")
14 | public class HikariReadProperties {
15 |
16 | private String poolName;
17 |
18 | private int minimumIdle;
19 |
20 | private int maximumPoolSize;
21 |
22 | private int idleTimeout;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/configurations/HikariWriteProperties.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.configurations;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.context.annotation.PropertySource;
8 |
9 | @Setter
10 | @Getter
11 | @Configuration
12 | @PropertySource("classpath:application.yml")
13 | @ConfigurationProperties("spring.datasource-write.hikari")
14 | public class HikariWriteProperties {
15 |
16 | private String poolName;
17 |
18 | private int minimumIdle;
19 |
20 | private int maximumPoolSize;
21 |
22 | private int idleTimeout;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/controller/CustomerControllerImpl.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.controller;
2 |
3 | import com.ehsaniara.multidatasource.handler.ResourceNotFoundException;
4 | import com.ehsaniara.multidatasource.model.Customer;
5 | import com.ehsaniara.multidatasource.service.CustomerService;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.PostMapping;
9 | import org.springframework.web.bind.annotation.PutMapping;
10 | import org.springframework.web.bind.annotation.RequestBody;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | /**
14 | * @author Jay Ehsaniara, Dec 30 2019
15 | */
16 | @RestController
17 | public class CustomerControllerImpl {
18 |
19 | private final CustomerService customerService;
20 |
21 | public CustomerControllerImpl(CustomerService customerService) {
22 | this.customerService = customerService;
23 | }
24 |
25 | @GetMapping("/customer/{id}")
26 | public Customer getCustomer(@PathVariable("id") Long id) {
27 |
28 | return customerService.getCustomer(id).orElseThrow(() -> new ResourceNotFoundException("Invalid Customer"));
29 | }
30 |
31 | @PostMapping("/customer")
32 | public Customer createCustomer(@RequestBody Customer customer) {
33 | return customerService.createCustomer(customer);
34 | }
35 |
36 | @PutMapping("/customer")
37 | public Customer updateCustomer(@RequestBody Customer customer) {
38 | return customerService.updateCustomer(customer);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/handler/ExceptionHandlerControllerAdvice.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.handler;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.hibernate.exception.SQLGrammarException;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.web.bind.annotation.ControllerAdvice;
7 | import org.springframework.web.bind.annotation.ExceptionHandler;
8 | import org.springframework.web.bind.annotation.ResponseBody;
9 | import org.springframework.web.bind.annotation.ResponseStatus;
10 |
11 | import javax.servlet.http.HttpServletRequest;
12 |
13 | /**
14 | * @author Jay Ehsaniara, Dec 30 2019
15 | */
16 |
17 | @Slf4j
18 | @ControllerAdvice
19 | public class ExceptionHandlerControllerAdvice {
20 |
21 | @ExceptionHandler(ResourceNotFoundException.class)
22 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
23 | @ResponseBody
24 | public ExceptionResponse handleResourceNotFound(final ResourceNotFoundException exception,
25 | final HttpServletRequest request) {
26 | log.error(exception.getMessage());
27 |
28 | ExceptionResponse error = new ExceptionResponse();
29 | error.setMessage(exception.getMessage());
30 | error.setUrl(request.getRequestURI());
31 | error.setStatus(HttpStatus.NOT_FOUND.value());
32 |
33 | return error;
34 | }
35 |
36 | @ExceptionHandler(IllegalStateException.class)
37 | @ResponseStatus(value = HttpStatus.BAD_REQUEST)
38 | @ResponseBody
39 | public ExceptionResponse handleIllegalStateException(final IllegalStateException exception,
40 | final HttpServletRequest request) {
41 | log.error(exception.getMessage());
42 |
43 | ExceptionResponse error = new ExceptionResponse();
44 | error.setMessage(exception.getMessage());
45 | error.setUrl(request.getRequestURI());
46 | error.setStatus(HttpStatus.BAD_REQUEST.value());
47 |
48 | return error;
49 | }
50 |
51 | @ExceptionHandler(IllegalArgumentException.class)
52 | @ResponseStatus(value = HttpStatus.BAD_REQUEST)
53 | @ResponseBody
54 | public ExceptionResponse handleIllegalArgumentException(final IllegalArgumentException exception,
55 | final HttpServletRequest request) {
56 | log.error(exception.getMessage());
57 |
58 | ExceptionResponse error = new ExceptionResponse();
59 | error.setMessage(exception.getMessage());
60 | error.setUrl(request.getRequestURI());
61 | error.setStatus(HttpStatus.BAD_REQUEST.value());
62 |
63 | return error;
64 | }
65 |
66 | @ExceptionHandler(SQLGrammarException.class)
67 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
68 | @ResponseBody
69 | public ExceptionResponse handleSQLGrammarException(final SQLGrammarException exception,
70 | final HttpServletRequest request) {
71 | log.error(exception.getMessage());
72 |
73 | ExceptionResponse error = new ExceptionResponse();
74 | error.setMessage("SQL Exception");
75 | error.setUrl(request.getRequestURI());
76 | error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
77 |
78 | return error;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/handler/ExceptionResponse.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.handler;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 | import com.fasterxml.jackson.annotation.JsonInclude;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | * @author Jay Ehsaniara, Dec 30 2019
10 | */
11 | @Setter
12 | @Getter
13 | @JsonIgnoreProperties(ignoreUnknown = true)
14 | @JsonInclude(JsonInclude.Include.NON_NULL)
15 | public class ExceptionResponse {
16 |
17 | private String message;
18 | private String error;
19 | private String url;
20 | private long status;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/handler/ResourceNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.handler;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | /**
7 | * @author Jay Ehsaniara, Dec 30 2019
8 | */
9 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
10 | public class ResourceNotFoundException extends RuntimeException {
11 | public ResourceNotFoundException(String message) {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/model/Customer.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.ToString;
6 |
7 | import javax.persistence.Entity;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.GenerationType;
10 | import javax.persistence.Id;
11 |
12 | /**
13 | * @author Jay Ehsaniara, Dec 30 2019
14 | */
15 | @ToString
16 | @Setter
17 | @Getter
18 | @Entity
19 | public class Customer {
20 |
21 | @Id
22 | @GeneratedValue(strategy = GenerationType.AUTO)
23 | private Long id;
24 |
25 | private String name;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/repository/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.repository;
2 |
3 | import com.ehsaniara.multidatasource.model.Customer;
4 | import com.ehsaniara.multidatasource.repository.readRepository.CustomerReadRepository;
5 | import com.ehsaniara.multidatasource.repository.writeRepository.CustomerWriteRepository;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.util.Optional;
9 |
10 | @Service
11 | public class CustomerRepository implements CustomerRepositoryCombo {
12 |
13 | private final CustomerReadRepository readRepository;
14 | private final CustomerWriteRepository writeRepository;
15 |
16 | public CustomerRepository(CustomerReadRepository customerReadRepository, CustomerWriteRepository customerWriteRepository) {
17 | this.readRepository = customerReadRepository;
18 | this.writeRepository = customerWriteRepository;
19 | }
20 |
21 | @Override
22 | public S save(S s) {
23 | return writeRepository.save(s);
24 | }
25 |
26 | @Override
27 | public Iterable saveAll(Iterable iterable) {
28 | return writeRepository.saveAll(iterable);
29 | }
30 |
31 | @Override
32 | public Optional findById(Long aLong) {
33 | return readRepository.findById(aLong);
34 | }
35 |
36 | @Override
37 | public boolean existsById(Long aLong) {
38 | return readRepository.existsById(aLong);
39 | }
40 |
41 | @Override
42 | public Iterable findAll() {
43 | return readRepository.findAll();
44 | }
45 |
46 | @Override
47 | public Iterable findAllById(Iterable iterable) {
48 | return readRepository.findAllById(iterable);
49 | }
50 |
51 | @Override
52 | public long count() {
53 | return readRepository.count();
54 | }
55 |
56 | @Override
57 | public void deleteById(Long aLong) {
58 | writeRepository.deleteById(aLong);
59 | }
60 |
61 | @Override
62 | public void delete(Customer customer) {
63 | writeRepository.delete(customer);
64 | }
65 |
66 | @Override
67 | public void deleteAll(Iterable extends Customer> iterable) {
68 | writeRepository.deleteAll(iterable);
69 | }
70 |
71 | @Override
72 | public void deleteAll() {
73 | writeRepository.deleteAll();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/repository/CustomerRepositoryCombo.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.repository;
2 |
3 | import com.ehsaniara.multidatasource.repository.readRepository.CustomerReadRepository;
4 | import com.ehsaniara.multidatasource.repository.writeRepository.CustomerWriteRepository;
5 |
6 | public interface CustomerRepositoryCombo extends CustomerReadRepository, CustomerWriteRepository {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/repository/readRepository/CustomerReadRepository.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.repository.readRepository;
2 |
3 | import com.ehsaniara.multidatasource.model.Customer;
4 | import org.springframework.data.repository.CrudRepository;
5 |
6 | /**
7 | * @author Jay Ehsaniara, Dec 30 2019
8 | */
9 | public interface CustomerReadRepository extends CrudRepository {
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/repository/writeRepository/CustomerWriteRepository.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.repository.writeRepository;
2 |
3 | import com.ehsaniara.multidatasource.model.Customer;
4 | import org.springframework.data.repository.CrudRepository;
5 |
6 | /**
7 | * @author Jay Ehsaniara, Dec 30 2019
8 | */
9 | public interface CustomerWriteRepository extends CrudRepository {
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/service/CustomerService.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.service;
2 |
3 | import com.ehsaniara.multidatasource.model.Customer;
4 |
5 | import java.util.Optional;
6 |
7 | /**
8 | * @author Jay Ehsaniara, Dec 30 2019
9 | */
10 | public interface CustomerService {
11 |
12 | Optional getCustomer(Long id);
13 |
14 | Customer createCustomer(Customer customer);
15 |
16 | Customer updateCustomer(Customer customer);
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/ehsaniara/multidatasource/service/CustomerServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource.service;
2 |
3 | import com.ehsaniara.multidatasource.model.Customer;
4 | import com.ehsaniara.multidatasource.repository.CustomerRepository;
5 | import org.springframework.stereotype.Service;
6 | import org.springframework.util.Assert;
7 |
8 | import java.util.Optional;
9 |
10 | /**
11 | * @author Jay Ehsaniara, Dec 30 2019
12 | */
13 | @Service
14 | public class CustomerServiceImpl implements CustomerService {
15 |
16 | private final CustomerRepository customerRepository;
17 |
18 | public CustomerServiceImpl(CustomerRepository customerRepository) {
19 | this.customerRepository = customerRepository;
20 | }
21 |
22 | public Optional getCustomer(Long id) {
23 | return customerRepository.findById(id);
24 | }
25 |
26 | public Customer createCustomer(Customer customer) {
27 |
28 | Assert.notNull(customer, "Invalid customer");
29 | Assert.isNull(customer.getId(), "customer id should be null");
30 | Assert.notNull(customer.getName(), "Invalid customer name");
31 |
32 | return customerRepository.save(customer);
33 | }
34 |
35 | public Customer updateCustomer(Customer customer) {
36 |
37 | Assert.notNull(customer, "Invalid customer");
38 | Assert.notNull(customer.getId(), "Invalid customer id");
39 |
40 | return customerRepository.save(customer);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource-write:
3 | driver-class-name: org.postgresql.Driver
4 | jdbc-url: jdbc:postgresql://localhost:5432/demo
5 | username: postgres
6 | password: password
7 | platform: postgresql
8 | hikari:
9 | idle-timeout: 10000
10 | maximum-pool-size: 10
11 | minimum-idle: 5
12 | pool-name: WriteHikariPool
13 |
14 | datasource-read:
15 | driver-class-name: org.postgresql.Driver
16 | jdbc-url: jdbc:postgresql://localhost:5433/demo
17 | username: postgres
18 | password: password
19 | platform: postgresql
20 | hikari:
21 | idle-timeout: 10000
22 | maximum-pool-size: 10
23 | minimum-idle: 5
24 | pool-name: ReadHikariPool
25 |
--------------------------------------------------------------------------------
/src/test/java/com/ehsaniara/multidatasource/DemoApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.ehsaniara.multidatasource;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class DemoApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------