是否可以禁止使用 CascadeType 或其他注释替换实体上的现有子实体?

Ιs it possible to disallow replacement of existing children entities on an entity using CascadeType or another annotation?

你好,我正在尝试制作一个 API,它使用这样的实体:

@Entity 
@NoArgsConstructor @Getter @Setter
class ParentEntity {
   @OneToOne(cascade= {CascadeType.ALL}, orphanRemoval = true)
   private ChildEntity child;
}

假设我在 http://localhost:8080/api/v1/parent-entities/1

有一个父实体

我想做的是能够通过以下方式 PUT(更新):

我希望能够即时创建新的子实体,因此需要允许(我想我需要 CascadeType.PERSIST):

PUT parent-entities/1 
{ "child": { "name": "new child" }}

需要允许通过父级更新子级(我想我需要 CascadeType.MERGE 为此)

PUT parent-entities/1
{ "child": { "id": 1, "name": "update existing child name" }}

我还需要能够删除一个项目并将其从数据库中删除(我想我需要 CascadeType.REMOVE 以及 orphanRemoval=true):

PUT parent-entities/1
{ "child": null} should remove child with id=1 from database

我需要帮助的是不允许允许这个:

PUT parent-entities/1
{ "child": { "id": 2, "name": "other existing child" }}

答案可能与 CascadeType.REFRESHCascadeType.DETACHCascadeType.MERGE

有关

根据我的测试,我无法让它以某种方式工作。我实际上设法销毁了许多现有的 childEntities,使它们的名称等于 null。我最好的猜测是 CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE

Motiviation for the above: Frontend app was sending data for a specific child entity but with wrong id - this had the result to mutate the wrong entity that was completely unrelated and on top of that it was attached to the wrong parent. I still need the CascadeType.MERGE though.

感谢任何帮助!

是的,这可以只用注释来完成。该解决方案最终将向特定列添加可更新字段。

您可以通过删除 CascadeType.MERGE 来阻止更新,但是您也将无法更新 ChildEntity.name... 如果您想阻止特定字段的可更新性,请尝试以下操作。

@Entity
@Getter
@Setter
@NoArgsConstructor
public class ParentEntity implements Serializable {

    private static final long serialVersionUID = -3126773862335903987L;

    @Id
    @GeneratedValue
    @Column(updatable = false)
    private Long id;

    **@JoinColumn(updatable = false)**
    @OneToOne(cascade= CascadeType.ALL, orphanRemoval = true)
    private ChildEntity child;
}
@Entity
@Getter
@Setter
@NoArgsConstructor
public class ChildEntity implements Serializable {

    private static final long serialVersionUID = -2903838577072685774L;

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @JsonIgnore
    @OneToOne(mappedBy = "child")
    private ParentEntity parent;
}
@PutMapping
private ResponseEntity<ParentEntity> update(
        @RequestBody final ParentEntity entity) {
    return ResponseEntity.of(Optional.of(this.repo.saveAndFlush(entity)));
}

为了演示,请在 application.properties

中启用 show-sql
spring.jpa.show-sql=true

创建初始实体

PUT /parent-entity
{
    "child": {
        "name": "alpha"
    }
}

Hibernate: insert into ChildEntity (name, id) values (?, ?)
Hibernate: insert into ParentEntity (child_id, id) values (?, ?)


PARENT_ID   CHILD_ID    CHILD   NAME
1           2           2       alpha

更新名称

PUT /parent-entity
{
    "id": 1,
    "child": {
        "id": 2,
        "name": "beta"
    }
}

Hibernate: update ChildEntity set name=? where id=?


PARENT_ID   CHILD_ID    CHILD   NAME
1           2           2       beta

尝试更新 ID

PUT /parent-entity
{
    "id": 1,
    "child": {
        "id": 3,
        "name": "gamma"
    }
}

{
    "timestamp": "2020-01-23T11:08:02.056+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "could not execute statement; SQL [n/a]; constraint [\"FKGD5S6Q2V5J8MX33JL99EYCY5R: PUBLIC.PARENTENTITY FOREIGN KEY(CHILD_ID) REFERENCES PUBLIC.CHILDENTITY(ID) (2)\"; SQL statement:\ndelete from ChildEntity where id=? [23503-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
    "path": "/parent-entities"
}


PARENT_ID   CHILD_ID    CHILD   NAME
1           2           2       beta

同样,如果你想防止非引用列被更新,只需使用@Column(updatable = false)