Eu tenho um modelo de objeto persistido por JPA que contém um relacionamento muitos-para-um: um Account
tem muitos Transactions
. A Transaction
tem um Account
.
Aqui está um trecho do código:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
Eu sou capaz de criar um Account
objeto, adicionar transações a ele e persistir o Account
objeto corretamente. Mas, quando crio uma transação, usando uma Conta já persistente e persistindo a Transação , recebo uma exceção:
Causado por: org.hibernate.PersistentObjectException: entidade desanexada passada para persistir: com.paulsanwald.Account em org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Portanto, sou capaz de persistir um Account
que contenha transações, mas não uma transação que possua um Account
. Eu pensei que isso era porque o Account
não pode ser anexado, mas esse código ainda me dá a mesma exceção:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Como posso salvar corretamente um Transaction
associado a um Account
objeto já persistente ?
Respostas:
Esse é um problema típico de consistência bidirecional. É bem discutido neste link e também neste link.
De acordo com os artigos nos 2 links anteriores, você precisa corrigir seus levantadores nos dois lados do relacionamento bidirecional. Um exemplo de setter para o lado Um está neste link.
Um exemplo de setter para o lado Muitos está neste link.
Depois de corrigir seus setters, você deseja declarar o tipo de acesso à entidade como "Propriedade". A melhor prática para declarar o tipo de acesso "Propriedade" é mover TODAS as anotações das propriedades do membro para os getters correspondentes. Uma grande palavra de cautela é não misturar os tipos de acesso "Campo" e "Propriedade" na classe da entidade, caso contrário, o comportamento não será definido pelas especificações da JSR-317.
fonte
detached entity passed to persist
por que melhorar a consistência faz com que funcione? Ok, a consistência foi reparada, mas o objeto ainda está desanexado.A solução é simples, basta usar o em
CascadeType.MERGE
vez deCascadeType.PERSIST
ouCascadeType.ALL
.Eu tive o mesmo problema e
CascadeType.MERGE
funcionou para mim.Espero que você esteja resolvido.
fonte
Remova a cascata da entidade filha
Transaction
, deve ser apenas:(
FetchType.EAGER
pode ser removido e também o padrão@ManyToOne
)Isso é tudo!
Por quê? Ao dizer "cascata ALL" na entidade filha,
Transaction
você exige que todas as operações do banco de dados sejam propagadas para a entidade paiAccount
. Se você o fizerpersist(transaction)
,persist(account)
será invocado também.Mas apenas entidades (novas) transitórias podem ser passadas para
persist
(Transaction
nesse caso). Os desanexados (ou outros estados não transitórios) podem não ser (Account
neste caso, como já está no DB).Portanto, você obtém a exceção "entidade desanexada passada para persistir" . A
Account
entidade é destinada! Não é o queTransaction
você chamapersist
.Você geralmente não deseja propagar de filho para pai. Infelizmente, existem muitos exemplos de código nos livros (mesmo nos bons) e na rede, que fazem exatamente isso. Não sei por que ... Talvez às vezes simplesmente copiei várias vezes sem pensar muito ...
Adivinha o que acontece se você
remove(transaction)
ainda chama "cascata ALL" nesse @ManyToOne? Oaccount
(btw, com todas as outras transações!) Também será excluído do banco de dados. Mas essa não era sua intenção, era?fonte
O uso da mesclagem é arriscado e complicado, portanto, é uma solução alternativa suja no seu caso. Você precisa lembrar pelo menos que, quando você passa um objeto de entidade para mesclar, ele deixa de ser anexado à transação e, em vez disso, uma nova entidade agora anexada é retornada. Isso significa que, se alguém tiver o objeto de entidade antigo ainda em sua posse, as alterações nele serão silenciosamente ignoradas e descartadas no commit.
Você não está mostrando o código completo aqui, por isso não posso verificar seu padrão de transação. Uma maneira de chegar a uma situação como essa é se você não tiver uma transação ativa ao executar a mesclagem e persistir. Nesse caso, espera-se que o provedor de persistência abra uma nova transação para cada operação JPA executada e confirme e feche-a imediatamente antes que a chamada retorne. Se esse for o caso, a mesclagem será executada em uma primeira transação e, depois que o método de mesclagem retornar, a transação será concluída e fechada e a entidade retornada será desanexada. A persistência abaixo abriria uma segunda transação e tentaria se referir a uma entidade desanexada, dando uma exceção. Sempre envolva seu código em uma transação, a menos que você saiba muito bem o que está fazendo.
Usando transações gerenciadas por contêiner, seria algo parecido com isto. Observe: isso pressupõe que o método esteja dentro de um bean de sessão e chamado via interface local ou remota.
fonte
@Transaction(readonly=false)
. Na camada de serviço, ainda im recebendo o mesmo problema,Provavelmente, nesse caso, você obteve seu
account
objeto usando a lógica de mesclagem epersist
é usado para persistir novos objetos e ele se queixará se a hierarquia estiver tendo um objeto já persistido. Você deve usarsaveOrUpdate
nesses casos, em vez depersist
.fonte
merge
notransaction
objeto que você obter o mesmo erro?transaction
não foi persistente? Como você checou. Observe quemerge
está retornando uma referência ao objeto persistente.Não passe id (pk) para o método persistente ou tente o método save () em vez de persist ().
fonte
TestEntityManager.persistAndFlush()
para tornar uma instância gerenciada e persistente e sincronizar o contexto de persistência com o banco de dados subjacente. Retorna a entidade de origem originalPara corrigir o problema, siga estas etapas:
1. Removendo a associação de filhos em cascata
Portanto, você precisa remover o
@CascadeType.ALL
da@ManyToOne
associação. Entidades filhas não devem cascatear com associações pai. Somente entidades pai devem cascatear para entidades filho.Observe que eu defino o
fetch
atributo comoFetchType.LAZY
porque a busca ansiosa é muito ruim para o desempenho .2. Defina os dois lados da associação
Sempre que você tem uma associação bidirecional, é necessário sincronizar os dois lados usando
addChild
eremoveChild
métodos na entidade pai:Para mais detalhes sobre por que é importante sincronizar as duas extremidades de uma associação bidirecional, confira este artigo .
fonte
Na definição da sua entidade, você não está especificando o @JoinColumn para o
Account
associado a umTransaction
. Você vai querer algo assim:EDIT: Bem, acho que seria útil se você estivesse usando a
@Table
anotação em sua classe. Heh. :)fonte
Mesmo que suas anotações sejam declaradas corretamente para gerenciar adequadamente o relacionamento um para muitos, você ainda poderá encontrar essa exceção precisa. Ao adicionar um novo objeto filho,,
Transaction
a um modelo de dados anexado, você precisará gerenciar o valor da chave primária - a menos que não deva . Se você fornecer um valor de chave primária para uma entidade filha declarada da seguinte maneira antes de chamarpersist(T)
, você encontrará essa exceção.Nesse caso, as anotações estão declarando que o banco de dados gerenciará a geração dos valores da chave primária da entidade após a inserção. Fornecer um você mesmo (como por meio do configurador do ID) causa essa exceção.
Alternativamente, mas efetivamente o mesmo, esta declaração de anotação resulta na mesma exceção:
Portanto, não defina o
id
valor no código do aplicativo quando ele já estiver sendo gerenciado.fonte
Se nada ajudar e você ainda estiver recebendo essa exceção, revise seus
equals()
métodos - e não inclua a coleção filho. Especialmente se você tiver uma estrutura profunda de coleções incorporadas (por exemplo, A contém Bs, B contém Cs etc.).No exemplo de
Account -> Transactions
:No exemplo acima, remova as transações dos
equals()
cheques. Isso ocorre porque o hibernate implica que você não está tentando atualizar um objeto antigo, mas passa um novo objeto para persistir sempre que alterar o elemento na coleção filho.É claro que essas soluções não se encaixam em todos os aplicativos e você deve projetar cuidadosamente o que deseja incluir nos métodos
equals
ehashCode
.fonte
Talvez seja o bug do OpenJPA. Ao reverter, redefine o campo @Version, mas o pcVersionInit permanece verdadeiro. Eu tenho um AbstraceEntity que declarou o campo @Version. Eu posso solucionar isso redefinindo o campo pcVersionInit. Mas não é uma boa ideia. Eu acho que não funciona quando a entidade persistir em cascata.
fonte
Você precisa definir a transação para cada conta.
Ou pode ser suficiente (se apropriado) para definir os IDs como nulos em muitos aspectos.
fonte
fonte
Minha resposta baseada em JPA do Spring Data: simplesmente adicionei uma
@Transactional
anotação ao meu método externo.Por que funciona
A entidade filha foi imediatamente desanexada porque não havia um contexto ativo da Hibernate Session. O fornecimento de uma transação Spring (Data JPA) garante a presença de uma sessão de hibernação.
Referência:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
fonte
No meu caso, eu estava confirmando a transação quando o método persist foi usado. Ao alterar o método persistir para salvar , ele foi resolvido.
fonte
Se as soluções acima não funcionarem apenas uma vez, comente os métodos getter e setter da classe de entidade e não defina o valor de id. (Chave primária) Então isso funcionará.
fonte
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) funcionou para mim.
fonte
Resolvido salvando o objeto dependente antes do próximo.
Isso aconteceu comigo porque eu não estava definindo o ID (que não foi gerado automaticamente). e tentando salvar com relação @ManytoOne
fonte