Jak dodać własną metodę do Spring Data JPA


160

Zaglądam do Spring Data JPA. Rozważ poniższy przykład, w którym otrzymam wszystkie funkcje Crud i Finder działające domyślnie, a jeśli chcę dostosować wyszukiwarkę, można to również łatwo zrobić w samym interfejsie.

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

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

Chciałbym wiedzieć, jak mogę dodać kompletną niestandardową metodę z jej implementacją dla powyższego AccountRepository? Ponieważ jest to interfejs, nie mogę tam zaimplementować metody.

Odpowiedzi:


290

Musisz utworzyć oddzielny interfejs dla swoich metod niestandardowych:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

i podaj klasę implementacji dla tego interfejsu:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Zobacz też:


21
Czy ta niestandardowa implementacja może wstrzyknąć rzeczywiste repozytorium, aby móc korzystać z metod tam zdefiniowanych? W szczególności chciałbym odwołać się do różnych funkcji find * zdefiniowanych w interfejsie repozytorium w implementacji wyszukiwania wyższego poziomu. Ponieważ te funkcje find * () nie mają implementacji, nie mogę ich zadeklarować w interfejsie niestandardowym ani w klasie Impl.
JBCP,

18
Postępowałem zgodnie z tą odpowiedzią, niestety teraz Spring Data próbuje znaleźć właściwość „customMethod” w moim obiekcie „Account”, ponieważ próbuje automatycznie wygenerować zapytanie dla wszystkich metod zdefiniowanych w AccountRepository. Jakiś sposób, żeby to zatrzymać?
Nick Foote,

41
@NickFoote zwróć uwagę, że nazwa klasy, którą zaimplementujesz w swoim repozytorium, powinna brzmieć: AccountRepositoryImplnie :, AccountRepositoryCustomImplitd. - to bardzo ścisła konwencja nazewnicza.
Xeon,

5
@ wired00 Myślę, że tworzy cykliczne odniesienie i nie widzę, jak @JBCP to działa. Kiedy próbuję zrobić coś podobnego, kończy się z wyjątkiem: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.
Robert Hunt

6
Tak, zobacz mój poprzedni komentarz, że to nie działa, jeśli rozszerzasz. QueryDslRepositorySupportMusisz również wstrzyknąć repozytorium przez wstrzyknięcie pola lub ustawiającego zamiast wstrzyknięcia konstruktora, w przeciwnym razie nie będzie w stanie utworzyć fasoli. Wydaje się, że to działa, ale rozwiązanie wydaje się nieco „brudne”, nie jestem pewien, czy są jakieś plany poprawy tego działania ze strony zespołu Spring Data.
Robert Hunt

72

Oprócz axtavt za odpowiedź , nie zapomnij można wstrzykiwać Entity Manager w implementacji niestandardowych jeśli jest to potrzebne do budowania zapytań:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}

10
Dziękuję jednak, chcę wiedzieć, jak używać Pageable i Page w niestandardowej implementacji. Jakieś wejścia?
Wand Maker,

17

Zaakceptowana odpowiedź działa, ale ma trzy problemy:

  • Używa nieudokumentowanej funkcji Spring Data podczas nazywania niestandardowej implementacji jako AccountRepositoryImpl. Dokumentacja wyraźnie stwierdza, że ma się nazywać AccountRepositoryCustomImpl, nazwa zwyczaj Interface PlusImpl
  • Nie można używać tylko iniekcji konstruktora @Autowired, które są uważane za złą praktykę
  • Masz cykliczną zależność wewnątrz implementacji niestandardowej (dlatego nie możesz użyć iniekcji konstruktora).

Znalazłem sposób, aby uczynić go idealnym, ale nie bez użycia innej nieudokumentowanej funkcji 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
    }
}

To zadziałało. Chcę podkreślić wagę nazwy parametru w konstruktorze musi być zgodna z konwencją w tej odpowiedzi (musi być accountRepositoryBasic). W przeciwnym razie wiosna narzekała, że ​​są 2 fasole do wstrzyknięcia do mojego *Implkonstruktora.
koza

więc jaki jest pożytek z AccountRepository
Kalpesh Soni

@KalpeshSoni metody z obu AccountRepositoryBasici AccountRepositoryCustombędą dostępne przez wstrzyknięcieAccountRepository
geg

1
Czy możesz podać sposób tworzenia kontekstu? Nie jestem w stanie tego wszystkiego poskładać. Dziękuję Ci.
franta kocourek

12

Jest to ograniczone w użyciu, ale w przypadku prostych metod niestandardowych można użyć domyślnych metod interfejsu, takich jak:

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

EDYTOWAĆ:

W tym wiosennym samouczku jest napisane:

Spring Data JPA umożliwia także definiowanie innych metod zapytań, po prostu deklarując ich sygnaturę.

Można więc po prostu zadeklarować taką metodę jak:

Customer findByHobby(Hobby personHobby);

a jeśli obiekt Hobbyjest własnością Klienta, Spring automatycznie zdefiniuje dla Ciebie metodę.


6

Używam następującego kodu, aby uzyskać dostęp do wygenerowanych metod wyszukiwania z mojej niestandardowej implementacji. Przeprowadzenie wdrożenia przez fabrykę fasoli zapobiega problemom z tworzeniem okrągłych ziaren.

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

