Pergunta sobre java, hibernate-cascade, jpa, many-to-one, hibernate – IllegalStateException com Hibernate 4 e ManyToOne em cascata

23

Eu tenho essas duas classes

Objeto MyItem:

<code>@Entity
public class MyItem implements Serializable {

    @Id
    private Integer id;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component defaultComponent;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component masterComponent;

    //default constructor, getter, setter, equals and hashCode
}
</code>

Objeto Componente:

<code>@Entity
public class Component implements Serializable {

    @Id
    private String name;

    //again, default constructor, getter, setter, equals and hashCode
}
</code>

E estou tentando persistir com o seguinte código:

<code>public class Test {

    public static void main(String[] args) {
        Component c1 = new Component();
        c1.setName("comp");
        Component c2 = new Component();
        c2.setName("comp");
        System.out.println(c1.equals(c2)); //TRUE

        MyItem item = new MyItem();
        item.setId(5);
        item.setDefaultComponent(c1);
        item.setMasterComponent(c2);

        ItemDAO itemDAO = new ItemDAO();
        itemDAO.merge(item);
    }
}
</code>

Enquanto isso funciona bem com o Hibernate 3.6, o Hibernate 4.1.3 lança

<code>Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
        at org.hibernate.event.internal.EventCache.put(EventCache.java:184)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:423)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:213)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:282)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
        at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874)
        at sandbox.h4bug.Test$GenericDAO.merge(Test.java:79)
        at sandbox.h4bug.Test.main(Test.java:25)
</code>

O backend do banco de dados é h2 (mas o mesmo acontece com o hsqldb ou derby). O que estou fazendo de errado?

Sua resposta

9   a resposta
4

unidirecional oubidirecional? Se for bidirecional, certifique-se de não terCascade.MERGE chama voltar para o item.

Basicamente, a versão mais nova do Hibernate tem um mapa de entidade que contém uma lista de todas as coisas que precisam ser mescladas com base na chamada para merge () ele chamará merge e então passará para o próximo, mas manterá as coisas no mapa, ele lançará o erro que você declarar acima "Uma cópia de entidade já foi atribuída a uma entidade diferente" quando encontrar um item que já tenha sido tratado. Encontramos em nosso aplicativo quando localizamos essas mesclagens "ascendentes" no gráfico de objeto, ou seja, nos links bidirecionais, ela fixa a chamada de mesclagem.

0

apenas resolvi isso. Embora as respostas acima possam resolver o problema, discordo de algumas delas, especialmente alterando os métodos implementados equlas () e hashcode (). No entanto, sinto que minha resposta reforça as respostas de @Tobb e @Supun s '.

No meu lado Many (lado da criança) eu tive

 @OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER)
 private Colllection books;

E do meu lado (lado dos pais)

 @ManyToOne(cascade =CascadeType.ALL)
 private AuthorID authorID;

Depois de ler a excelente resposta oferecida pelo @Tobb e pensar um pouco, percebi que as anotações não faziam sentido. Do jeito que eu entendi (no meu caso) eu estava mesclando () o objeto Author e mesclando () o objeto Book. Mas como a coleção de livros é um componente do objeto Autor, ela tentava salvá-lo duas vezes. Minha solução foi mudar os tipos de cascata para:

  @OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER)
  private Collection bookCollection;

e

 @ManyToOne(cascade =CascadeType.MERGE)
 private AuthorID authorID;

Para encurtar a história, persista o objeto pai e mescle o objeto filho.

Espero que isso ajude / faça sentido.

0

você pode usar o objeto c1 em todo o código.

Se isso for apenas um exemplo e você criar o objeto c2 em outra parte do código, não deverá criar um novo objeto, mas carregá-lo do banco de dados:

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
27

O método de mesclagem percorre o gráfico do objeto que você deseja armazenar, e para cada objeto nesse gráfico ele o carrega do banco de dados, portanto, ele tem um par de (entidade persistente, entidade separada) para cada objeto no gráfico, onde A entidade desanexada é a entidade que será armazenada e a entidade persistente é obtida do banco de dados. (No método, bem como na mensagem de erro, a entidade persistente é conhecida como 'copy'). Em seguida, esses pares são colocados em dois mapas, um com a entidade persistente como chave e a entidade desanexada como valor, e um com a entidade desanexada como chave e a entidade persistente como valor.

