Cómo agregar un método personalizado a Spring Data JPA

160

Estoy investigando Spring Data JPA. Considere el ejemplo a continuación, donde conseguiré que todas las funciones de búsqueda y búsqueda funcionen de manera predeterminada y si deseo personalizar un buscador, eso también se puede hacer fácilmente en la interfaz.

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

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

Me gustaría saber cómo puedo agregar un método personalizado completo con su implementación para el AccountRepository anterior. Como es una interfaz, no puedo implementar el método allí.

Sharad Yadav
fuente

Respuestas:

290

Debe crear una interfaz separada para sus métodos personalizados:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

y proporcione una clase de implementación para esa interfaz:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Ver también:

axtavt
fuente
21
¿Puede esta implementación personalizada inyectar el repositorio real, para que pueda usar los métodos definidos allí? Específicamente, me gustaría hacer referencia a varias funciones find * definidas en la interfaz del repositorio en una implementación de búsqueda de nivel superior. Como esas funciones find * () no tienen una implementación, no puedo declararlas en la interfaz personalizada o en la clase Impl.
JBCP
18
He seguido esta respuesta, desafortunadamente ahora Spring Data está tratando de encontrar la propiedad "customMethod" en mi objeto "Account" ya que está tratando de generar automáticamente una consulta para todos los métodos definidos en AccountRepository. ¿Alguna forma de detener esto?
Nick Foote
41
@NickFoote tenga en cuenta que el nombre de la clase que implementa su repositorio debe ser: AccountRepositoryImplnot:, AccountRepositoryCustomImpletc., es una convención de nomenclatura muy estricta.
Xeon
55
@ wired00 Creo que crea una referencia circular y no puedo ver cómo @JBCP lo hizo funcionar. Cuando trato de hacer algo similar termino con una excepción: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
66
Sí, vea mi comentario anterior sobre que no funciona si está extendiendo QueryDslRepositorySupportTambién debe inyectar el repositorio a través de la inyección de campo o setter en lugar de la inyección del constructor, de lo contrario no podrá crear el bean. Parece que funciona, pero la solución se siente un poco 'sucia', no estoy seguro de si hay planes para mejorar cómo funciona esto por parte del equipo de Spring Data.
Robert Hunt
72

Además de la respuesta de axtavt , no olvide que puede inyectar Entity Manager en su implementación personalizada si lo necesita para generar sus consultas:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
gelatinas
fuente
10
Gracias, sin embargo, quiero saber cómo usar Pageable y Page en la implementación personalizada. ¿Alguna entrada?
Wand Maker
17

La respuesta aceptada funciona, pero tiene tres problemas:

  • Utiliza una función Spring Data no documentada cuando nombra la implementación personalizada como AccountRepositoryImpl. La documentación indica claramente que debe llamarse AccountRepositoryCustomImpl, el nombre de la interfaz personalizada másImpl
  • No puede usar la inyección de constructor, solo @Autowired, que se consideran una mala práctica
  • Tiene una dependencia circular dentro de la implementación personalizada (es por eso que no puede usar la inyección del constructor).

Encontré una manera de hacerlo perfecto, aunque no sin usar otra función Spring Data indocumentada:

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
fuente
Esto funcionó. Quiero enfatizar la importancia del nombre del parámetro en el constructor debe seguir la convención en esta respuesta (debe ser accountRepositoryBasic). De lo contrario, Spring se quejó de que había 2 opciones de frijol para inyectar en mi *Implconstructor.
cabra
Entonces
Kalpesh Soni
@KalpeshSoni los métodos de ambos AccountRepositoryBasicy AccountRepositoryCustomestarán disponibles a través de un inyectadoAccountRepository
geg
1
¿Puede proporcionar la forma en que se debe crear el contexto? No puedo poner todo junto. Gracias.
franta kocourek
12

Su uso es limitado, pero para métodos personalizados simples puede usar métodos de interfaz predeterminados 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:

En este tutorial de primavera está escrito:

Spring Data JPA también le permite definir otros métodos de consulta simplemente declarando su firma de método.

Por lo tanto, incluso es posible declarar un método como:

Customer findByHobby(Hobby personHobby);

y si el objeto Hobbyes propiedad del Cliente, Spring definirá automáticamente el método para usted.

Tomasz Mularczyk
fuente
6

Estoy usando el siguiente código para acceder a los métodos de búsqueda generados desde mi implementación personalizada. Obtener la implementación a través de la fábrica de beans evita problemas de creación de beans circulares.

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

