Erro de Hibernate: um objeto diferente com o mesmo valor de identificador já estava associado à sessão

91

Eu tenho essencialmente alguns objetos nesta configuração (o modelo de dados real é um pouco mais complexo):

  • A tem um relacionamento muitos para muitos com B. (B tem inverse="true")
  • B tem uma relação muitos-para-um com C. (eu cascadeconfigurei para "save-update")
  • C é um tipo de tabela de tipo / categoria.

Além disso, provavelmente devo mencionar que as chaves primárias são geradas pelo banco de dados ao salvar.

Com meus dados, às vezes tenho problemas em que A tem um conjunto de objetos B diferentes e esses objetos B se referem ao mesmo objeto C.

Quando eu chamar session.saveOrUpdate(myAObject), eu recebo um erro de hibernação dizendo: "a different object with the same identifier value was already associated with the session: C". Eu sei que o Hibernate não pode inserir / atualizar / excluir o mesmo objeto duas vezes na mesma sessão, mas há alguma maneira de contornar isso? Esta não parece ser uma situação tão incomum.

Durante minha pesquisa desse problema, vi pessoas sugerirem o uso de session.merge(), mas quando faço isso, quaisquer objetos "conflitantes" são inseridos no banco de dados como objetos em branco com todos os valores definidos como nulos. Obviamente, não é isso que queremos.

[Editar] Outra coisa que esqueci de mencionar é que (por razões arquitetônicas além do meu controle), cada leitura ou gravação precisa ser feita em uma sessão separada.

John
fonte
Veja se essa resposta ajuda você ..
joaonlima

Respostas:

97

Muito provavelmente é porque os objetos B não estão se referindo à mesma instância do objeto Java C. Eles estão se referindo à mesma linha no banco de dados (ou seja, a mesma chave primária), mas são cópias diferentes dela.

Então o que está acontecendo é que a sessão do Hibernate, que está gerenciando as entidades, estaria monitorando qual objeto Java corresponde à linha com a mesma chave primária.

Uma opção seria certificar-se de que as entidades de objetos B que se referem à mesma linha estão realmente se referindo à mesma instância de objeto de C. Como alternativa, desative o cascateamento para essa variável de membro. Dessa forma, quando B é persistente, C não é. Você terá que salvar C manualmente separadamente. Se C for uma tabela de tipo / categoria, provavelmente faz sentido ser assim.

jbx
fonte
3
Obrigado jbx. Como você disse, acontece que os objetos B estão se referindo a várias instâncias C na memória. Essencialmente, o que está acontecendo é que uma parte do meu programa está lendo em C e anexando-o a B. Outra parte está carregando um B diferente com o mesmo C do banco de dados. Ambos estão sendo anexados a A, o que aciona o erro ao salvar. Eu defini a <pre> cascata </pre> para o relacionamento B-> C como "<pre> none </pre>", mas ainda estou recebendo o mesmo erro. Em relacionamentos muitos-para-um ou um-para-muitos, existe uma maneira de dizer ao Hibernate apenas para alterar a chave estrangeira e não se preocupar com o resto?
João,
1
A chave primária de C tem alguma estratégia de geração de ID? Como um gerador de sequência ou algo semelhante?
jbx
Sim, cada um tem sua própria sequência no banco de dados. Como você mencionou, o cascateamento acabou sendo o problema. Desativamos o cascateamento para as tabelas de tipo e, para as outras, usamos a cascata "merge", que nos permitiu chamar merge () sem criar todas as linhas nulas. Marquei sua resposta de acordo, obrigado!
João
14
Usei merge () em vez de saveOrUpdate () e BOOM! funciona :)
Lahiru Ruhunage
27

Apenas defina cascade como MERGE, isso deve funcionar.

andy.codes
fonte
13

Você só precisa fazer uma coisa. Corresession_object.clear() e salve o novo objeto. Isso limpará a sessão (como apropriadamente nomeado) e removerá o objeto duplicado ofensivo de sua sessão.

dsk
fonte
9

Eu concordo com @Hemant Kumar, muito obrigado. De acordo com sua solução, resolvi meu problema.

Por exemplo:

@Test
public void testSavePerson() {
    try (Session session = sessionFactory.openSession()) {
        Transaction tx = session.beginTransaction();
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("222");
        person2.setName("111");
        session.save(person1);
        session.save(person2);
        tx.commit();
    }
}

Person.java

