Tengo un caso de uso en el que 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 repositorio 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 el 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 .
Uso el getOne
método porque está definido en la interfaz JpaRepository y mi interfaz Repository se extiende desde allí, por supuesto, estoy trabajando con JPA.
La interfaz JpaRepository se extiende desde CrudRepository . El findOne(id)
método se define en CrudRepository
.
Mis preguntas son:
- ¿Por qué fallar el
getOne(id)
método? - ¿Cuándo debo usar el
getOne(id)
método?
Estoy trabajando con otros repositorios y todos usan el getOne(id)
método 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 debo usar el findOne(id)
método?
4) ¿Qué método se recomienda usar?
Gracias por adelantado.
fuente
Respuestas:
TL; DR
T findOne(ID id)
(nombre en la antigua API) /Optional<T> findById(ID id)
(nombre en la nueva API) se basa enEntityManager.find()
que realiza una carga ansiosa de la entidad .T getOne(ID id)
se basa enEntityManager.getReference()
que realiza una entidad de carga diferida . Por lo tanto, para garantizar la carga efectiva de la entidad, se requiere invocar un método.findOne()/findById()
es realmente más claro y fácil de usar quegetOne()
.Así que en el mismo la mayor parte de los casos, favorecen
findOne()/findById()
másgetOne()
.Cambio de API
De al menos, la
2.0
versión,Spring-Data-Jpa
modificadafindOne()
.Anteriormente, se definía en la
CrudRepository
interfaz como:Ahora, el único
findOne()
método que encontraráCrudRepository
es el que se define en laQueryByExampleExecutor
interfaz como:Eso se implementa finalmente mediante
SimpleJpaRepository
la implementación predeterminada de laCrudRepository
interfaz.Este método es una búsqueda por ejemplo de búsqueda 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.
Fue renombrado de
findOne()
afindById()
en laCrudRepository
interfaz:Ahora devuelve un
Optional
. Lo cual no es tan malo de prevenirNullPointerException
.Entonces, la elección real es ahora entre
Optional<T> findById(ID id)
yT getOne(ID id)
.Dos métodos distintos que se basan en dos métodos distintos de recuperación de JPA EntityManager
1) El
Optional<T> findById(ID id)
javadoc afirma que:A medida que analizamos la implementación, podemos ver que depende de
EntityManager.find()
la recuperación:Y aquí
em.find()
hay unEntityManager
método declarado como:Sus estados javadoc:
Entonces, recuperar una entidad cargada parece esperado.
2) Mientras que los estados
T getOne(ID id)
javadoc (el énfasis es mío):De hecho, la terminología de referencia es realmente la placa y JPA API no especifica ningún
getOne()
método.Entonces, lo mejor que puede hacer para comprender lo que hace el contenedor de Spring es investigar la implementación:
Aquí
em.getReference()
hay unEntityManager
método declarado como:Y afortunadamente, el
EntityManager
javadoc definió mejor su intención (el énfasis es mío):Por lo tanto, invocar
getOne()
puede devolver una entidad perezosamente obtenida.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, por lo tanto, el resultado es realmente impredecible.Por ejemplo, si el objeto proxy se serializa, puede obtener una
null
referencia como resultado serializado o si se invoca un método en el objeto proxy, seLazyInitializationException
produce una excepción como la que se produce.Entonces, en este tipo de situación, el lanzamiento de
EntityNotFoundException
eso es la razón principal para usargetOne()
para manejar una instancia que no existe en la base de datos, ya que 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. Puede hacerlo invocando cualquier método en la entidad.
O un mejor uso alternativo en
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
getOne()
? La carga diferida de la entidad realmente no es un detalle.¿Por qué necesitas presentar
getOne()
para envolverEM.getReference()
?¿Por qué no simplemente apegarse al método envuelto
getReference()
:? Este método EM es realmente muy particular mientrasgetOne()
transmite un procesamiento tan simple.fuente
getOne()
utiliza la carga diferida y arroja unEntityNotFoundException
elemento si no se encuentra ningún elemento.findById()
se carga de inmediato y devuelve nulo si no se encuentra. Como hay algunas situaciones impredecibles con getOne (), se recomienda usar findById () en su lugar.La diferencia básica es que
getOne
tiene carga lenta yfindOne
no.Considere el siguiente ejemplo:
fuente
1. ¿Por qué falla el método getOne (id)?
Vea esta sección en los documentos . Si anula la transacción ya establecida, puede estar causando el problema. Sin embargo, sin más información, esta es difícil de responder.
2. ¿Cuándo debo usar el método getOne (id)?
Sin profundizar en lo interno de Spring Data JPA, la diferencia parece estar en el mecanismo utilizado para recuperar la entidad.
Si mira JavaDoc por
getOne(ID)
debajo de Vea también :parece que este método simplemente delega a 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 repositorios.
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 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 más especializadogetOne(ID)
. El que use depende de usted y de su proyecto, pero personalmente me apegaría afindOne(ID)
él, 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 :)fuente
there's not really a 'difference' in the two methods
aquí, porque realmente hay una gran diferencia en cómo se recupera la entidad y qué debería esperar que regrese el método. La respuesta más abajo de @davidxxx resalta esto muy bien, y creo que todos los que usan Spring Data JPA deberían ser conscientes de esto. De lo contrario, puede causar bastante dolor de cabeza.Los
getOne
métodos devuelven solo la referencia de DB (carga diferida). Así que básicamente estás fuera de la transacción (elTransactional
no se considera que ha sido declarado en la clase de servicio) y se produce el error.fuente
Realmente me parece 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 de primavera + hibernación + dozer + proyecto Mysql. Para ser claro.
Tengo entidad de usuario, entidad de libro. Haces los cálculos de mapeo.
¿Se vincularon los libros múltiples a un usuario? Pero en UserServiceImpl estaba tratando de encontrarlo mediante getOne (userId);
El resultado de descanso es
}
El código anterior no obtuvo los libros que el usuario lee, digamos.
La lista de libros siempre fue nula debido a getOne (ID). Después de cambiar a findOne (ID). El resultado es
}
fuente
Si bien spring.jpa.open-in-view era cierto, no tuve ningún problema con getOne, pero después de establecerlo en falso, obtuve LazyInitializationException. Entonces el problema se resolvió reemplazando con findById.
Aunque hay otra solución sin reemplazar el método getOne, y es poner @Transactional en el método que llama a repository.getOne (id). De esta manera, la transacción existirá y la sesión no se cerrará en su método y, al usar la entidad, no habrá ninguna LazyInitializationException.
fuente
Tuve un problema similar al entender por qué JpaRespository.getOne (id) no funciona y arroja un error.
Fui y cambié a JpaRespository.findById (id) que requiere que devuelvas un Opcional.
Este es probablemente mi primer comentario en StackOverflow.
fuente