Как добавить пользовательский метод в Spring Data JPA

160

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

@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {

  @Query("<JPQ statement here>")
  List<Account> findByCustomer(Customer customer);
}

Я хотел бы знать, как я могу добавить полный пользовательский метод с его реализацией для вышеупомянутого AccountRepository? Поскольку это интерфейс, я не могу реализовать метод там.

Шарад Ядав
источник

Ответы:

290

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

public interface AccountRepository 
    extends JpaRepository<Account, Long>, AccountRepositoryCustom { ... }

public interface AccountRepositoryCustom {
    public void customMethod();
}

и предоставить класс реализации для этого интерфейса:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @Autowired
    @Lazy
    AccountRepository accountRepository;  /* Optional - if you need it */

    public void customMethod() { ... }
}

Смотрите также:

axtavt
источник
21
Может ли эта пользовательская реализация внедрить реальное хранилище, чтобы оно могло использовать методы, определенные там? В частности, я хотел бы сослаться на различные функции find *, определенные в интерфейсе Repository, в реализации поиска более высокого уровня. Поскольку эти функции find * () не имеют реализации, я не могу объявить их в пользовательском интерфейсе или классе Impl.
JBCP
18
Я следовал этому ответу, но, к сожалению, теперь Spring Data пытается найти свойство «customMethod» в моем объекте «Account», поскольку он пытается автоматически сгенерировать запрос для всех методов, определенных в AccountRepository. Есть ли способ остановить это?
Ник Фут
41
@NickFoote обратите внимание, что имя класса, в котором вы реализуете свой репозиторий, должно быть: AccountRepositoryImplnot: AccountRepositoryCustomImplи т. Д. - это очень строгое соглашение об именах.
Xeon
5
@ wired00 Я думаю, что это создает циклическую ссылку, и я не вижу, как @JBCP это работает. Когда я пытаюсь сделать что-то подобное, я получаю исключение:Error creating bean with name 'accountRepositoryImpl': Bean with name 'accountRepositoryImpl' has been injected into other beans [accountRepository] in its raw version as part of a circular reference, but has eventually been wrapped.
Роберт Хант
6
Да, смотрите мой предыдущий комментарий о том, что он не работает, если вы расширяете. QueryDslRepositorySupportВы также должны внедрить репозиторий с помощью внедрения поля или метода установки, а не с помощью конструктора, иначе он не сможет создать компонент. Кажется, что это работает, но решение кажется немного «грязным», я не уверен, есть ли какие-либо планы по улучшению того, как это работает от команды Spring Data.
Роберт Хант
72

В дополнение к axtavt в ответ , не забудьте , что вы можете вводить Entity менеджер в пользовательской реализации , если вам это нужно , чтобы построить свои запросы:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
jelies
источник
10
Спасибо, однако, я хочу знать, как использовать Pageable и Page в пользовательской реализации. Любые входы?
Жезл
17

Принятый ответ работает, но имеет три проблемы:

  • Он использует недокументированную функцию Spring Data при именовании пользовательской реализации как AccountRepositoryImpl. В документации четко указано, что он должен быть вызван AccountRepositoryCustomImpl, имя пользовательского интерфейса плюсImpl
  • Вы не можете использовать инъекцию конструктора, только @Autowiredесли это считается плохой практикой
  • У вас есть циклическая зависимость внутри пользовательской реализации (вот почему вы не можете использовать внедрение конструктора).

Я нашел способ сделать его идеальным, хотя не без использования другой недокументированной функции Spring Data:

public interface AccountRepository extends AccountRepositoryBasic,
                                           AccountRepositoryCustom 
{ 
}

public interface AccountRepositoryBasic extends JpaRepository<Account, Long>
{
    // standard Spring Data methods, like findByLogin
}

public interface AccountRepositoryCustom 
{
    public void customMethod();
}

public class AccountRepositoryCustomImpl implements AccountRepositoryCustom 
{
    private final AccountRepositoryBasic accountRepositoryBasic;

    // constructor-based injection
    public AccountRepositoryCustomImpl(
        AccountRepositoryBasic accountRepositoryBasic)
    {
        this.accountRepositoryBasic = accountRepositoryBasic;
    }

