Hinzufügen einer benutzerdefinierten Methode zu Spring Data JPA

160

Ich beschäftige mich mit Spring Data JPA. Betrachten Sie das folgende Beispiel, in dem standardmäßig alle Funktionen für Rohöl und Finder funktionieren. Wenn ich einen Finder anpassen möchte, kann dies auch problemlos in der Benutzeroberfläche selbst durchgeführt werden.

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

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

Ich möchte wissen, wie ich eine vollständige benutzerdefinierte Methode mit ihrer Implementierung für das oben genannte AccountRepository hinzufügen kann. Da es sich um eine Schnittstelle handelt, kann ich die Methode dort nicht implementieren.

Sharad Yadav
quelle

Antworten:

290

Sie müssen eine separate Schnittstelle für Ihre benutzerdefinierten Methoden erstellen:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

und stellen Sie eine Implementierungsklasse für diese Schnittstelle bereit:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Siehe auch:

axtavt
quelle
21
Kann diese benutzerdefinierte Implementierung das eigentliche Repository einfügen, damit die dort definierten Methoden verwendet werden können? Insbesondere möchte ich auf verschiedene find * -Funktionen verweisen, die in der Repository-Schnittstelle in einer übergeordneten Find-Implementierung definiert sind. Da diese find * () -Funktionen keine Implementierung haben, kann ich sie nicht in der benutzerdefinierten Schnittstelle oder der Impl-Klasse deklarieren.
JBCP
18
Ich habe diese Antwort befolgt. Leider versucht Spring Data jetzt, die Eigenschaft "customMethod" in meinem "Account" -Objekt zu finden, da automatisch eine Abfrage für alle im AccountRepository definierten Methoden generiert wird. Wie kann man das aufhalten?
Nick Foote
41
@ NickFoote Beachten Sie, dass der Name der Klasse, die Sie in Ihrem Repository implementieren, lauten sollte: AccountRepositoryImplnicht: AccountRepositoryCustomImplusw. - Dies ist eine sehr strenge Namenskonvention.
Xeon
5
@ wired00 Ich denke, es erstellt einen Zirkelverweis und ich kann nicht sehen, wie @JBCP ihn zum Laufen gebracht hat. Wenn ich versuche, etwas Ähnliches zu tun, habe ich eine Ausnahme: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
Ja, siehe meinen vorherigen Kommentar dazu, dass es nicht funktioniert, wenn Sie erweitern. QueryDslRepositorySupportSie müssen das Repository auch über eine Feld- oder Setter-Injektion anstelle einer Konstruktor-Injektion injizieren, da es sonst nicht in der Lage ist, die Bean zu erstellen. Es scheint zu funktionieren, aber die Lösung fühlt sich ein bisschen "schmutzig" an. Ich bin mir nicht sicher, ob es Pläne gibt, die Funktionsweise des Spring Data-Teams zu verbessern.
Robert Hunt
72

Vergessen Sie nicht, dass Sie neben der Antwort von axtavt auch Entity Manager in Ihre benutzerdefinierte Implementierung einfügen können, wenn Sie ihn zum Erstellen Ihrer Abfragen benötigen:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
Gelees
quelle
10
Vielen Dank, ich möchte jedoch wissen, wie Pageable und Page in der benutzerdefinierten Implementierung verwendet werden. Irgendwelche Eingaben?
Zauberstabmacher
17

Die akzeptierte Antwort funktioniert, hat aber drei Probleme:

  • Es verwendet eine undokumentierte Spring Data-Funktion, wenn die benutzerdefinierte Implementierung als benannt wird AccountRepositoryImpl. In der Dokumentation ist eindeutig angegeben, dass AccountRepositoryCustomImplder Name der benutzerdefinierten Schnittstelle plus aufgerufen werden mussImpl
  • Sie können nicht nur die Konstruktorinjektion verwenden @Autowired die als schlechte Praxis angesehen wird
  • Sie haben eine zirkuläre Abhängigkeit innerhalb der benutzerdefinierten Implementierung (daher können Sie die Konstruktorinjektion nicht verwenden).

