web-dev-qa-db-fra.com

Comment ajouter une méthode personnalisée à Spring Data JPA

Je regarde dans Spring Data JPA. Prenons l'exemple ci-dessous où toutes les fonctionnalités crud et Finder fonctionnent par défaut et si je souhaite personnaliser un Finder, cette opération peut également être effectuée facilement dans l'interface elle-même.

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

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

Je voudrais savoir comment puis-je ajouter une méthode personnalisée complète avec son implémentation pour le compte ci-dessus AccountRepository? Depuis que c'est une interface, je ne peux pas implémenter la méthode ici.

126
Sharad Yadav

Vous devez créer une interface distincte pour vos méthodes personnalisées:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

et fournissez une classe d'implémentation pour cette interface:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Voir également:

224
axtavt

En plus de answer de axtavt, n'oubliez pas que vous pouvez injecter Entity Manager dans votre implémentation personnalisée si vous en avez besoin pour construire vos requêtes:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

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

L'utilisation est limitée, mais pour les méthodes personnalisées simples, vous pouvez utiliser les méthodes d'interface default comme:

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

MODIFIER: 

Dans ce printemps tutorial il est écrit:

Spring Data JPA vous permet également de définir d’autres méthodes de requête par déclarant simplement leur signature de méthode.

Donc, il est même possible de déclarer une méthode comme:

Customer findByHobby(Hobby personHobby);

et si l'objet Hobby est une propriété de Customer, Spring définira automatiquement la méthode pour vous.

10
Tomasz Mularczyk

J'utilise le code suivant pour accéder aux méthodes de recherche générées à partir de mon implémentation personnalisée. Obtenir l'implémentation via la fabrique de haricots empêche les problèmes de création de haricots circulaires.

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
Peter Rietzler

La réponse acceptée fonctionne, mais présente trois problèmes:

  • Il utilise une fonctionnalité Spring Data non documentée pour nommer l'implémentation personnalisée AccountRepositoryImpl. Le documentation indique clairement qu'il doit s'appeler AccountRepositoryCustomImpl, le nom de l'interface personnalisée plus Impl
  • Vous ne pouvez pas utiliser l'injection de constructeur, uniquement @Autowired, considérée comme une mauvaise pratique.
  • Vous avez une dépendance circulaire dans l'implémentation personnalisée (c'est pourquoi vous ne pouvez pas utiliser l'injection de constructeur). 

J'ai trouvé un moyen de le rendre parfait, sans toutefois utiliser une autre fonctionnalité non documentée de 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
    }
}
4
Danila Piatov

Compte tenu de votre extrait de code, veuillez noter que vous ne pouvez transmettre que des objets natifs à la méthode findBy ###. Supposons que vous souhaitiez charger une liste de comptes appartenant à certains clients. 

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

Assurez-vous que le nom de la table à interroger est identique à celui de la classe d'entité . Pour d'autres implémentations, jetez un coup d'œil à this

4
samba

Il y a une autre question à considérer ici. Certaines personnes s'attendent à ce que l'ajout d'une méthode personnalisée à votre référentiel les expose automatiquement en tant que services REST sous le lien '/ search'. Ce n'est malheureusement pas le cas. Le printemps ne supporte pas cela actuellement. 

Il s'agit d'une fonctionnalité "par conception", Spring Data Rest vérifie explicitement si method est une méthode personnalisée et ne l'expose pas sous forme de lien de recherche REST:

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

Ceci est une sélection d'Oliver Gierke:

C'est par conception. Les méthodes de référentiel personnalisées ne sont pas des méthodes de requête en tant que ils peuvent effectivement mettre en œuvre n'importe quel comportement. Ainsi, c'est actuellement impossible pour nous de décider de la méthode HTTP pour exposer la méthode sous. POST serait l'option la plus sûre, mais cela ne correspond pas à la méthodes de requête génériques (qui reçoivent GET).

Pour plus de détails, voir ce numéro: https://jira.spring.io/browse/DATAREST-206

3
Lukasz Magiera

Si vous voulez pouvoir effectuer des opérations plus sophistiquées, vous aurez peut-être besoin d'accéder aux internes de Spring Data. Dans ce cas, voici ce qui fonctionne (comme solution provisoire à 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);
        }
    }

}
3
NealeU

Note latérale:

Lors de la création d'implémentations personnalisées pour les référentiels de données Spring:

La partie la plus importante du nom de la classe qui correspond à l'interface de fragment est le Impl postfix.

0
Serhat Oz

Comme spécifié dans la fonctionnalité documentée , l’utilisation du préfixe Impl nous permet d’avoir une solution relativement propre:

  • Définissez dans l'interface @Repository, par exemple, MyEntityRepository, soit des méthodes Spring Data, soit des méthodes personnalisées.
  • Créez une classe MyEntityRepositoryImpl (le suffixe Impl est la magie) n'importe où (il n'est même pas nécessaire qu'elle soit dans le même package) que implémente les méthodes personnalisées only et annotate cette classe avec @Component ** (@Repository ne sera pas travail).
    • Cette classe peut même injecter MyEntityRepository via @Autowired pour une utilisation dans les méthodes personnalisées.


Exemple:

Classe d'entité:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Interface de référentiel:

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 d'implémentation de méthodes personnalisées:

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

}

Les petits inconvénients que j'ai identifiés sont les suivants:

  • Les méthodes personnalisées de la classe Impl sont marquées comme non utilisées par le compilateur, d'où la suggestion @SuppressWarnings("unused").
  • Vous avez une limite d'une classe Impl. (Considérant que dans l'implémentation régulière des interfaces de fragment les documents suggèrent vous pourriez en avoir beaucoup.)
0
acdcjunior

J'étend le 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;
    }
}

et ajoute cette classe à @EnableJpaRepositoryries repositoryBaseClass.

0
Devilluminati