Para cada par de diretórios, ele verifica esses mapas, para ver se a entidade persistente mapeia para a mesma entidade separada de antes (se já foi visitada) e vice-versa. Esse problema ocorre quando você obtém um par de entidades em que fazer um get com a entidade persistente retorna um valor, mas um get do outro mapa, com a entidade desanexada retorna null, o que significa que você já vinculou a entidade persistente a um desanexado entidade com um código hash diferente (basicamente o identificador de objeto, se você não tiver substituído o método hashcode).

TL; DR, você tem vários objetos com diferentes identificadores de objeto / hashcode, mas com o mesmo identificador de persistência (referenciando a mesma entidade persistente). Isso aparentemente não é mais permitido em versões mais recentes do Hibernate4 (4.1.3.Final e superior do que eu poderia dizer).

A mensagem de erro não é muito boa, o que realmente deve ser dito é algo como:

A persistent entity has already been assigned to a different detached entity

ou

Multiple detached objects corresponding to the same persistent entity

Não tenho certeza, mas pelo que entendi da sua descrição, parece que é até "mais válido", o que significa que você não pode contornar o erro adicionando seu próprio código hass / ish. Agora, é necessário que haja um 1-1 entre os IDs de objetos e os IDs do banco de dados no gráfico de objetos para mesclar e não lançar tal exceção. Tobb
Problema ainda existe com a versão "4.1.10 Final". Revertendo para a versão "4.1.2 Final" funciona. Zaki
Vejohibernate.atlassian.net/browse/HHH-7605 Zaki
Grande descrição do erro. Qualquer pensamento sobre o que podemos fazer sobre isso? Webnet
0

@GeneratedValue anotação sob@Id na classe Component. caso contrário, duas instâncias diferentes podem obter o mesmo id e colidir.

Parece que você está dando a mesma identificação.

    Component c1 = new Component();
    c1.setName("comp");
    Component c2 = new Component();
    c2.setName("comp");

Isso só poderia resolver seu problema.

Infelizmente, os dois IDs não são gerados por banco de dados, mas definidos explicitamente. Clayton Louden
@RichardPena, Se você quiser que essas duas instâncias representem a mesma entidade do banco de dados, por que não usar apenas uma instância? Ido.Co
Sim, essa é a ideia por trás disso. Os dois componentes obtêm o mesmo id e são iguais (consulte a instrução equals acima). Então cascata deve cuidar disso, certo? Você pode até tentar usar a mesma referência (por exemplo, c1) para ambas as variáveis ​​(defaultComponent e masterComponent), pois elas são iguais de qualquer maneira. Clayton Louden
Por que você tenta atribuir duas instâncias diferentes que representam a mesma entidade do banco de dados para uma classe? Teria sido melhor usar a mesma instância. Eu acho que você está abusando do campo DB id @. Ido.Co
Opa, eu quis dizer a classe Component. você está dando a eles o mesmo id? Ido.Co
0

todas as entidades no gráfico de objeto devem ser exclusivas. Portanto, a melhor solução (ou é possível?) É remover cascata em MyItem to Component. E mesclar Component separadamente se realmente for necessário - eu apostaria que em 95% dos casos o componente não deveria ser mesclado de acordo com a lógica de negócios.

Por outro lado - estou realmente interessado em conhecer os verdadeiros pensamentos por trás dessa restrição.

5

verifique seu método equals (). Muito provavelmente está mal implementado.

Edit: Eu verifiquei que uma operação de mesclagem não funcionará se você não implementar os métodos equals () e hashCode () da Entity corretamente.

Você deve seguir estas diretrizes para implementar equals () e hashCode ():

http://docs.jboss.org/hibernate/orm/4.1/manual/pt-BR/html/ch04.html#persistent-classes-equalshashcode

"É recomendado que você implemente equals () e hashCode () usando a igualdade de chave Business. Business key equality significa que o método equals () compara apenas as propriedades que formam a chave de negócios. É uma chave que identifica nossa instância no mundo real (uma chave candidata natural) "

Isso significa: você NÃO deve usar seu ID como parte de sua implementação equals ()!

Mais informações:blog.andrewbeacock.com/2008/08/… Ricardo Arguello
0
O problema ainda está lá com o Jboss AS 7.2. Magnilex
2

eto que está tendo duas cópias de um objeto filho, foi corrigido por, na entidade a partir de:

@OneToOne(cascade = CascadeType.MERGE)
private User reporter;
@OneToOne(cascade = CascadeType.MERGE)
private User assignedto;

para somente,

@OneToOne
private User reporter;
@OneToOne
private User assignedto;

Eu não sei o motivo embora

Perguntas relacionadas