Ich habe einen Weg gefunden, es perfekt zu machen, allerdings nicht ohne eine andere undokumentierte Spring Data-Funktion zu verwenden:

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
    }
}
Danila Piatov
quelle
Das hat funktioniert. Ich möchte betonen, wie wichtig der Name des Parameters im Konstruktor ist, der der Konvention in dieser Antwort folgen muss (muss sein accountRepositoryBasic). Ansonsten beschwerte sich Spring darüber, dass es 2 Bohnen zur Injektion in meinen *ImplKonstruktor gibt.
Ziege
Also, was ist die Verwendung von AccountRepository
Kalpesh Soni
@ KalpeshSoni die Methoden von beiden AccountRepositoryBasicund AccountRepositoryCustomwird über eine injizierteAccountRepository
geg
1
Können Sie bitte angeben, wie der Kontext erstellt werden soll? Ich kann nicht alles zusammenfügen. Danke dir.
Franta Kocourek
12

Dies ist in der Verwendung begrenzt, aber für einfache benutzerdefinierte Methoden können Sie Standardschnittstellenmethoden verwenden, wie:

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", "[email protected]", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
            new Customer("Adrian", "Mularczyk", "[email protected]", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
            new Customer("Kazimierz", "Dejna", "[email protected]", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
            new Customer("Celina", "Dykiel", "[email protected]", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
        };

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

BEARBEITEN:

In diesem Frühjahrstutorial steht geschrieben:

Mit Spring Data JPA können Sie auch andere Abfragemethoden definieren, indem Sie einfach deren Methodensignatur deklarieren.

Es ist also sogar möglich, eine Methode wie folgt zu deklarieren:

Customer findByHobby(Hobby personHobby);

Wenn das Objekt HobbyEigentum des Kunden ist, definiert Spring automatisch die Methode für Sie.

Tomasz Mularczyk
quelle
6

Ich verwende den folgenden Code, um auf generierte Suchmethoden aus meiner benutzerdefinierten Implementierung zuzugreifen. Durch die Implementierung durch die Bean Factory werden Probleme bei der Erstellung von Circular Bean vermieden.

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);
    }
}
Peter Rietzler
quelle
5

Wie in der dokumentierten Funktionalität angegeben , Implermöglicht uns das Suffix eine saubere Lösung:

  • Definieren Sie in der @RepositorySchnittstelle, sagen wirMyEntityRepository entweder Spring Data-Methoden oder benutzerdefinierte Methoden
  • Erstellen Sie überall eine Klasse MyEntityRepositoryImpl(das ImplSuffix ist die Magie) (muss sich nicht einmal im selben Paket befinden), die nur die benutzerdefinierten Methoden implementiert, und kommentieren Sie diese Klasse mit @Component** ( funktioniert @Repository nicht ).
    • Diese Klasse kann sogar MyEntityRepositoryüber @Autowiredzur Verwendung in den benutzerdefinierten Methoden injizieren .


Beispiel:

Entitätsklasse:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Repository-Schnittstelle:

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

}

Implementierungs-Bean für benutzerdefinierte Methoden:

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

}

Die kleinen Nachteile, die ich identifiziert habe, sind:

  • Die benutzerdefinierten Methoden in der ImplKlasse werden vom Compiler als nicht verwendet markiert, daher der @SuppressWarnings("unused")Vorschlag.
  • Sie haben ein Limit von einer ImplKlasse. (Während in der regulären Implementierung der Fragmentschnittstellen die Dokumente vorschlagen, dass Sie viele haben könnten.)
acdcjunior
quelle
Beim Testen gibt es eine kleine Einschränkung. Wenn Sie es brauchen, lassen Sie es mich wissen und ich werde die Antwort aktualisieren.
acdcjunior
Wie kann ich MyEntityRepositoryImpl richtig autowire?
Konstantin Zyubin
@KonstantinZyubin Sie autowire MyEntityRepository, nicht die *Impl.
acdcjunior
4

