Como adicionar método personalizado ao Spring Data JPA

160

Eu estou olhando para o Spring Data JPA. Considere o exemplo abaixo, onde vou obter todas as funcionalidades de crud e finder funcionando por padrão e, se eu quiser personalizar um localizador, isso também poderá ser feito facilmente na própria interface.

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

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

Gostaria de saber como posso adicionar um método personalizado completo com sua implementação para o AccountRepository acima? Como é uma interface, não posso implementar o método lá.

Sharad Yadav
fonte

Respostas:

290

Você precisa criar uma interface separada para seus métodos personalizados:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

e forneça uma classe de implementação para essa interface:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Veja também:

axtavt
fonte
21
Essa implementação personalizada pode injetar o repositório real, para que ele possa usar os métodos definidos lá? Especificamente, eu gostaria de referenciar várias funções find * definidas na interface do Repositório em uma implementação de localização de nível superior. Como essas funções find * () não têm uma implementação, não posso declará-las na interface Personalizada ou na classe Impl.
JBCP # 23/14
18
Eu segui essa resposta, infelizmente agora o Spring Data está tentando encontrar a propriedade "customMethod" no meu objeto "Conta", pois está tentando gerar automaticamente uma consulta para todos os métodos definidos no AccountRepository. Alguma maneira de parar isso?
Nick Foote
41
@NickFoote observe que o nome da classe que você implementa seu repositório deve ser: AccountRepositoryImplnot :, AccountRepositoryCustomImpletc. - é uma convenção de nomes muito estrita.
Xeon
5
@ wired00 Acho que ele cria uma referência circular e não consigo ver como o @JBCP fez isso funcionar. Quando eu tentar fazer algo semelhante eu acabar com uma exceção: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 na
6
Sim, veja meu comentário anterior sobre o fato de não funcionar se você estiver estendendo. QueryDslRepositorySupportVocê também deve injetar o repositório via injeção de campo ou setter, em vez de injeção de construtor, caso contrário, ele não poderá criar o bean. Parece funcionar, mas a solução parece um pouco "suja", não tenho certeza se existem planos para melhorar como isso funciona da equipe do Spring Data.
Robert Hunt
72

Além da resposta do axtavt , não se esqueça de que você pode injetar o Entity Manager em sua implementação personalizada, se você precisar criar suas consultas:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
geléias
fonte
10
Obrigado, no entanto, quero saber como usar Pageable e Page na implementação personalizada. Alguma entrada?
Criador de varinhas
17

A resposta aceita funciona, mas tem três problemas:

  • Ele usa um recurso não documentado do Spring Data ao nomear a implementação personalizada como AccountRepositoryImpl. A documentação afirma claramente que deve ser chamada AccountRepositoryCustomImpl, o nome da interface personalizada maisImpl
  • Você não pode usar apenas injeção de construtor @Autowired, que é considerada uma má prática
  • Você tem uma dependência circular dentro da implementação customizada (é por isso que você não pode usar a injeção de construtor).

Eu encontrei uma maneira de torná-lo perfeito, embora não sem o uso de outro recurso não documentado do 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
    }
}
Danila Piatov
fonte
Isso funcionou. Quero enfatizar a importância do nome do parâmetro no construtor deve seguir a convenção nesta resposta (deve ser accountRepositoryBasic). Caso contrário, a primavera reclamou da existência de duas opções de feijão para injeção no meu *Implconstrutor.
cabra
então o que é o uso de AccountRepository
Kalpesh Soni
@KalpeshSoni os métodos de ambos AccountRepositoryBasice AccountRepositoryCustomestarão disponíveis por meio de uma injeçãoAccountRepository
geg
1
Você pode, por favor, fornecer a maneira como o contexto deve ser criado? Eu não sou capaz de juntar tudo. Obrigado.
franta kocourek
12

O uso é limitado, mas para métodos personalizados simples, você pode usar métodos de interface padrão , como:

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

EDITAR:

Na Primavera deste ano tutorial está escrito:

O Spring Data JPA também permite definir outros métodos de consulta simplesmente declarando sua assinatura de método.

Portanto, é possível apenas declarar um método como:

Customer findByHobby(Hobby personHobby);

e se o objeto Hobbyfor uma propriedade do Cliente, o Spring definirá automaticamente o método para você.

Tomasz Mularczyk
fonte
6

Estou usando o código a seguir para acessar os métodos de localização gerados da minha implementação personalizada. A implementação da fábrica de beans evita problemas de criação circular de beans.

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
fonte
5