    public void customMethod() 
    {
        // we can call all basic Spring Data methods using
        // accountRepositoryBasic
    }
}
Данила Пятов
источник
Это сработало. Я хочу подчеркнуть важность того, что имя параметра в конструкторе должно следовать соглашению в этом ответе (должно быть accountRepositoryBasic). В противном случае весна жаловалась на то, что в моем *Implконструкторе есть 2 варианта бина для инъекций .
коза
так что толку от AccountRepository
Kalpesh Soni
@KalpeshSoni методы от обоих AccountRepositoryBasicи AccountRepositoryCustomбудут доступны через впрыскиваемогоAccountRepository
гэг
1
Можете ли вы предоставить способ создания контекста? Я не могу собрать все это вместе. Спасибо.
Франта Кокоурек
12

Это ограничено в использовании, но для простых пользовательских методов вы можете использовать методы интерфейса по умолчанию, такие как:

import demo.database.Customer;
import org.springframework.data.repository.CrudRepository;

public interface CustomerService extends CrudRepository<Customer, Long> {


    default void addSomeCustomers() {
        Customer[] customers = {
            new Customer("Józef", "Nowak", "nowakJ@o2.pl", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
            new Customer("Adrian", "Mularczyk", "adii333@wp.pl", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
            new Customer("Kazimierz", "Dejna", "sobieski22@weebly.com", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
            new Customer("Celina", "Dykiel", "celina.dykiel39@yahoo.org", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
        };

        for (Customer customer : customers) {
            save(customer);
        }
    }
}

РЕДАКТИРОВАТЬ:

В этом весеннем уроке написано:

Spring Data JPA также позволяет определять другие методы запросов, просто объявляя сигнатуру их методов.

Так что даже можно просто объявить метод как:

Customer findByHobby(Hobby personHobby);

и если object Hobbyявляется свойством Customer, Spring автоматически определит метод для вас.

Томаш Муларчик
источник
6

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

public class MyRepositoryImpl implements MyRepositoryExtensions, BeanFactoryAware {

    private BrandRepository myRepository;

    public MyBean findOne(int first, int second) {
        return myRepository.findOne(new Id(first, second));
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        myRepository = beanFactory.getBean(MyRepository.class);
    }
}
Питер Рицлер
источник
5

Как указано в документированной функциональности , Implсуффикс позволяет нам иметь чистое решение:

  • Определите в @Repositoryинтерфейсе, скажем MyEntityRepository, методы Spring Data или пользовательские методы.
  • Создайте класс MyEntityRepositoryImpl( Implсуффикс - это магия) где угодно (даже не обязательно находиться в одном пакете), который реализует пользовательские методы и комментируйте такой класс с помощью @Component** ( @Repository не будет работать).
    • Этот класс может даже вводить MyEntityRepositoryчерез@Autowired для использования в пользовательских методах.


Пример:

Класс сущности:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Интерфейс репозитория:

package myapp.domain.myentity;

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {

    // EXAMPLE SPRING DATA METHOD
    List<MyEntity> findByCommentEndsWith(String x);

    List<MyEntity> doSomeHql(Long id);

    List<MyEntity> useTheRepo(Long id);

}

Пользовательские методы реализации bean:

package myapp.infrastructure.myentity;

@Component // Must be @Component !!
public class MyEntityRepositoryImpl { // must have the repo name + Impl !!

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private MyEntityRepository myEntityRepository;

    @SuppressWarnings("unused")
    public List<MyEntity> doSomeHql(Long id) {
        String hql = "SELECT eFROM MyEntity e WHERE e.id = :id";
        TypedQuery<MyEntity> query = entityManager.createQuery(hql, MyEntity.class);
        query.setParameter("id", id);
        return query.getResultList();
    }

    @SuppressWarnings("unused")
    public List<MyEntity> useTheRepo(Long id) {
        List<MyEntity> es = doSomeHql(id);
        es.addAll(myEntityRepository.findByCommentEndsWith("DO"));
        es.add(myEntityRepository.findById(2L).get());
        return es;
    }

}

Небольшие недостатки, которые я выявил:

  • Пользовательские методы в Impl классе помечены как неиспользуемые компилятором, таким образом, @SuppressWarnings("unused")предложение.
  • У вас есть ограничение в один Implкласс. (Принимая во внимание, что в реализации обычных интерфейсов фрагментов документы предполагают, что у вас может быть много.)
acdcjunior
источник
Во время тестирования есть небольшая оговорка. Если вам это нужно, дайте мне знать, и я обновлю ответ.
acdcjunior
как правильно Autowire MyEntityRepositoryImpl?
Константин Зюбин
@KonstantinZyubin Вы autowire MyEntityRepository, а не *Impl.
acdcjunior
4

Если вы хотите иметь возможность выполнять более сложные операции, вам может потребоваться доступ к внутренним компонентам Spring Data, и в этом случае работает следующее (как мое временное решение для DATAJPA-422 ):

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    private JpaEntityInformation<Account, ?> entityInformation;

    @PostConstruct
    public void postConstruct() {
        this.entityInformation = JpaEntityInformationSupport.getMetadata(Account.class, entityManager);
    }

    @Override
    @Transactional
    public Account saveWithReferenceToOrganisation(Account entity, long referralId) {
        entity.setOrganisation(entityManager.getReference(Organisation.class, organisationId));
        return save(entity);
    }

    private Account save(Account entity) {
        // save in same way as SimpleJpaRepository
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }

}
NealeU
источник
4

Учитывая ваш фрагмент кода, обратите внимание, что вы можете передавать только собственные объекты в метод findBy ###, допустим, вы хотите загрузить список учетных записей, принадлежащих определенным клиентам, одно из решений состоит в том, чтобы сделать это,

 @Query("Select a from Account a where a."#nameoffield"=?1")
      List<Account> findByCustomer(String "#nameoffield");

Make sue - имя таблицы, к которой нужно обращаться, то же самое, что и класс Entity. Для дальнейших реализаций, пожалуйста, посмотрите на это

самба
источник
1
Это опечатка в запросе, это должно быть nameoffie l d, я не имею права исправлять это.
BrunoJCM
3

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

Это особенность «по замыслу», данные пружины явно проверяют, является ли метод пользовательским методом, и не отображают его как поисковую ссылку REST:

private boolean isQueryMethodCandidate(Method method) {    
  return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}

Это цитата из Оливера Гирке:

Это по замыслу. Пользовательские методы репозитория не являются методами запросов, поскольку они могут эффективно реализовывать любое поведение. Таким образом, в настоящее время мы не можем принять решение о методе HTTP, в котором будет представлен метод. POST будет самым безопасным вариантом, но это не соответствует общим методам запросов (которые получают GET).

Для получения дополнительной информации см. Эту проблему: https://jira.spring.io/browse/DATAREST-206

Лукаш Магиера
источник
К сожалению, я потратил столько времени, пытаясь выяснить, что я сделал неправильно, и, наконец, я понимаю, что такой функции нет. Зачем им вообще реализовывать эту функциональность? Чтобы было меньше бобов? Чтобы все методы дао в одном месте? Я мог бы достичь этого другими способами. Кто-нибудь знает, какова цель «добавления поведения в отдельные репозитории»?
Скив
Вы можете предоставить любые методы репозитория через REST, просто добавив @RestResource(path = "myQueryMethod")аннотацию к методу. Приведенная выше цитата просто говорит о том, что Spring не знает, как вы хотите, чтобы он отображался (то есть GET против POST и т. Д.), Поэтому вы можете указать это с помощью аннотации.
GreenGiant
1

Добавление пользовательского поведения во все репозитории:

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

public interface MyRepository <T, ID extends Serializable> extends JpaRepository<T, ID>
{

    void sharedCustomMethod( ID id );
}

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

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

public class MyRepositoryImpl <T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID>
{

    private EntityManager entityManager;

       // There are two constructors to choose from, either can be used.
    public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager)
    {
        super( domainClass, entityManager );

        // This is the recommended method for accessing inherited class dependencies.
        this.entityManager = entityManager;
    }


    public void sharedCustomMethod( ID id )
    {
        // implementation goes here
    }
}

Репозитории данных Spring, часть I. введите описание изображения здесь

Али еганех
источник
0

Я расширяю SimpleJpaRepository:

public class ExtendedRepositoryImpl<T extends EntityBean> extends SimpleJpaRepository<T, Long>
    implements ExtendedRepository<T> {

    private final JpaEntityInformation<T, ?> entityInformation;

    private final EntityManager em;

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

и добавляет этот класс в @EnableJpaRepositoryries repositoryBaseClass.

Devilluminati
источник