Fui verificar fontes diferentes mas nenhum resolver o meu problema, tais como: https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId
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();
}
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 em
SessionImpl.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.
- intercalação selecciona-mãe (com
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();
}
}