Um objecto diferente com o mesmo identificador usando jpa dados da mola com hibernação

HKIT:

Fui verificar fontes diferentes mas nenhum resolver o meu problema, tais como: https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId

Spring + hibernação: um objecto diferente, com o mesmo valor do identificador já foi associado com a sessão

Meu caso: eu criei 2 classes, 1 repositório como abaixo:

@Entity
public class Parent{
  @Id
  public long pid;

  public String name;

  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
  public List<Child> children;
}

-------------------------------------------------------------------

@Entity
public class Child{
  @EmbeddedId
  public PK childPK = new PK();

  public String name;

  @ManyToOne
  @MapsId("parentPk")
  @JoinColumn(name = "foreignKeyFromParent")
  public Parent parent;

  @Embeddable
  @EqualsAndHashCode
  static class PK implements Serializable {
      public long parentPk;
      public long cid;
  }
}
------------------------------------------------------------------------

public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}

Onde pai e filho tem relação um para muitos. No meu método principal:

public static void main(String[] args) {
    @Autowired
    private ParentRepository parentRepository;

    Parent parent = new Parent();
    parent.pid = 1;
    parent.name = "Parent 1";

    Child child = new Child();
    List<Child> childList = new ArrayList<>();

    child.childPK.cid = 1;
    child.name = "Child 1";
    childList.add(child);

    parent.children= childList;

    parentRepository.save(parent);
    parentRepository.flush();
}


Quando eu executar o aplicativo pela primeira vez, os dados podem salvas com sucesso ao banco de dados. Mas se eu executá-lo novamente, ele dá erro "Exceção: org.springframework.dao.DataIntegrityViolationException: Um objeto diferente com o mesmo valor identificador já foi associado com a sessão".
Eu estava esperando se os dados é novo, ele irá atualizar meu banco de dados, se os dados é o mesmo, nada acontecerá. O que há de errado com meu código.

Se eu fiz estande pai sozinho (sem qualquer relação com a criança). Não vai dar qualquer erro, mesmo que eu volte a executar o aplicativo.

Editado: No entanto, se eu usar o abaixo implementação com chave primária simples na entidade filho, que vai funcionar como eu esperava. Posso voltar a executar o aplicativo sem erros. Eu também pode alterar o valor, como o child.name e ele vai refletir no banco de dados.

@Entity
public class Parent{
   @Id
   public long pid;

   public String name;

   @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
   public List<Child> children;
}

-------------------------------------------------------------------

@Entity
public class Child{
   @Id
   public long cid;


   public String name;

   @ManyToOne
   @JoinColumn(name = "foreignKeyFromParent")
   public Parent parent;

}
------------------------------------------------------------------------

public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}

-------------------------------------------------------------------------
public static void main(String[] args) {
   @Autowired
   private ParentRepository parentRepository;

   Parent parent = new Parent();
   parent.pid = 1;
   parent.name = "Parent 1";

   Child child = new Child();
   List<Child> childList = new ArrayList<>();

   child.cid = 1;
   child.name = "Child 1";
   childList.add(child);

   parent.children= childList;

   parentRepository.save(parent);
   parentRepository.flush();
}
Lesiak:

Antes completa explicação uma pequena nota: tentativa de código postal que realmente compila e funciona como anunciado.

  • Seu main()não compilar,
  • você não há configurar completa relação entre pai e filho.
  • Também tentar transações explicitamente demarcar no exemplo postado.

Como seu código funciona

Você está chamando save em um repositório. Debaixo, este método chama entityManager.merge()como você definir um ID de si mesmo. Mesclar chama SQL Select para verificar se o objeto está lá, e posteriormente chama inserção SQL ou atualização para o objeto. (As sugestões que poupam com o objeto com ID que existe no db estão errados)

  • Na primeira execução, o objeto não está lá.

    • inserir pai
    • merge é em cascata e você inserir criança (vamos chamá-lo childA)
  • Na segunda corrida

    • intercalação selecciona-mãe (com childA)
    • Nós comparar se novo pai já está em sessão. Isso é feito emSessionImpl.getEntityUsingInterceptor
    • pai é encontrado
    • mesclagem é em cascata para a criança
    • mais uma vez, vamos verificar se o objeto já está na sessão.
    • Agora, a diferença vem:
    • Dependendo de como você configurar a relação entre a criança e os pais, a criança pode ter uma PK incompleta (e confiar em preenchê-lo a partir da relação de pai anotado com @MapsId). Infelizmente, a entidade não é encontrada na sessão através do PK incompleta, mas depois, ao salvar, o PK está completo, e agora, você tem 2 objetos confilicting com a mesma chave.

Para resolvê-lo

Child child = new Child();
child.parent = parent;
child.childPK.cid = 1;
child.childPK.parentPk = 1;

Isso também explica por que o código funciona quando você altera o PK da criança para um longo - não há nenhuma maneira de estragar tudo e ter uma PK incompleta.

NOTA

A solução anterior torna confusão com órfãos.

Eu ainda acho que a solução original é melhor como os órfãos são removidos. Além disso, a adição de soution atualizado para solução original é uma atualização de valor. Removendo lista inteira e re-inseri-lo não é provável que um bom desempenho sob carga. Unfortunalely ele remove a lista na primeira fusão do pai, e re-adiciona-los no segundo merge do pai. (É por isso claro não é necessário)

Melhor ainda, basta encontrar a entidade pai e fazer as atualizações sobre ele (como outras respostas sugerem).

Ainda melhor, tentar olhar para a solução e adicionar / substituir apenas as crianças específicas do pai, não lookig no pai e seus filhos ollection. Este será provavelmente o mais alto desempenho.

Solução original

Proponho o seguinte (note que a substituição total da lista chilren não é permitido, uma vez que é um proxy de hibernação).

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Child> children = new ArrayList<>();
@SpringBootTest
public class ParentOrphanRepositoryTest {

    @Autowired
    private ParentOrphanRepository parentOrphanRepository;

    @Test
    public void testDoubleAdd() {
        addEntity();
        addEntity();
    }

    @Transactional
    public void addEntity() {
        Parent parent = new Parent();
        parent.pid = 1;
        parent.name = "Parent 1";

        parent = parentOrphanRepository.save(parent);


        Child child = new Child();
        List<Child> childList = new ArrayList<>();

        child.parent = parent;
        child.childPK.cid = 1;
        child.name = "Child 1";
        childList.add(child);

        // parent.children.clear(); Not needed.
        parent.children.addAll(childList);
        parentOrphanRepository.save(parent);
        parentOrphanRepository.flush();
    }
}

Acho que você gosta

Origin http://43.154.161.224:23101/article/api/json?id=214741&siteId=1
Recomendado
Clasificación