Read Replicas and Spring Data Part 4: Configuring the Read Repository
Here are the next steps for configuring your read repo! Check it out.
Join the DZone community and get the full member experience.
Join For FreePreviously, we set up two EntityManagers
in the same application — one for the reads and one for the writes. Now, it's time to create our read repository. Let's get started!
In case you missed it: Read Replicas and Spring Data, Part 1: Configuring the Database; Part 2: Configuring the Base Project; and Part 3: Configuring Two EntityManagers.
The read-only repository will use the secondary read-only EntityManager
.
In order to make it a read-only repository, it is essential not to have any save and persist actions.
package com.gkatzioura.springdatareadreplica.repository;
import java.util.List;
import org.springframework.data.repository.Repository;
import com.gkatzioura.springdatareadreplica.config.ReadOnlyRepository;
import com.gkatzioura.springdatareadreplica.entity.Employee;
/**
* This is a read only repository
*/
public interface ReadEmployeeRepository extends Repository {
List findAll();
}
Our next task would be to create this repository with the read database entity manager. This means that all repositories shall be created using the default entity manager except from the read-only repositories.
I would create an annotation first. This annotation will declare my repository as "read-only." Also, I will use this annotation for the scanning operation so that the appropriate EntityManager
can be used.
package com.gkatzioura.springdatareadreplica.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ReadOnlyRepository {
}
Now, I know that Spring Boot removes the need for annotations and does repository creation in an automated way; however, our case is a peculiar one.
By making some adjustments, our read-only repository will look like this:
package com.gkatzioura.springdatareadreplica.repository;
import java.util.List;
import org.springframework.data.repository.Repository;
import com.gkatzioura.springdatareadreplica.config.ReadOnlyRepository;
import com.gkatzioura.springdatareadreplica.entity.Employee;
/**
* This is a read only repository
*/
@ReadOnlyRepository
public interface ReadEmployeeRepository extends Repository {
List findAll();
}
And now, it's time to work with our repository scanning. All the repositories will be injected with the main EntityManager,
except the ones annotated with the @ReadOnlyRepository
annotation.
package com.gkatzioura.springdatareadreplica.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@Configuration
@EnableJpaRepositories(
basePackages = "com.gkatzioura",
excludeFilters = @ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "entityManagerFactory"
)
public class PrimaryEntityManagerConfiguration {
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.url}")
private String url;
@Bean
@Primary
public DataSource dataSource() throws Exception {
return DataSourceBuilder.create()
.url(url)
.username(username)
.password(password)
.driverClassName("org.postgresql.Driver")
.build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("dataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.gkatzioura.springdatareadreplica")
.persistenceUnit("main")
.build();
}
}
Also, we will add the following configuration for the read-only repositories:
package com.gkatzioura.springdatareadreplica.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@Configuration
@EnableJpaRepositories(
basePackages = "com.gkatzioura",
includeFilters= @ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "readEntityManagerFactory"
)
public class ReadOnlyEntityManagerConfiguration {
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.readUrl}")
private String readUrl;
@Bean
public DataSource readDataSource() throws Exception {
return DataSourceBuilder.create()
.url(readUrl)
.username(username)
.password(password)
.driverClassName("org.postgresql.Driver")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean readEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("readDataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.gkatzioura.springdatareadreplica")
.persistenceUnit("read")
.build();
}
}
The secondary entity manager will be injected only to the repositories that only have the @ReadOnlyRepository
annotation.
And to show this, let's make some changes to our controller:
package com.gkatzioura.springdatareadreplica.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.gkatzioura.springdatareadreplica.entity.Employee;
import com.gkatzioura.springdatareadreplica.repository.EmployeeRepository;
import com.gkatzioura.springdatareadreplica.repository.ReadEmployeeRepository;
@RestController
public class EmployeeContoller {
private final EmployeeRepository employeeRepository;
private final ReadEmployeeRepository readEmployeeRepository;
public EmployeeContoller(EmployeeRepository employeeRepository,
ReadEmployeeRepository readEmployeeRepository) {
this.employeeRepository = employeeRepository;
this.readEmployeeRepository = readEmployeeRepository;
}
@GetMapping("/employee")
public List getEmployees() {
return employeeRepository.findAll();
}
@GetMapping("/employee/read")
public List getEmployeesRead() {
return readEmployeeRepository.findAll();
}
@PostMapping("/employee")
@ResponseStatus(HttpStatus.CREATED)
public void addEmployee(@RequestBody Employee employee) {
employeeRepository.save(employee);
}
}
As you add employees to the system, the read-only repository will keep fetching the old employees while the main repository will fetch all of them including the recently persisted.
Hope you enjoyed this demonstration!
Further Reading
[DZone Refcard] Core Spring Data
Published at DZone with permission of Emmanouil Gkatziouras, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments