cbm*_*eks 8 java spring persistence jpa
所以,我有这个场景,我需要获取标题记录,删除它的详细信息,然后以不同的方式重新创建详细信息.更新细节会非常麻烦.
我基本上有:
@Transactional
public void create(Integer id, List<Integer> customerIDs) {
Header header = headerService.findOne(id);
// header is found, has multiple details
// Remove the details
for(Detail detail : header.getDetails()) {
header.getDetails().remove(detail);
}
// Iterate through list of ID's and create Detail with other objects
for(Integer id : customerIDs) {
Customer customer = customerService.findOne(id);
Detail detail = new Detail();
detail.setCustomer(customer);
header.getDetails().add(detail);
}
headerService.save(header);
}
Run Code Online (Sandbox Code Playgroud)
现在,数据库具有如下约束:
Header
=================================
ID, other columns...
Detail
=================================
ID, HEADER_ID, CUSTOMER_ID
Customer
=================================
ID, other columns...
Constraint: Details must be unique by HEADER_ID and CUSTOMER_ID so:
Detail (VALID)
=================================
1, 123, 10
2, 123, 12
Detail (IN-VALID)
=================================
1, 123, 10
1, 123, 10
Run Code Online (Sandbox Code Playgroud)
好吧,当我运行它并传入2,3,20等客户时,Detail
只要之前没有任何记录,它就会创建所有记录.
如果我再次运行它,传入不同的客户列表,我希望ALL
先删除详细信息,然后再删除NEW
要创建的详细信息列表.
但正在发生的事情是,在创建之前,删除似乎并未得到尊重.因为错误是重复键约束.重复键是上面的"IN-VALID"场景.
如果我用一堆详细信息手动填充数据库并注释掉该CREATE details
部分(仅运行删除),那么记录就会被删除.所以删除工作.创作有效.只是两者都不能一起工作.
我可以提供更多代码.我正在使用Spring Data JPA
.
谢谢
UPDATE
我的实体基本上注释了以下内容:
@Entity
@Table
public class Header {
...
@OneToMany(mappedBy = "header", orphanRemoval = true, cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
private Set<Detail> Details = new HashSet<>();
...
}
@Entity
@Table
public class Detail {
...
@ManyToOne(optional = false)
@JoinColumn(name = "HEADER_ID", referencedColumnName = "ID", nullable = false)
private Header header;
...
}
Run Code Online (Sandbox Code Playgroud)
更新2
@Klaus Groenbaek
实际上,我最初并没有提到这个,但我是第一次这样做.另外,我使用Cascading.ALL,我假设包括PERSIST.
仅用于测试,我已将代码更新为以下内容:
@Transactional
public void create(Integer id, List<Integer> customerIDs) {
Header header = headerService.findOne(id);
// Remove the details
detailRepository.delete(header.getDetails()); // Does not work
// I've also tried this:
for(Detail detail : header.getDetails()) {
detailRepository.delete(detail);
}
// Iterate through list of ID's and create Detail with other objects
for(Integer id : customerIDs) {
Customer customer = customerService.findOne(id);
Detail detail = new Detail();
detail.setCustomer(customer);
detail.setHeader(header);
detailRepository.save(detail)
}
}
Run Code Online (Sandbox Code Playgroud)
再次......我想重申......如果我之后没有立即创建删除将会工作.如果我之前没有删除,则创建将工作.但是,如果由于数据库中的重复键约束错误它们在一起,它们都不会起作用.
我尝试了相同的场景WITH和WITHOUT级联删除.
Kla*_*aek 14
坚持你的帽子,因为这是一个相当长的解释,但是当我查看你的代码时,看起来你错过了关于JPA如何工作的几个关键概念.
首先,将实体添加到集合或从集合中删除实体并不意味着数据库中将发生相同的操作,除非使用cascadeding或orphanRemoval传播持久性操作.
对于要添加到数据库的实体,您必须EntityManager.persist()
直接调用,或通过级联持久调用.这基本上就是里面发生的事情JPARepository.save()
如果要删除实体,则必须EntityManager.remove()
直接调用或通过级联操作或通过JpaRepository.delete()
.
如果您有一个托管实体(一个加载到持久化上下文中),并且您在事务中修改了一个基本字段(非实体,非集合),那么当事务提交时,此更改将写入数据库,甚至如果你没有打电话persist/save
.持久化上下文保留每个加载实体的内部副本,并且当事务提交时,它循环通过内部副本并与当前状态进行比较,并且任何基本字段更改都会触发更新查询.
如果您已将新实体(A)添加到另一个实体(B)上的集合中,但尚未在A上调用持久性,则A将不会保存到数据库中.如果在B上调用persist,则会发生以下两种情况之一,如果持久化操作是级联的,则A也将保存到数据库中.如果persist不是级联的,则会出现错误,因为托管实体引用了一个非托管实体,它在EclipseLink上发出此错误:"在同步期间,通过未标记为级联PERSIST的关系找到了新对象".级联持久化是有道理的,因为你经常创建一个父实体,它同时是它的子节点.
如果要从另一个实体B上的集合中删除实体A,则不能依赖级联,因为您没有删除B.相反,您必须直接在A上调用remove,将其从B上的集合中删除不有任何影响,因为没有在EntityManager上调用持久性操作.您也可以使用orphanRemoval来触发删除,但我建议您在使用此功能时要小心,特别是因为您似乎缺少有关持久性操作如何工作的一些基本知识.
通常,考虑持久性操作以及必须应用于哪个实体会有所帮助.如果我写了代码,代码将如何显示.
@Transactional
public void create(Integer id, List<Integer> customerIDs) {
Header header = headerService.findOne(id);
// header is found, has multiple details
// Remove the details
for(Detail detail : header.getDetails()) {
em.remove(detail);
}
// em.flush(); // In some case you need to flush, see comments below
// Iterate through list of ID's and create Detail with other objects
for(Integer id : customerIDs) {
Customer customer = customerService.findOne(id);
Detail detail = new Detail();
detail.setCustomer(customer);
detail.setHeader(header); // did this happen inside you service?
em.persist(detail);
}
}
Run Code Online (Sandbox Code Playgroud)
首先,没有理由坚持Header,它是一个托管实体,您修改的任何基本字段将在事务提交时更改.头恰好是对细节的实体,这意味着重要的是外键detail.setHeader(header);
和em.persist(details)
,因为你必须将所有的外交关系,并坚持任何新的Details
.同样,从Header中删除现有详细信息与Header无关,定义关系(外键)在Details中,因此从持久性上下文中删除详细信息是将其从数据库中删除的内容.您也可以使用orphanRemoval,但这需要为每个事务提供额外的逻辑,并且在我看来,如果每个执行操作都是显式的,则代码更容易阅读,这样您就不需要返回实体来读取注释.
最后:代码中的持久性操作序列不会转换为针对数据库执行的查询的顺序.Hibernate和EclipseLink都将首先插入新实体,然后删除现有实体.根据我的经验,这是"主键已经存在"的最常见原因.如果删除具有特定主键的实体,然后添加具有相同主键的新实体,则首先进行插入,并导致密钥违规.这可以通过告诉JPA将当前的Persistence状态刷新到数据库来解决.em.flush()
将删除查询推送到数据库,这样您就可以插入另一行与您删除的主键相同的主键.
这是很多信息,请告诉我是否有任何你不理解的事情,或者需要我澄清一下.
Mar*_*den 10
原因由@klaus-groenbaek 描述,但我在解决它时注意到一些有趣的事情。
在使用 Spring 时,JpaRepository
我无法在使用派生方法时使其工作。
所以以下不起作用:
void deleteByChannelId(Long channelId);
Run Code Online (Sandbox Code Playgroud)
但是指定显式 ( Modifying
)Query
使其正常工作,因此以下工作:
@Modifying
@Query("delete from ClientConfigValue v where v.channelId = :channelId")
void deleteByChannelId(@Param("channelId") Long channelId);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,语句以正确的顺序提交/持久化。
归档时间: |
|
查看次数: |
5721 次 |
最近记录: |