Cuando use los métodos getOne y findOne Spring Data JPA

Tengo un caso de uso donde se llama lo siguiente:

@Override @Transactional(propagation=Propagation.REQUIRES_NEW) public UserControl getUserControlById(Integer id){ return this.userControlRepository.getOne(id); } 

Observe que @Transactional tiene Propagation.REQUIRES_NEW y el repository usa getOne . Cuando ejecuto la aplicación, recibo el siguiente mensaje de error:

 Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session ... 

Pero si cambio getOne(id) por findOne(id) todo funciona bien.

Por cierto, justo antes de que el caso de uso llame al método getUserControlById , ya ha llamado al método insertUserControl

 @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public UserControl insertUserControl(UserControl userControl) { return this.userControlRepository.save(userControl); } 

Ambos métodos son Propagation.REQUIRES_NEW porque estoy haciendo un control de auditoría simple.

Utilizo el método getOne porque está definido en la interfaz JpaRepository y la interfaz de mi repository se extiende desde allí, estoy trabajando con JPA, por supuesto.

La interfaz JpaRepository se extiende desde CrudRepository . El findOne(id) se define en CrudRepository .

Mis preguntas son:

  1. ¿Por qué falla el getOne(id) ?
  2. ¿Cuándo debería usar el getOne(id) ?

Estoy trabajando con otros repositorys y todos usan el getOne(id) y todo funciona bien, solo cuando uso Propagation.REQUIRES_NEW falla.

De acuerdo con la API getOne :

Devuelve una referencia a la entidad con el identificador dado.

De acuerdo con la API findOne :

Recupera una entidad por su id.

3) ¿Cuándo debería usar el findOne(id) ?

4) ¿Qué método se recomienda usar?

Gracias por adelantado.

1. ¿Por qué falla el método getOne (id)?

Vea esta sección en los documentos . Es posible que esté sobrescribiendo la transacción que ya estaba en el lugar. Sin embargo, sin más información, este es difícil de responder.

2. ¿Cuándo debería usar el método getOne (id)?

Sin profundizar en las partes internas de Spring Data JPA, la diferencia parece estar en el mecanismo utilizado para recuperar la entidad.

Si miras el getOne(ID) para getOne(ID) en See Also :

 See Also: EntityManager.getReference(Class, Object) 

parece que este método solo delega en la implementación del administrador de la entidad JPA.

Sin embargo, los documentos para findOne(ID) no mencionan esto.

La pista también está en los nombres de los repositorys. JpaRepository es específico de JPA y, por lo tanto, puede delegar llamadas al administrador de la entidad si es necesario. CrudRepository es agnóstico de la tecnología de persistencia utilizada. Mira aquí . Se utiliza como una interfaz de marcador para múltiples tecnologías de persistencia como JPA, Neo4J , etc.

Entonces, no hay realmente una ‘diferencia’ en los dos métodos para sus casos de uso, es solo que findOne(ID) es más genérico que el getOne(ID) más especializado. Cuál de ustedes es depende de usted y de su proyecto, pero yo personalmente me findOne(ID) al findOne(ID) ya que hace que su código sea menos específico de implementación y abre las puertas para pasar a cosas como MongoDB etc. en el futuro sin demasiada refactorización: )

La diferencia básica es que getOne está cargado de forma diferida y findOne no.

Considere el siguiente ejemplo:

 public static String NON_EXISTING_ID = -1; ... MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID); MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID); if(findEnt != null) { findEnt.getText(); // findEnt is null - this code is not executed } if(getEnt != null) { getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!! } 

TL; DR

T findOne(ID id) (nombre en la antigua API) / Optional findById(ID id) (nombre en la nueva API) se basa en EntityManager.find() que realiza una carga entusiasta de la entidad .

T getOne(ID id) basa en EntityManager.getReference() que realiza una carga diferida de entidad . Por lo tanto, para garantizar la carga efectiva de la entidad, se requiere invocar un método en ella.

findOne()/findById() es realmente más claro y fácil de usar que getOne() .
Por lo tanto, en la mayoría de los casos, findOne()/findById() sobre getOne() .


Cambio de API

Por lo menos, la versión 2.0 , Spring-Data-Jpa modificó findOne() .
Anteriormente, se definió en la interfaz CrudRepository como:

 T findOne(ID primaryKey); 

Ahora, el único método findOne() que encontrará en CrudRepository es el que se definió en la interfaz QueryByExampleExecutor como:

  Optional findOne(Example example); 

Eso es implementado finalmente por SimpleJpaRepository , la implementación predeterminada de la interfaz CrudRepository .
Este método es una consulta mediante búsqueda de ejemplo y no desea hacerlo como reemplazo.

De hecho, el método con el mismo comportamiento todavía está allí en la nueva API, pero el nombre del método ha cambiado.
Se cambió el nombre de findOne() a findById() en la interfaz CrudRepository :

 Optional findById(ID id); 

Ahora devuelve un Optional . Lo cual no es tan malo para prevenir NullPointerException .

Por lo tanto, la elección real ahora es entre Optional findById(ID id) y T getOne(ID id) .


Dos métodos distintos que se basan en dos métodos de recuperación distintos de JPA EntityManager

1) El javadoc Optional findById(ID id) indica:

