我有一个springboot项目,我使用PostgreSQL和Hibernate。
我有几个实体,其中两个如下:
公司实体:
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Getter
@Setter
public class Company extends BaseDatedEntity {
....... fields
@ManyToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST}
)
@JoinTable(
name = "company_interest",
joinColumns = {@JoinColumn(name = "company_id")},
inverseJoinColumns = {@JoinColumn(name = "interest_id")}
)
private List<Interest> interests = new ArrayList<>();
}
利益主体:
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Getter
@Setter
public class Interest extends BaseDatedEntity {
@Column(unique = true)
private String name;
public boolean equals(Interest interest){
return this.name.equals(interest.getName());
}
}
当我发布一个新的公司,它也持续的兴趣。这是我们想要的和预期的结果。如果我用删除请求删除指定的公司,我的公司信息将沿着company_interest连接表中的关系一起从数据库中删除。在利息表中,利息记录仍然存在,因为这些记录与公司本身无关。这也是意料之中的,也是可以的。
但是当我用数据库中已经记录的兴趣再次添加一个新公司时,我得到了传递给持久化的分离实体异常。
1-我想添加公司,即使它的兴趣已经在数据库中,只需在连接表中连接它。
2-如果兴趣不在数据库中,则将其保存在兴趣表中。
整个异常日志:14:44:50.427 [http-nio-8081-exec-4] WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolved [org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.aedesium.domain.entity.company.Interest; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.aedesium.domain.entity.company.Interest]
我相信这有一个简单的解决办法。但我想不通。
已经谢谢你了
2条答案
按热度按时间omtl5h9j1#
看起来您在处理存储库操作的服务方法上缺少了
@Transactional
注解。因为fetch类型被设置为lazy,所以会话需要打开,以便ORM知道您正在尝试访问的其他实体。所有实体都成功保存,因为您已经在父实体上设置了级联类型,因此它会将该操作“级联”到所有子实体。在那一刻,你没有获取任何东西,所以之后你不会遇到任何错误。
ruoxqz4g2#
通过在引用现有Interest示例的新Company上调用persist(或Spring的保存),由于使用了cascade persist选项,您实际上是在这些Interest示例上调用persist。JPA不允许在分离的(现有的)实体上调用persist,并要求提供者抛出一个异常来指示存在问题。
您需要确定要如何处理Interest示例中的数据。
1.如果您希望将数据合并到数据库中,您可能应该在关系上使用CascadeType.MERGE类型,然后切换到在公司示例上使用直接的AlternityManager.merge。JPA仍然会插入Company示例,但会级联合并操作,这样就可以找到您的Interest示例并将数据合并到数据库中。
1.如果您不希望过时的Interest数据覆盖数据库中的数据,并且只希望设置数据库关系,请不要使用cascadeType.Persist. JPA不会持久化Interest示例,并且只使用这些示例中的身份来设置DB中的关系。
但是请注意,您可能需要不同的语义,这取决于您的应用程序可能为Interest数据发送的内容。最佳实践(对我来说)是检查每个用例的关系并直接处理示例。在某些情况下,这可能意味着迭代通过Company传入的Interest数据,在需要时调用persist(如果没有ID),或者阅读它们并有选择地将传入副本中的部分值合并到托管示例,然后将Company中的托管示例设置为要保存。