Read replicas and Spring Data Part 4: Configuring the read repository
Previously 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.
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.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | 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 will be used.
01 02 03 04 05 06 07 08 09 10 11 12 13 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | 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 from the ones annotated with the @ReadOnlyRepository annotation.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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 configuration for the read only repositories.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Read replicas and Spring Data Part 4: Configuring the read repository Opinions expressed by Java Code Geeks contributors are their own. |