Recupera una entidad por su id.

Al analizar la implementación, podemos ver que confía en EntityManager.find() para realizar la recuperación:

 public Optional findById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); Class domainType = getDomainClass(); if (metadata == null) { return Optional.ofNullable(em.find(domainType, id)); } LockModeType type = metadata.getLockModeType(); Map hints = getQueryHints().withFetchGraphs(em).asMap(); return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints)); } 

Y aquí em.find() es un método EntityManager declarado como:

 public  T find(Class entityClass, Object primaryKey, Map properties); 

Sus estados javadoc:

Buscar por clave principal, usando las propiedades especificadas

Entonces, la recuperación de una entidad cargada parece esperada.

2) Mientras que los estados de javadoc de T getOne(ID id) (el énfasis es mío):

Devuelve una referencia a la entidad con el identificador dado.

De hecho, la terminología de referencia es realmente board y JPA API no especifica ningún método getOne() .
Entonces, lo mejor que se puede hacer para comprender lo que hace Spring wrapper es observar la implementación:

 @Override public T getOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); return em.getReference(getDomainClass(), id); } 

Aquí em.getReference() es un método EntityManager declarado como:

 public  T getReference(Class entityClass, Object primaryKey); 

Y afortunadamente, el EntityManager javadoc mejoró su intención (el énfasis es mío):

Obtenga una instancia, cuyo estado puede ser extraído de forma perezosa . Si la instancia solicitada no existe en la base de datos, la EntityNotFoundException se lanza cuando se accede por primera vez al estado de la instancia . (El tiempo de ejecución del proveedor de persistencia permite lanzar EntityNotFoundException cuando se llama a getReference). La aplicación no debe esperar que el estado de instancia esté disponible al momento de la separación , a menos que la aplicación lo haya accedido mientras el administrador de entidades estaba abierto.

Por lo tanto, al invocar getOne() puede devolverse una entidad getOne() .
Aquí, la recuperación perezosa no se refiere a las relaciones de la entidad, sino a la entidad misma.

Significa que si invocamos getOne() y luego se cierra el contexto de Persistencia, la entidad nunca se cargará y el resultado será realmente impredecible.
Por ejemplo, si el objeto proxy está serializado, puede obtener una referencia null como resultado serializado o si se invoca un método en el objeto proxy, se lanza una excepción como LazyInitializationException .
Entonces, en este tipo de situación, el lanzamiento de EntityNotFoundException que es la razón principal para usar getOne() para manejar una instancia que no existe en la base de datos como una situación de error nunca se puede realizar mientras la entidad no existe.

En cualquier caso, para garantizar su carga, debe manipular la entidad mientras se abre la sesión. Puedes hacerlo invocando cualquier método en la entidad.
O una mejor alternativa use findById(ID id) lugar de.


¿Por qué una API tan poco clara?

Para finalizar, dos preguntas para los desarrolladores de Spring-Data-JPA:

  • ¿Por qué no tener una documentación más clara para getOne() ? La carga diferida de la entidad no es realmente un detalle.

  • ¿Por qué necesita introducir getOne() para ajustar EM.getReference() ?
    ¿Por qué no simplemente apegarse al método envuelto: getReference() ? Este método EM es realmente muy particular, mientras que getOne() transmite un procesamiento tan simple.

Los métodos getOne devuelven solo la referencia de DB (carga diferida). Así que, básicamente, se encuentra fuera de la transacción (no se considera el Transactional que se ha declarado en clase de servicio) y se produce el error.

Realmente encuentro muy difícil de las respuestas anteriores. Desde la perspectiva de la depuración casi pasé 8 horas para conocer el error tonto.

Tengo pruebas spring + hibernación + dozer + proyecto Mysql. Para ser claro.

Tengo entidad de usuario, Entidad de libro. Usted hace los cálculos de mapeo.

Los libros múltiples estaban vinculados a un usuario. Pero en UserServiceImpl estaba tratando de encontrarlo por getOne (userId);

 public UserDTO getById(int userId) throws Exception { final User user = userDao.getOne(userId); if (user == null) { throw new ServiceException("User not found", HttpStatus.NOT_FOUND); } userDto = mapEntityToDto.transformBO(user, UserDTO.class); return userDto; } 

El resultado del rest es

 { "collection": { "version": "1.0", "data": { "id": 1, "name": "TEST_ME", "bookList": null }, "error": null, "statusCode": 200 }, "booleanStatus": null 

}

El código anterior no obtuvo los libros que el usuario lee, por ejemplo.

BookList siempre fue nulo debido a getOne (ID). Después de cambiar a findOne (ID). El resultado es

 { "collection": { "version": "1.0", "data": { "id": 0, "name": "Annama", "bookList": [ { "id": 2, "book_no": "The karma of searching", } ] }, "error": null, "statusCode": 200 }, "booleanStatus": null 

}

Tuve un problema similar al entender por qué JpaRespository.getOne (id) no funciona y arrojar un error.

Fui y cambié a JpaRespository.findById (id) que requiere que devuelva un Opcional.

Este es probablemente mi primer comentario sobre StackOverflow.