我检查了不同的来源,但没有一个能解决我的问题,例如: https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId
我的情况:我创建了2个类,如下所示是1个存储库:
@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> {
}
父母与子女之间存在一对多关系。 在我的主要方法中:
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();
}
当我第一次运行该应用程序时,数据可以成功保存到数据库中。但是,如果我再次运行它,则会出现错误“异常:org.springframework.dao.DataIntegrityViolationException:具有相同标识符值的另一个对象已与该会话关联”。
我期望如果数据是新的,它将更新我的数据库,如果数据相同,什么也不会发生。我的代码有什么问题。
如果我让父母独立(与孩子没有任何关系)。即使我重新运行该应用程序,也不会出现任何错误。
编辑:但是,如果我在子实体中使用以下带有简单主键的实现,它将按预期工作。我可以重新运行该应用程序而不会出现错误。我还可以更改该值,例如child.name,它将反映在数据库中。
@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();
}
答案 0 :(得分:1)
好吧,parent.pid是您的数据库主键。您只能将一个记录集保存到ID = 1的数据库中。这是预期的行为。
也许让自己熟悉@GeneratedValue以避免自己设置ID。
答案 1 :(得分:0)
在进行全面解释之前,请注意以下几点:尝试发布实际上可以编译并按宣传方式工作的代码。
main()
无法编译,代码的工作方式
您正在调用存储库上的save。在下面,此方法将在您自己设置ID时调用entityManager.merge()
。合并调用SQL Select以验证对象是否存在,然后调用该对象的SQL插入或更新。 (使用db中存在的具有ID的对象保存的建议是错误的)
在第一次运行中,对象不存在。
childA
)第二次运行
childA
)SessionImpl.getEntityUsingInterceptor
@MapsId
的PK)。不幸的是,通过不完整的PK在会话中找不到该实体,但是稍后,保存时PK已完成,现在,您有2个具有相同密钥的冲突对象。要解决
Child child = new Child();
child.parent = parent;
child.childPK.cid = 1;
child.childPK.parentPk = 1;
这也解释了为什么将Child的PK更改为很长时代码可以工作-没有办法搞砸它并拥有不完整的PK。
注意
以上解决方案使孤儿陷入困境。
我仍然认为,如果将孤儿移走,原始的解决方案会更好。 另外,将更新的版本添加到原始解决方案中也是值得的。 在负载下,删除整个列表并重新插入它的效果不太好。 不幸的是,它会在父项的第一次合并中删除列表,并在父项的第二次合并中重新添加列表。 (这就是为什么不需要清除的原因)
更好的是,只需找到父实体并对其进行更新(如其他答案所示)。
更好的是,尝试查看解决方案,仅添加/替换父级的特定子级,而不是查看/查看父级及其子级的倾斜。这可能是最出色的表现。
原始解决方案
我提出以下建议(请注意,不允许完全替换儿童列表,因为它是休眠代理)。
@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();
}
}