Como se especifica en la funcionalidad documentada , el Implsufijo nos permite tener una solución limpia:

  • Defina en la @Repositoryinterfaz, por ejemplo MyEntityRepository, métodos Spring Data o métodos personalizados.
  • Cree una clase MyEntityRepositoryImpl(el Implsufijo es la magia) en cualquier lugar (ni siquiera necesita estar en el mismo paquete) que implemente solo los métodos personalizados y anote dicha clase con @Component** ( @Repository no funcionará).
    • Esta clase puede incluso inyectar MyEntityRepositorya través @Autowiredde su uso en los métodos personalizados.


Ejemplo:

Clase de entidad:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Interfaz de repositorio:

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 implementación de métodos personalizados:

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

}

Los pequeños inconvenientes que identifiqué son:

  • Los métodos personalizados en la Implclase están marcados como no utilizados por el compilador, de ahí la @SuppressWarnings("unused")sugerencia.
  • Tienes un límite de una Implclase. (Mientras que en la implementación de interfaces de fragmentos regulares, los documentos sugieren que podría tener muchos).
acdcjunior
fuente
Hay una pequeña advertencia durante las pruebas. Si lo necesita, avíseme y actualizaré la respuesta.
acdcjunior
¿Cómo Autowire correctamente MyEntityRepositoryImpl?
Konstantin Zyubin
@ KonstantinZyubin Usted autoconecta MyEntityRepository, no el *Impl.
acdcjunior
4

Si desea poder realizar operaciones más sofisticadas, es posible que necesite acceso a las partes internas de Spring Data, en cuyo caso lo siguiente funciona (como mi solución provisional para 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
fuente
4

Teniendo en cuenta su fragmento de código, tenga en cuenta que solo puede pasar objetos nativos al método findBy ###, digamos que desea cargar una lista de cuentas que pertenecen a ciertos clientes, una solución es hacer esto,

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

Hacer demandar el nombre de la tabla a consultar es la misma clase de entidad. Para otras implementaciones, échale un vistazo a esto

Samba
fuente
1
El es un error tipográfico en la consulta, debe ser nameoffie l d, no tengo el derecho adecuado para solucionarlo.
BrunoJCM
3

Hay otro problema a considerar aquí. Algunas personas esperan que agregar un método personalizado a su repositorio los expondrá automáticamente como servicios REST en el enlace '/ buscar'. Desafortunadamente, este no es el caso. Spring no es compatible con eso actualmente.

Esta es la característica 'por diseño', el resto de datos de primavera verifica explícitamente si el método es un método personalizado y no lo expone como un enlace de búsqueda REST:

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

Esta es una qoute de Oliver Gierke:

Esto es por diseño. Los métodos de repositorio personalizados no son métodos de consulta, ya que pueden implementar efectivamente cualquier comportamiento. Por lo tanto, actualmente es imposible para nosotros decidir sobre el método HTTP para exponer el método bajo. POST sería la opción más segura, pero eso no está en línea con los métodos de consulta genéricos (que reciben GET).

Para obtener más detalles, consulte este problema: https://jira.spring.io/browse/DATAREST-206

Lukasz Magiera
fuente
Eso es desafortunado, he perdido mucho tiempo tratando de averiguar qué hice mal y, finalmente, entiendo que no existe tal característica. ¿Por qué implementarían incluso esa funcionalidad? ¿Tener menos frijoles? ¿Tener todos los métodos dao en un solo lugar? Podría haberlo logrado de otras maneras. ¿Alguien sabe cuál es el objetivo de "agregar comportamiento a los repositorios individuales"?
Skeeve
Puede exponer cualquier método de repositorio a través de REST simplemente agregando la @RestResource(path = "myQueryMethod")anotación al método. La cita anterior solo indica que Spring no sabe cómo desea que se asigne (es decir, GET vs POST, etc.), por lo que depende de usted especificarlo a través de la anotación.
GreenGiant
1

Agregar un comportamiento personalizado a todos los repositorios:

Para agregar un comportamiento personalizado a todos los repositorios, primero agregue una interfaz intermedia para declarar el comportamiento compartido.

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

    void sharedCustomMethod( ID id );
}

Ahora sus interfaces de repositorio individuales ampliarán esta interfaz intermedia en lugar de la interfaz de repositorio para incluir la funcionalidad declarada.

A continuación, cree una implementación de la interfaz intermedia que amplíe la clase base de repositorio específico de tecnología de persistencia. Esta clase actuará como una clase base personalizada para los servidores proxy del repositorio.

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 Parte I. Referencia ingrese la descripción de la imagen aquí

Ali Yeganeh
fuente
0

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

y agrega esta clase a @EnableJpaRepositoryries repositoryBaseClass.

Devilluminati
fuente