public class Person {
    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Este código sempre erra na minha aplicação :,A different object with the same identifier value was already associated with the session depois descobri que esqueci de aumentar automaticamente a minha chave primária!

Minha solução é adicionar este código à sua chave primária:

@GeneratedValue(strategy = GenerationType.AUTO)
Gelo azul
fonte
6

Isso significa que você está tentando salvar várias linhas em sua tabela com referência ao mesmo objeto.

verifique a propriedade id da sua classe de entidade.

@Id
private Integer id;

para

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;
rex roy
fonte
1
não é o caso com a pergunta conforme a descrição fornecida
Sudip Bhandari
5

Transfira a tarefa de atribuição do ID do objeto do Hibernate para o banco de dados usando:

<generator class="native"/>

Isso resolveu o problema para mim.

user2845946
fonte
3

Adicione a anotação @GeneratedValue ao bean que você está inserindo.

Hemant kumar
fonte
Obrigado! Esse era exatamente o meu problema
DriLLFreAK100
3

Uma maneira de resolver o problema acima será substituir o hashcode().
Também limpe a sessão de hibernação antes e depois de salvar.

getHibernateTemplate().flush();

Definir explicitamente o objeto destacado como nulltambém ajuda.

Waqar
fonte
2

Acabei de encontrar esta mensagem, mas em código c #. Não tenho certeza se é relevante (exatamente a mesma mensagem de erro).

Eu estava depurando o código com pontos de interrupção e expandindo algumas coleções por meio de membros privados enquanto o depurador estava em um ponto de interrupção. Ter executado novamente o código sem vasculhar as estruturas fez a mensagem de erro desaparecer. Parece que o ato de examinar as coleções privadas carregadas lentamente fez o NHibernate carregar coisas que não deveriam ser carregadas naquele momento (porque estavam em membros privados).

O próprio código está envolvido em uma transação bastante complicada que pode atualizar um grande número de registros e muitas dependências como parte dessa transação (processo de importação).

Esperançosamente, uma pista para qualquer pessoa que se deparar com o problema.

Ales Potocnik Hahonina
fonte
2

Encontre o atributo "Cascade" no Hibernate e apague-o. Ao definir "Cascata" disponível, ele chamará outras operações (salvar, atualizar e excluir) em outras entidades que tenham relação com as classes relacionadas. Assim, o mesmo valor de identidades acontecerá. Funcionou comigo.

Nguyen Vu Quang
fonte
1

Tive esse erro alguns dias depois e gastei muito tempo para corrigi-lo.

 public boolean save(OrderHeader header) {
    Session session = sessionFactory.openSession();


    Transaction transaction = session.beginTransaction();

    try {
        session.save(header);

        for (OrderDetail detail : header.getDetails()) {
            session.save(detail);
        }

        transaction.commit();
        session.close();

        return true;
    } catch (HibernateException exception) {

        exception.printStackTrace();
        transaction.rollback();
        return false;
    }
}

Antes de receber esse erro, não mencionei o tipo de geração de ID no objeto OrderDetil. quando sem gerar o id de Orderdetails, ele mantém o Id como 0 para todos os objetos OrderDetail. foi isso que #jbx explicou. Sim, é a melhor resposta. este um exemplo de como isso acontece.

Buddhi
fonte
1

Tente colocar o código da sua consulta antes. Isso resolve meu problema. por exemplo, mude isso:

query1 
query2 - get the error 
update

para isso:

query2
query1
update
juldeh
fonte
0

você pode não definir o identificador do objeto antes de chamar a consulta de atualização.

Fawad Khaliq
fonte
3
Do contrário, não teria esse problema. O problema é que ele tem dois objetos com o mesmo identificador.
aalku
0

Eu encontrei o problema porque a geração da chave primária está errada, quando insiro uma linha como esta:

public void addTerminal(String typeOfDevice,Map<Byte,Integer> map) {
        // TODO Auto-generated method stub
        try {
            Set<Byte> keySet = map.keySet();
            for (Byte byte1 : keySet) {
                Device device=new Device();
                device.setNumDevice(DeviceCount.map.get(byte1));
                device.setTimestamp(System.currentTimeMillis());
                device.setTypeDevice(byte1);
                this.getHibernateTemplate().save(device);
            }
            System.out.println("hah");
        }catch (Exception e) {
            // TODO: handle exception
            logger.warn("wrong");
            logger.warn(e.getStackTrace()+e.getMessage());
        }
}

Eu mudo a classe do gerador de id para identidade

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>
张云 风
fonte
0

No meu caso, apenas flush () não funcionou. Tive que usar um clear () após flush ().

public Object merge(final Object detachedInstance)
    {
        this.getHibernateTemplate().flush();
        this.getHibernateTemplate().clear();
        try
        {
            this.getHibernateTemplate().evict(detachedInstance);
        }
}
Shubhranshu
fonte
0

se você usar EntityRepository, use saveAndFlush em vez de salvar

Jayen Chondigara
fonte
0

Se deixei uma aba de expressões em meu IDE aberta que estava fazendo uma chamada get de hibernação no objeto que está causando esta exceção. Eu estava tentando excluir este mesmo objeto. Também tive um ponto de interrupção na chamada de exclusão que parece ser necessário para que esse erro aconteça. Simplesmente fazer outra guia de expressões ser a guia frontal ou alterar a configuração para que o ide não pare nos pontos de interrupção resolveu este problema.

Aaron Byrnes
fonte
0

Certifique-se de que sua entidade tem o mesmo tipo de geração com todas as entidades mapeadas

Ex: UserRole

public class UserRole extends AbstractDomain {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

@Enumerated(EnumType.STRING)
private CommonStatus status;

private String roleCode;

private Long level;

@Column(columnDefinition = "integer default 0")
private Integer subRoleCount;

private String modification;

@ManyToOne(fetch = FetchType.LAZY)
private TypeOfUsers licenseType;

}

Módulo:

public class Modules implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

}

Entidade Principal com Mapeamento

public class RoleModules implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private UserRole role;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Modules modules;

@Type(type = "yes_no")
private boolean isPrimaryModule;

public boolean getIsPrimaryModule() {
    return isPrimaryModule;
}

}

Sandeep Patel
fonte
0

Além de todas as respostas anteriores, uma possível correção para este problema em um projeto de grande escala, se você usar um objeto de valor para suas classes não definir o atributo id na classe VO Transformer.

George Michael
fonte
0

apenas confirme a transação atual.

currentSession.getTransaction().commit();

agora você pode começar outra transação e fazer qualquer coisa na entidade

Mahdi
fonte