Come aggiungere un metodo personalizzato a Spring Data JPA


160

Sto esaminando l'APP di Spring Data. Considera l'esempio di seguito in cui farò funzionare tutte le funzionalità crud e finder per impostazione predefinita e se voglio personalizzare un finder, ciò può essere fatto facilmente anche nell'interfaccia stessa.

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

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

Vorrei sapere come posso aggiungere un metodo personalizzato completo con la sua implementazione per il suddetto AccountRepository? Dal momento che è un'interfaccia non posso implementare il metodo lì.

Risposte:


290

Devi creare un'interfaccia separata per i tuoi metodi personalizzati:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

e fornire una classe di implementazione per tale interfaccia:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Guarda anche:


21
Questa implementazione personalizzata può iniettare il repository effettivo, in modo che possa utilizzare i metodi lì definiti? In particolare, vorrei fare riferimento a varie funzioni find * definite nell'interfaccia del repository in un'implementazione di ricerca di livello superiore. Poiché quelle funzioni find * () non hanno un'implementazione, non posso dichiararle nell'interfaccia personalizzata o nella classe Impl.
JBCP,

18
Ho seguito questa risposta, sfortunatamente ora Spring Data sta cercando di trovare la proprietà "customMethod" sul mio oggetto "Account" mentre sta cercando di generare automaticamente una query per tutti i metodi definiti in AccountRepository. Un modo per fermarlo?
Nick Foote,

41
@NickFoote nota che il nome della classe che implementi il ​​tuo repository dovrebbe essere: AccountRepositoryImplno :, AccountRepositoryCustomImplecc. - è una convenzione di denominazione molto rigida.
Xeon,

5
@ wired00 Penso che crei un riferimento circolare e non riesco a vedere come @JBCP abbia funzionato. Quando provo a fare qualcosa di simile finisco con un'eccezione: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
Sì, vedi il mio commento precedente sul fatto che non funziona se stai estendendo QueryDslRepositorySupportDevi anche iniettare il repository tramite l'iniezione di campo o setter piuttosto che l'iniezione del costruttore, altrimenti non sarà in grado di creare il bean. Sembra funzionare ma la soluzione sembra un po '"sporca", non sono sicuro che ci siano piani per migliorare il funzionamento del team di Spring Data.
Robert Hunt,

72

Oltre alla risposta di axtavt , non dimenticare che puoi inserire Entity Manager nella tua implementazione personalizzata se ne hai bisogno per creare le tue query:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

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

10
Grazie, tuttavia, voglio sapere come utilizzare Pageable e Page nell'implementazione personalizzata. Qualche input?
Wand Maker,

17

La risposta accettata funziona, ma presenta tre problemi:

  • Utilizza una funzione Spring Data non documentata quando si nomina l'implementazione personalizzata come AccountRepositoryImpl. La documentazione afferma chiaramente che deve essere chiamato AccountRepositoryCustomImpl, il nome dell'interfaccia personalizzata piùImpl
  • Non è possibile utilizzare solo l'iniezione del costruttore @Autowired, che sono considerate cattive pratiche
  • All'interno dell'implementazione personalizzata esiste una dipendenza circolare (ecco perché non è possibile utilizzare l'iniezione del costruttore).

Ho trovato un modo per renderlo perfetto, anche se non senza usare un'altra funzionalità Spring Data non documentata:

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

Questo ha funzionato. Voglio sottolineare l'importanza del nome del parametro nel costruttore che deve seguire la convenzione in questa risposta (deve essere accountRepositoryBasic). Altrimenti la primavera si è lamentata del fatto che ci fossero 2 scelte di fagioli da iniettare nel mio *Implcostruttore.
capra

quindi qual è l'uso di AccountRepository
Kalpesh Soni il

@KalpeshSoni i metodi da entrambi AccountRepositoryBasice AccountRepositoryCustomsaranno disponibili tramite un iniettatoAccountRepository
geg

1
Potete per favore fornire il modo in cui il contesto dovrebbe essere creato? Non sono in grado di mettere tutto insieme. Grazie.
franta kocourek,

12

L'utilizzo è limitato, ma per semplici metodi personalizzati è possibile utilizzare metodi di interfaccia predefiniti come:

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

MODIFICARE:

In questo tutorial di primavera è scritto:

Spring Data JPA consente inoltre di definire altri metodi di query dichiarando semplicemente la loro firma del metodo.

Quindi è anche possibile dichiarare un metodo come:

Customer findByHobby(Hobby personHobby);

e se l'oggetto Hobbyè di proprietà del Cliente, Spring definirà automaticamente il metodo per te.


6

Sto usando il seguente codice per accedere ai metodi di ricerca generati dalla mia implementazione personalizzata. Ottenere l'implementazione attraverso la factory bean evita problemi di creazione di bean circolare.

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

Come specificato nella funzionalità documentata , il Implsuffisso ci consente di avere una soluzione pulita:

  • Definire @Repositorynell'interfaccia, ad esempio MyEntityRepository, metodi Spring Data o metodi personalizzati
  • Crea una classe MyEntityRepositoryImpl(il Implsuffisso è la magia) ovunque (non è nemmeno necessario essere nello stesso pacchetto) che implementa solo i metodi personalizzati e annota tale classe con @Component** ( @Repository non funzionerà).
    • Questa classe può anche essere iniettata MyEntityRepositorytramite @Autowiredper l'uso nei metodi personalizzati.


Esempio:

Classe di entità:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Interfaccia del repository:

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 di implementazione dei metodi personalizzati:

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

}