Conforme especificado na funcionalidade documentada , o Implsufixo nos permite ter uma solução limpa:

  • Defina na @Repositoryinterface, digamosMyEntityRepository , métodos Spring Data ou métodos personalizados
  • Crie uma classe MyEntityRepositoryImpl(o Implsufixo é a mágica) em qualquer lugar (nem precisa estar no mesmo pacote) que implemente apenas os métodos personalizados e anote essa classe com @Component** ( @Repository não funcionará).
    • Essa classe pode até injetar MyEntityRepositoryvia @Autowiredpara uso nos métodos personalizados.


Exemplo:

Classe de entidade:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Interface do repositório:

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 de implementação de métodos customizados:

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

}

As pequenas desvantagens que identifiquei são:

  • Os métodos personalizados na Implclasse são marcados como não utilizados pelo compilador, portanto, o@SuppressWarnings("unused") sugestão.
  • Você tem um limite de uma Implclasse. (Enquanto na implementação regular das interfaces de fragmento, os documentos sugerem que você pode ter muitos.)
acdcjunior
fonte
Há uma pequena ressalva durante o teste. Se você precisar, entre em contato e atualizarei a resposta.
acdcjunior
como autowire MyEntityRepositoryImpl corretamente?
Konstantin Zyubin 07/04
@KonstantinZyubin Você autowire MyEntityRepository, não o *Impl.
acdcjunior 8/04
4

Se você deseja realizar operações mais sofisticadas, pode precisar de acesso aos componentes internos do Spring Data. Nesse caso, o seguinte funciona (como minha solução provisória para o 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
fonte
4

Considerando o seu snippet de código, observe que você só pode passar objetos nativos para o método findBy ###, digamos que deseja carregar uma lista de contas que pertencem a determinados clientes, uma solução é fazer isso,

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

Faça com que o nome da tabela a ser consultada seja o mesmo que a classe Entity. Para outras implementações, dê uma olhada neste

Samba
fonte
1
O é um erro de digitação na consulta, ele deve ser nameoffie l d, eu não tenho direito adequada para corrigi-lo.
BrunoJCM
3

Há outra questão a ser considerada aqui. Algumas pessoas esperam que a adição de método personalizado ao seu repositório os exponha automaticamente como serviços REST no link '/ search'. Infelizmente, este não é o caso. A primavera não suporta isso atualmente.

Esse é o recurso "por design", o restante dos dados da primavera verifica explicitamente se o método é um método personalizado e não o expõe como um link de pesquisa REST:

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

Este é um qoute de Oliver Gierke:

Isso ocorre por design. Os métodos de repositório personalizado não são métodos de consulta, pois podem implementar efetivamente qualquer comportamento. Portanto, atualmente é impossível para nós decidir sobre o método HTTP para expor o método abaixo. POST seria a opção mais segura, mas isso não está de acordo com os métodos de consulta genéricos (que recebem GET).

Para mais detalhes, consulte este problema: https://jira.spring.io/browse/DATAREST-206

Lukasz Magiera
fonte
Isso é lamentável, perdi tanto tempo tentando descobrir o que fiz de errado e, finalmente, entendo que não existe esse recurso. Por que eles implementariam essa funcionalidade? Ter menos feijão? Para ter todos os métodos dao em um só lugar? Eu poderia ter conseguido isso de outras maneiras. Alguém sabe qual é o objetivo de "adicionar comportamento a repositórios únicos"?
Skeeve
Você pode expor qualquer método de repositório via REST, simplesmente adicionando a @RestResource(path = "myQueryMethod")anotação ao método. A citação acima está apenas afirmando que o Spring não sabe como você deseja que ele seja mapeado (por exemplo, GET vs POST etc.), portanto, cabe a você especificá-lo através da anotação.
GreenGiant
1

Incluindo comportamento personalizado em todos os repositórios:

Para adicionar um comportamento personalizado a todos os repositórios, primeiro adicione uma interface intermediária para declarar o comportamento compartilhado.

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

    void sharedCustomMethod( ID id );
}

Agora, suas interfaces de repositório individuais estenderão essa interface intermediária em vez da interface de repositório para incluir a funcionalidade declarada.

Em seguida, crie uma implementação da interface intermediária que estenda a classe base do repositório específico da tecnologia de persistência. Essa classe atuará como uma classe base personalizada para os proxies do repositório.

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

Repositórios de Dados Spring Parte I. Referência insira a descrição da imagem aqui

Ali Yeganeh
fonte
0

Eu estendo o 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 adiciona essa classe a @EnableJpaRepositoryries repositoryBaseClass.

Devilluminati
fonte