Wenn Sie komplexere Vorgänge ausführen möchten, benötigen Sie möglicherweise Zugriff auf die Interna von Spring Data. In diesem Fall funktioniert Folgendes (als meine Zwischenlösung für 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
quelle
4

Beachten Sie in Anbetracht Ihres Code-Snippets, dass Sie nur native Objekte an die findBy ### -Methode übergeben können. Nehmen wir an, Sie möchten eine Liste von Konten laden, die bestimmten Kunden gehören. Eine Lösung besteht darin, dies zu tun.

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

Stellen Sie sicher, dass der Name der abzufragenden Tabelle derselbe ist wie die Entitätsklasse. Für weitere Implementierungen schauen Sie sich dies bitte an

Samba
quelle
1
Das ist ein Tippfehler in der Abfrage, es sollte nameoffie l d sein, ich habe kein richtiges Recht, es zu beheben.
BrunoJCM
3

Es gibt noch ein weiteres Problem, das hier berücksichtigt werden muss. Einige Leute erwarten, dass das Hinzufügen einer benutzerdefinierten Methode zu Ihrem Repository sie automatisch als REST-Services unter dem Link '/ search' verfügbar macht. Dies ist leider nicht der Fall. Der Frühling unterstützt das derzeit nicht.

Dies ist eine 'by Design'-Funktion. Spring Data Rest prüft explizit, ob es sich bei der Methode um eine benutzerdefinierte Methode handelt, und macht sie nicht als REST-Suchlink verfügbar:

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

Dies ist eine Frage von Oliver Gierke:

Dies ist beabsichtigt. Benutzerdefinierte Repository-Methoden sind keine Abfragemethoden, da sie jedes Verhalten effektiv implementieren können. Daher ist es uns derzeit unmöglich, uns für die HTTP-Methode zu entscheiden, unter der die Methode verfügbar gemacht werden soll. POST wäre die sicherste Option, entspricht jedoch nicht den generischen Abfragemethoden (die GET erhalten).

Weitere Informationen finden Sie in diesem Problem: https://jira.spring.io/browse/DATAREST-206

Lukasz Magiera
quelle
Das ist bedauerlich, ich habe so viel Zeit damit verschwendet, herauszufinden, was ich falsch gemacht habe, und schließlich verstehe ich, dass es keine solche Funktion gibt. Warum sollten sie diese Funktionalität überhaupt implementieren? Weniger Bohnen haben? Alle Dao-Methoden an einem Ort haben? Ich hätte das auf andere Weise erreichen können. Weiß jemand, was das Ziel ist, "einzelnen Repositorys Verhalten hinzuzufügen"?
Skeeve
Sie können alle Repository-Methoden über REST verfügbar machen, indem Sie @RestResource(path = "myQueryMethod")der Methode einfach die Anmerkung hinzufügen . Das obige Zitat besagt lediglich, dass Spring nicht weiß, wie Sie es zuordnen möchten (dh GET vs POST usw.). Es liegt also an Ihnen, es über die Anmerkung anzugeben.
GreenGiant
1

Hinzufügen eines benutzerdefinierten Verhaltens zu allen Repositorys:

Um allen Repositorys benutzerdefiniertes Verhalten hinzuzufügen, fügen Sie zunächst eine Zwischenschnittstelle hinzu, um das freigegebene Verhalten zu deklarieren.

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

    void sharedCustomMethod( ID id );
}

Jetzt erweitern Ihre individuellen Repository-Schnittstellen diese Zwischenschnittstelle anstelle der Repository-Schnittstelle um die deklarierte Funktionalität.

Erstellen Sie als Nächstes eine Implementierung der Zwischenschnittstelle, die die für die Persistenztechnologie spezifische Repository-Basisklasse erweitert. Diese Klasse fungiert dann als benutzerdefinierte Basisklasse für die Repository-Proxys.

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 Teil I. Referenz Geben Sie hier die Bildbeschreibung ein

Ali Yeganeh
quelle
0

Ich erweitere das 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;
    }
}

und fügt diese Klasse zu @EnableJpaRepositoryries repositoryBaseClass hinzu.

Devilluminati
quelle