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.
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:
4.6 Implémentations personnalisées pour les référentiels de données Spring
Notez que le schéma de nommage a changé entre les versions. Voir https://stackoverflow.com/a/52624752/66686 pour plus de détails.
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);
...
}
}
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.
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);
}
}
La réponse acceptée fonctionne, mais présente trois problèmes:
AccountRepositoryImpl
. Le documentation indique clairement qu'il doit s'appeler AccountRepositoryCustomImpl
, le nom de l'interface personnalisée plus Impl
@Autowired
, considérée comme une mauvaise pratique.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
}
}
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
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
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);
}
}
}
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.
Comme spécifié dans la fonctionnalité documentée , l’utilisation du préfixe Impl
nous permet d’avoir une solution relativement propre:
@Repository
, par exemple, MyEntityRepository
, soit des méthodes Spring Data, soit des méthodes personnalisées.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). MyEntityRepository
via @Autowired
pour une utilisation dans les méthodes personnalisées.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:
Impl
sont marquées comme non utilisées par le compilateur, d'où la suggestion @SuppressWarnings("unused")
.Impl
. (Considérant que dans l'implémentation régulière des interfaces de fragment les documents suggèrent vous pourriez en avoir beaucoup.)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.