Статьи

Чтение реплик и Spring Data. Часть 4. Настройка репозитория чтения

Ранее мы установили два EntityManager в одном приложении. Один для чтения и один для записи. Теперь пришло время создать наш репозиторий для чтения.

Хранилище только для чтения будет использовать вторичный EntityManager только для чтения.

Чтобы сделать его доступным только для чтения, важно не выполнять никаких действий сохранения и сохранения.

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();
 
}

Нашей следующей задачей было бы создать этот репозиторий с менеджером сущностей базы данных для чтения.
Это означает, что все репозитории должны быть созданы с использованием менеджера сущностей по умолчанию, за исключением репозиториев только для чтения.

Я бы сначала создал аннотацию. Эта аннотация объявит мой репозиторий только для чтения. Также я буду использовать эту аннотацию для операции сканирования, чтобы использовать соответствующий EntityManager.

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 {
}

Теперь я знаю, что весенняя загрузка устраняет необходимость в аннотациях и делает создание хранилища автоматизированным способом, однако наш случай является специфическим.

После внесения некоторых изменений наш репозиторий только для чтения будет выглядеть следующим образом

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();
 
}

И теперь пришло время работать с нашим сканированием репозитория. Все репозитории будут добавлены в основной EntityManager, за исключением тех, которые помечены аннотацией @ReadOnlyRepository.

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();
    }
 
}

Также мы добавим конфигурацию для репозиториев только для чтения.

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();
    }
 
}

Вторичный менеджер сущностей будет внедрен только в репозитории, которые имеют только аннотацию @ReadOnlyRepository.

И чтобы показать это, давайте внесем некоторые изменения в наш контроллер.

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);
    }
 
}

Когда вы добавляете сотрудников в систему, хранилище только для чтения будет продолжать извлекать старых сотрудников, в то время как основное хранилище будет извлекать всех из них, включая недавно сохраненные.

Опубликовано на Java Code Geeks с разрешения Эммануила Гкациоураса, партнера нашей программы JCG. См. Оригинальную статью здесь: Чтение реплик и Spring Data. Часть 4. Настройка чтения репозитория.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.