I piccoli inconvenienti che ho identificato sono:

  • I metodi personalizzati in Impl classe sono contrassegnati come non utilizzati dal compilatore, quindi il @SuppressWarnings("unused")suggerimento.
  • Hai un limite di una Implclasse. (Mentre nella normale implementazione delle interfacce di frammenti i documenti suggeriscono che potresti averne molti.)

C'è un piccolo avvertimento durante i test. Se ne hai bisogno, fammi sapere e aggiornerò la risposta.
acdcjunior,

come correttamente Autowire MyEntityRepositoryImpl?
Konstantin Zyubin

@KonstantinZyubin Autowire MyEntityRepository, non il *Impl.
acdcjunior,

4

Se vuoi essere in grado di eseguire operazioni più sofisticate potresti aver bisogno di accedere agli interni di Spring Data, nel qual caso funziona come la mia soluzione intermedia a 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

Considerando il tuo frammento di codice, tieni presente che puoi solo passare oggetti nativi al metodo findBy ###, supponiamo che tu voglia caricare un elenco di account che appartiene a determinati clienti, una soluzione è fare questo,

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

Fai causa il nome della tabella da interrogare è la stessa classe Entità. Per ulteriori implementazioni, dai un'occhiata a questo


1
È un errore di battitura sulla query, dovrebbe essere nameoffie l d, non ho il diritto di ripararlo.
BrunoJCM,

3

C'è un altro problema da considerare qui. Alcune persone prevedono che l'aggiunta di un metodo personalizzato al repository li esporrà automaticamente come servizi REST nel collegamento "/ cerca". Purtroppo non è così. Spring non lo supporta attualmente.

Questa è la funzionalità "in base alla progettazione", il resto dei dati di primavera controlla esplicitamente se il metodo è un metodo personalizzato e non lo espone come collegamento di ricerca REST:

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

Questo è un qoute di Oliver Gierke:

Questo è di progettazione. I metodi di repository personalizzati non sono metodi di query in quanto possono implementare in modo efficace qualsiasi comportamento. Pertanto, al momento è impossibile per noi decidere il metodo HTTP per esporre il metodo sotto. Il POST sarebbe l'opzione più sicura ma non è in linea con i metodi di query generici (che ricevono GET).

Per maggiori dettagli vedi questo numero: https://jira.spring.io/browse/DATAREST-206


È un peccato, ho perso così tanto tempo a cercare di scoprire cosa ho fatto di sbagliato e, infine, capisco che non esiste tale caratteristica. Perché dovrebbero persino implementare quella funzionalità? Avere meno fagioli? Avere tutti i metodi dao in un unico posto? Avrei potuto raggiungerlo in altri modi. Qualcuno sa qual è l'obiettivo di "aggiungere comportamento ai singoli repository"?
Skeeve

È possibile esporre qualsiasi metodo di repository tramite REST semplicemente aggiungendo l' @RestResource(path = "myQueryMethod")annotazione al metodo. La citazione sopra sta solo affermando che Spring non sa come lo vuoi mappare (cioè GET vs POST ecc.) Quindi spetta a te specificarlo tramite l'annotazione.
GreenGiant

1

Aggiunta di comportamento personalizzato a tutti i repository:

Per aggiungere un comportamento personalizzato a tutti i repository, è innanzitutto necessario aggiungere un'interfaccia intermedia per dichiarare il comportamento condiviso.

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

    void sharedCustomMethod( ID id );
}

Ora le tue singole interfacce di repository estenderanno questa interfaccia intermedia anziché l'interfaccia del repository per includere la funzionalità dichiarata.

Quindi, creare un'implementazione dell'interfaccia intermedia che estende la classe base del repository specifica della tecnologia di persistenza. Questa classe fungerà quindi da classe di base personalizzata per i proxy repository.

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

Repository di dati di primavera Parte I. Riferimento inserisci qui la descrizione dell'immagine


0

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

e aggiunge questa classe a repositoryBaseClass @EnableJpaRepositoryries.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.