Eu tenho um caso de uso em que ele chama o seguinte:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Observe o @Transactional
has Propagation.REQUIRES_NEW e o repositório usa getOne . Quando executo o aplicativo, recebo a seguinte mensagem de erro:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
Mas se eu mudar o getOne(id)
por findOne(id)
tudo funciona bem.
BTW, logo antes do caso de uso chamar o método getUserControlById , ele já chamou o método insertUserControl
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Ambos os métodos são Propagation.REQUIRES_NEW porque estou executando um controle de auditoria simples .
Eu uso o getOne
método porque ele é definido na interface JpaRepository e minha interface Repository se estende a partir daí, é claro que estou trabalhando com o JPA.
A interface JpaRepository se estende a partir de CrudRepository . O findOne(id)
método é definido em CrudRepository
.
Minhas perguntas são:
- Por que falhar no
getOne(id)
método? - Quando devo usar o
getOne(id)
método?
Estou trabalhando com outros repositórios e todos usam o getOne(id)
método e todos funcionam bem, somente quando eu uso o Propagation.REQUIRES_NEW ele falha.
De acordo com a API getOne :
Retorna uma referência para a entidade com o identificador fornecido.
De acordo com a API findOne :
Recupera uma entidade pelo seu ID.
3) Quando devo usar o findOne(id)
método?
4) Qual método é recomendado para ser usado?
Desde já, obrigado.
fonte
Respostas:
TL; DR
T findOne(ID id)
(nome na API antiga) /Optional<T> findById(ID id)
(nome na nova API) conta comEntityManager.find()
a execução de um carregamento ansioso da entidade .T getOne(ID id)
dependeEntityManager.getReference()
que executa um carregamento lento da entidade . Portanto, para garantir o carregamento efetivo da entidade, é necessário invocar um método.findOne()/findById()
é realmente mais claro e simples de usar do quegetOne()
.Portanto, mesmo no mais dos casos, favorecem
findOne()/findById()
maisgetOne()
.Alteração de API
Pelo menos, a
2.0
versão,Spring-Data-Jpa
modificadafindOne()
.Anteriormente, era definido na
CrudRepository
interface como:Agora, o
findOne()
método único em que você encontraráCrudRepository
é aquele definido naQueryByExampleExecutor
interface como:Finalmente
SimpleJpaRepository
, isso é implementado pela implementação padrão daCrudRepository
interface.Este método é uma consulta por exemplo e você não deseja substituí-lo.
De fato, o método com o mesmo comportamento ainda está lá na nova API, mas o nome do método foi alterado.
Foi renomeado de
findOne()
parafindById()
naCrudRepository
interface:Agora ele retorna um
Optional
. O que não é tão ruim de prevenirNullPointerException
.Portanto, a escolha real é agora entre
Optional<T> findById(ID id)
eT getOne(ID id)
.Dois métodos distintos que se baseiam em dois métodos distintos de recuperação do JPA EntityManager
1) O
Optional<T> findById(ID id)
javadoc afirma que:À medida que analisamos a implementação, podemos ver que ela depende
EntityManager.find()
da recuperação:E aqui
em.find()
está umEntityManager
método declarado como:Seu javadoc declara:
Portanto, a recuperação de uma entidade carregada parece esperada.
2) Enquanto o
T getOne(ID id)
javadoc declara (a ênfase é minha):De fato, a referência terminologia de é realmente válida e a API JPA não especifica nenhum
getOne()
método.Portanto, a melhor coisa a fazer para entender o que o wrapper Spring faz é examinar a implementação:
Aqui
em.getReference()
está umEntityManager
método declarado como:Felizmente, o
EntityManager
javadoc definiu melhor sua intenção (a ênfase é minha):Então, invocando
getOne()
pode retornar uma entidade buscada preguiçosamente.Aqui, a busca preguiçosa não se refere aos relacionamentos da entidade, mas à própria entidade.
Isso significa que, se invocarmos
getOne()
e o contexto de persistência for fechado, a entidade poderá nunca ser carregada e, portanto, o resultado será realmente imprevisível.Por exemplo, se o objeto proxy for serializado, você poderá obter uma
null
referência como resultado serializado ou se um método for chamado no objeto proxy, uma exceção como aLazyInitializationException
lançada.Portanto, nesse tipo de situação, o lance
EntityNotFoundException
disso é o principal motivo paragetOne()
lidar com uma instância que não existe no banco de dados, pois uma situação de erro nunca pode ser executada enquanto a entidade não existe.De qualquer forma, para garantir seu carregamento, você precisa manipular a entidade enquanto a sessão é aberta. Você pode fazer isso invocando qualquer método na entidade.
Ou um uso alternativo melhor
findById(ID id)
vez de.Por que uma API tão obscura?
Para finalizar, duas perguntas para os desenvolvedores Spring-Data-JPA:
por que não ter uma documentação mais clara
getOne()
? O carregamento lento da entidade não é realmente um detalhe.por que você precisa apresentar
getOne()
para embrulharEM.getReference()
?Por que não seguir simplesmente o método embrulhado
getReference()
:? Esse método EM é realmente muito particular enquantogetOne()
transmite um processamento tão simples.fonte
getOne()
usa carregamento lento e lança umEntityNotFoundException
se nenhum item for encontrado.findById()
carrega imediatamente e retorna nulo se não for encontrado. Como existem situações imprevisíveis com getOne (), é recomendável usar findById ().A diferença básica é que
getOne
é carregado preguiçosamente efindOne
não é.Considere o seguinte exemplo:
fonte
1. Por que o método getOne (id) falha?
Veja esta seção nos documentos . A substituição da transação já em vigor pode estar causando o problema. No entanto, sem mais informações, este é difícil de responder.
2. Quando devo usar o método getOne (id)?
Sem se aprofundar nas informações internas da Spring Data JPA, a diferença parece estar no mecanismo usado para recuperar a entidade.
Se você olhar para o JavaDoc para
getOne(ID)
sob Consulte também :parece que esse método apenas delega para a implementação do gerente de entidade da JPA.
No entanto, os documentos para
findOne(ID)
não mencionam isso.A pista também está nos nomes dos repositórios.
JpaRepository
é específico da JPA e, portanto, pode delegar chamadas ao gerente da entidade, se necessário.CrudRepository
é independente da tecnologia de persistência usada. Olha aqui . É usado como uma interface de marcador para várias tecnologias de persistência como JPA, Neo4J etc.Portanto, não há realmente uma 'diferença' nos dois métodos para seus casos de uso, é apenas
findOne(ID)
mais genérico que o mais especializadogetOne(ID)
. Qual você usa depende de você e do seu projeto, mas eu pessoalmente me ateria aofindOne(ID)
, pois torna seu código menos específico da implementação e abre as portas para passar a coisas como MongoDB etc. no futuro sem muita refatoração :)fonte
there's not really a 'difference' in the two methods
aqui, porque realmente há uma grande diferença em como a entidade é recuperada e o que você deve esperar que o método retorne. A resposta mais adiante por @davidxxx destaca isso muito bem, e acho que todos que usam o Spring Data JPA devem estar cientes disso. Caso contrário, pode causar um pouco de dor de cabeça.Os
getOne
métodos retornam apenas a referência do DB (carregamento lento). Então, basicamente, você está fora da transação (o queTransactional
você declarou na classe de serviço não é considerado) e o erro ocorre.fonte
Eu realmente acho muito difícil pelas respostas acima. Do ponto de vista da depuração, eu quase passei 8 horas para conhecer o erro bobo.
Eu tenho o projeto spring + hibernate + dozer + Mysql. Para ser claro.
Eu tenho entidade de usuário, entidade de livro. Você faz os cálculos do mapeamento.
Os vários livros estavam vinculados a um usuário. Mas em UserServiceImpl eu estava tentando encontrá-lo por getOne (userId);
O resultado restante é
}
O código acima não buscou os livros que são lidos pelo usuário, digamos.
O bookList sempre foi nulo por causa de getOne (ID). Depois de alterar para findOne (ID). O resultado é
}
fonte
Embora spring.jpa.open-view fosse verdadeiro, não tive nenhum problema com o getOne, mas depois de defini-lo como false, obtive LazyInitializationException. Em seguida, o problema foi resolvido substituindo-o por findById.
Embora exista outra solução sem substituir o método getOne, e que seja colocado @Transactional no método que está chamando repository.getOne (id). Dessa maneira, a transação existe e a sessão não será fechada no seu método e, enquanto estiver usando a entidade, não haverá LazyInitializationException.
fonte
Eu tive um problema semelhante ao entender por que o JpaRespository.getOne (id) não funciona e gera um erro.
Fui e mudei para JpaRespository.findById (id), que requer que você retorne um opcional.
Este é provavelmente o meu primeiro comentário no StackOverflow.
fonte