«1. Обзор

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

Однако, если мы хотим добавить пользовательский метод, доступный во всех репозиториях, процесс немного усложняется. Итак, это то, что мы будем исследовать здесь с помощью Spring Data JPA.

Для получения дополнительной информации о настройке и использовании Spring Data JPA ознакомьтесь с нашими предыдущими статьями: Guide to Hibernate with Spring 4 и Introduction to Spring Data JPA.

2. Определение интерфейса базового репозитория

Во-первых, мы должны создать новый интерфейс, который объявляет наш пользовательский метод:

@NoRepositoryBean
public interface ExtendedRepository<T, ID extends Serializable> 
  extends JpaRepository<T, ID> {
 
    public List<T> findByAttributeContainsText(String attributeName, String text);
}

Наш интерфейс расширяет интерфейс JpaRepository, чтобы мы могли использовать все стандартные поведение.

Вы также заметите, что мы добавили аннотацию @NoRepositoryBean. Это необходимо, поскольку в противном случае поведение Spring по умолчанию заключается в создании реализации для всех подынтерфейсов репозитория.

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

3. Реализация базового класса

Далее мы предоставим нашу реализацию интерфейса ExtendedRepository:

public class ExtendedRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> implements ExtendedRepository<T, ID> {
    
    private EntityManager entityManager;

    public ExtendedRepositoryImpl(JpaEntityInformation<T, ?> 
      entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    // ...
}

Этот класс расширяет класс SimpleJpaRepository, который является классом по умолчанию, который Spring использует для реализации интерфейсы репозитория.

Для этого необходимо создать конструктор с параметрами JpaEntityInformation и EntityManager, который вызывает конструктор из родительского класса.

Нам также нужно свойство EntityManager для использования в нашем пользовательском методе.

Кроме того, мы должны реализовать пользовательский метод, унаследованный от интерфейса ExtendedRepository:

@Transactional
public List<T> findByAttributeContainsText(String attributeName, String text) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<T> cQuery = builder.createQuery(getDomainClass());
    Root<T> root = cQuery.from(getDomainClass());
    cQuery
      .select(root)
      .where(builder
        .like(root.<String>get(attributeName), "%" + text + "%"));
    TypedQuery<T> query = entityManager.createQuery(cQuery);
    return query.getResultList();
}

Здесь метод findByAttributeContainsText() ищет все объекты типа T, которые имеют определенный атрибут, содержащий заданное значение String как параметр.

4. Конфигурация JPA

Чтобы указать Spring использовать наш пользовательский класс вместо класса по умолчанию для создания реализаций репозитория, мы можем использовать атрибут репозиторияBaseClass:

@Configuration
@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao", 
  repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
    // additional JPA Configuration
}

5. Создание репозитория сущностей

Далее давайте посмотрим, как мы можем использовать наш новый интерфейс.

Сначала добавим простую сущность Student:

@Entity
public class Student {

    @Id
    private long id;
    private String name;
    
    // standard constructor, getters, setters
}

Затем мы можем создать DAO для сущности Student, которая расширяет интерфейс ExtendedRepository:

public interface ExtendedStudentRepository extends ExtendedRepository<Student, Long> {
}

Вот и все! Теперь наша реализация будет иметь собственный метод findByAttributeContainsText().

Точно так же любой интерфейс, который мы определяем, расширяя интерфейс ExtendedRepository, будет иметь тот же метод.

6. Тестирование репозитория

Давайте создадим тест JUnit, показывающий пользовательский метод в действии:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { StudentJPAH2Config.class })
public class ExtendedStudentRepositoryIntegrationTest {
 
    @Resource
    private ExtendedStudentRepository extendedStudentRepository;
   
    @Before
    public void setup() {
        Student student = new Student(1, "john");
        extendedStudentRepository.save(student);
        Student student2 = new Student(2, "johnson");
        extendedStudentRepository.save(student2);
        Student student3 = new Student(3, "tom");
        extendedStudentRepository.save(student3);
    }
    
    @Test
    public void givenStudents_whenFindByName_thenOk(){
        List<Student> students 
          = extendedStudentRepository.findByAttributeContainsText("name", "john");
 
        assertEquals("size incorrect", 2, students.size());        
    }
}

Тест сначала использует bean-компонент extendedStudentRepository для создания 3 записей Student. Затем вызывается метод findByAttributeContains() для поиска всех учащихся, чье имя содержит текст «john».

Класс ExtendedStudentRepository может использовать как стандартные методы, такие как save(), так и добавленный нами пользовательский метод.

7. Заключение

В этой быстрой статье мы показали, как мы можем добавить пользовательский метод во все репозитории в Spring Data JPA.

Полный исходный код примеров можно найти на GitHub.