Jak określono w udokumentowanej funkcjonalności , Implprzyrostek pozwala nam mieć czyste rozwiązanie:

  • Zdefiniuj w @Repositoryinterfejsie, powiedzmy MyEntityRepository, metody Spring Data lub metody niestandardowe
  • Stwórz klasę MyEntityRepositoryImpl( Implprzyrostek jest magią) w dowolnym miejscu (nawet nie musi znajdować się w tym samym pakiecie), która implementuje tylko metody niestandardowe i dodaj adnotację do takiej klasy za pomocą @Component** ( @Repository nie zadziała).
    • Ta klasa może nawet wstrzykiwać MyEntityRepositoryza pośrednictwem @Autowireddo użytku w metodach niestandardowych.


Przykład:

Klasa podmiotu:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Interfejs repozytorium:

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 implementacji metod niestandardowych:

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

}

Małe wady, które zidentyfikowałem, to:

  • Niestandardowe metody w Implklasie są oznaczane przez kompilator jako nieużywane, stąd @SuppressWarnings("unused")sugestia.
  • Masz limit jednej Implklasy. (Podczas gdy w standardowej implementacji interfejsów fragmentów dokumenty sugerują , że możesz mieć ich wiele).

Podczas testów istnieje małe zastrzeżenie. Jeśli tego potrzebujesz, daj mi znać, a zaktualizuję odpowiedź.
acdcjunior

jak prawidłowo Autowire MyEntityRepositoryImpl?
Konstantin Zyubin

@KonstantinZyubin Ty autowire MyEntityRepository, a nie *Impl.
acdcjunior

4

Jeśli chcesz mieć możliwość wykonywania bardziej wyrafinowanych operacji, możesz potrzebować dostępu do wewnętrznych elementów Spring Data, w którym to przypadku działa (jako moje tymczasowe rozwiązanie do 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);
        }
    }

}

4

Biorąc pod uwagę fragment kodu, pamiętaj, że możesz przekazać obiekty natywne tylko do metody findBy ###, powiedzmy, że chcesz załadować listę kont należących do określonych klientów, jednym z rozwiązań jest to zrobić,

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

Spraw, aby nazwa tabeli, o którą chcesz zapytać, jest taka sama jak klasa Entity. Aby zapoznać się z dalszymi wdrożeniami, spójrz na to


1
To jest literówka w zapytaniu, powinno to być nameoffie l d, nie mam prawa go poprawiać.
BrunoJCM

3

Jest jeszcze jedna kwestia do rozważenia. Niektórzy ludzie oczekują, że dodanie niestandardowej metody do repozytorium automatycznie ujawni je jako usługi REST pod linkiem „/ search”. Tak niestety nie jest. Spring obecnie tego nie obsługuje.

Jest to funkcja `` zgodnie z projektem '', reszta danych sprężynowych wyraźnie sprawdza, czy metoda jest metodą niestandardową i nie ujawnia jej jako linku wyszukiwania REST:

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

Oto wypowiedź Olivera Gierke:

Jest to zgodne z projektem. Niestandardowe metody repozytorium nie są metodami zapytań, ponieważ mogą skutecznie implementować dowolne zachowanie. W związku z tym obecnie nie możemy zdecydować o metodzie HTTP, aby ujawnić metodę pod. POST byłaby najbezpieczniejszą opcją, ale nie jest to zgodne z ogólnymi metodami zapytań (które otrzymują GET).

Więcej szczegółów można znaleźć w tym numerze: https://jira.spring.io/browse/DATAREST-206


To niefortunne, straciłem tyle czasu, próbując dowiedzieć się, co zrobiłem źle, i wreszcie rozumiem, że nie ma takiej funkcji. Dlaczego mieliby w ogóle wdrożyć tę funkcjonalność? Aby mieć mniej fasoli? Aby mieć wszystkie metody dao w jednym miejscu? Mogłem to osiągnąć innymi sposobami. Czy ktoś wie, jaki jest cel funkcji „dodawania zachowania do pojedynczych repozytoriów”?
Skeeve

Możesz udostępnić dowolne metody repozytorium za pośrednictwem REST, po prostu dodając @RestResource(path = "myQueryMethod")adnotację do metody. Powyższy cytat po prostu stwierdza, że ​​Spring nie wie, jak chcesz go zmapować (np. GET vs POST itp.), Więc to do Ciebie należy określenie tego za pomocą adnotacji.
GreenGiant

1

Dodanie niestandardowego zachowania do wszystkich repozytoriów:

Aby dodać niestandardowe zachowanie do wszystkich repozytoriów, należy najpierw dodać interfejs pośredni, aby zadeklarować zachowanie współdzielone.

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

    void sharedCustomMethod( ID id );
}

Teraz Twoje indywidualne interfejsy repozytorium rozszerzą ten interfejs pośredni zamiast interfejsu repozytorium, aby uwzględnić zadeklarowaną funkcjonalność.

Następnie utwórz implementację interfejsu pośredniego, który rozszerza klasę bazową repozytorium specyficzną dla technologii trwałości. Ta klasa będzie następnie działać jako niestandardowa klasa bazowa dla serwerów proxy repozytorium.

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 Data Repositories Część I. Odniesienie wprowadź opis obrazu tutaj


0

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

i dodaje tę klasę do @EnableJpaRepositoryries repositoryBaseClass.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.