【问题标题】:Spring Data JPA - Delete child entities instead of setting to null on update?Spring Data JPA - 删除子实体而不是在更新时设置为空?
【发布时间】:2016-05-10 16:17:59
【问题描述】:

我有以下领域模型

users
----
id (PK)
name

orders
------
id (PK)
userid (PK)
name

Orders.userid 引用 User 中的 id 并且是复合主键的一部分。 (我需要主键中的用户 ID,因为它是一个分区键,在 MySQL 中我们无法在没有分区键的情况下创建主键)

关于使用 JPA 更新用户,如果我尝试使用清除关联的订单集合

User user = userRepository.getOne(id);
user.getOrders().clear();
userRepository.save(user);

JPA 首先尝试在关联的 Orders 上将 userid 设置为 null,然后删除该行。这失败了,因为 userid 是不可为空的并且是主键的一部分。有没有办法告诉 JPA 它只需要删除 Order 行而不先将 userid 列设置为 null?

更新

映射: 我的映射是这样的:

@Entity
@Table(name = "users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    public Long id;

    @Column(name="name")
    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name="userid", referencedColumnName = "id", insertable = true, updatable = true)
    @Fetch(FetchMode.JOIN)
    private Set<Order> orders;

}

@Entity
@Table(name = "orders")
@IdClass(Order.OrderPk.class)
public class Order implements Serializable {

    @Id
    @Column(name="id")
    private Long id;

    @Id
    @Column(name="userid")
    private Long userId;

    @Column(name="name")
    private String name;

    public static class OrderPk implements Serializable  {

        private Long id;

        private Long userId;
    }
}

你能告诉我映射会有什么变化吗?

更新:

也尝试了以下映射:

@Entity
@Table(name = "users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id", nullable = false)
    public Long id;

    @Column(name="name")
    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "orderPk.user")
    @Fetch(FetchMode.JOIN)
    private Set<Order> orders;
}

@Entity
@Table(name = "orders")
public class Order implements Serializable {

    @EmbeddedId
    private OrderPk orderPk;

    @Column(name="name")
    private String name;

    @Embeddable
    public static class OrderPk implements Serializable {

        @GeneratedValue
        @Column(name="id", insertable = false, updatable = false, nullable = false)
        private Long id;

        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name="userid", referencedColumnName = "id", insertable = false, updatable = false, nullable = false)
        private User user;
    }
}

在插入时,它抱怨说“为:类订单生成空 id” (也尝试过 insertable=true 和 updatable=true)

【问题讨论】:

  • 修复您的映射,除此之外,这确实是清除托管集合的一种糟糕方法。您不应允许从 User 对象外部修改集合。或者甚至可以完全从User 中删除订单,只在订单上留下User 字段并简单地创建一个查询。
  • @M.Deinum 感谢您的回复,我已经用映射更新了问题。您能告诉我可以进行哪些更改吗?
  • 本质上,我在问如何处理具有非空外键的子实体。插入也将尝试首先使用 NULL 外键值插入,然后尝试将其更新为父键。在这种情况下插入会失败,因为数据库不允许 NULL。
  • 订单上真的需要组合PK吗?至少将其设置为updatable=falsenullable=false
  • @M.Deinum,是的,我确实需要组合 PK,因为分区键需要成为 PK 的一部分。将其设置为 updateable=false 和 nullable=false 似乎无法解决问题。

标签: java mysql spring jpa spring-data


【解决方案1】:

这里的原因是关联是单向的,所以User是拥有方(因为它是唯一的一方)。

将关联设为双向,并将 Order 设为关联所有者。这样您就可以避免冗余更新(和not null 约束违规)。

【讨论】:

  • 一旦我删除复合键关联,我假设当我们使关系双向时会出现这种情况(因为连接键会重复两次),一切正常。但是,这意味着我不会完全映射到数据库。问题在于实体中有一个复合键。
  • 我已经用我尝试过的双向映射编辑了这个问题。它再次抱怨说不能在 Order 上设置 null id。
  • 你确定这是用外键映射嵌入式 id 的方法吗? This answer 可能会有所帮助。此外,如果您希望 Order 成为关联的所有者,它绝对应该是可插入和可更新的。
【解决方案2】:

以下实体映射对我有用。为简洁起见,删除了所有 getter 和 setter。

用户类

@Entity
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @OneToMany(mappedBy = "userId", cascade = { CascadeType.ALL }, orphanRemoval = true)
    private List<Orders> orders;

    public void removeOrder(Orders orders) {
        orders.setUserId(null);
        this.orders.remove(orders);
    }

    public void removeAllOrders() {
        orders.forEach(order -> order.setUserId(null));
        this.orders.clear();
    }
}

订单类

@Entity
@IdClass(Orders.OrderPk.class)
public class Orders {

    @Id
    private Integer id;

    @Id
    @ManyToOne
    @JoinColumn(name = "userid")
    private User userId;

    private String name;

    public static class OrderPk implements Serializable {
        private Integer id;
        private Integer userId;
    }
}

要删除的代码

@Override
@Transactional
public void run(String... args) throws Exception {
    User user = userRepository.findOne(1);
    user.removeAllOrders();
    userRepository.save(user);
    System.out.println("Done");
}

Sql 生成

Hibernate: select user0_.id as id1_1_0_, user0_.name as name2_1_0_ from user user0_ where user0_.id=?
Hibernate: select orders0_.userid as userid2_1_0_, orders0_.id as id1_0_0_, orders0_.userid as userid2_0_0_, orders0_.id as id1_0_1_, orders0_.userid as userid2_0_1_, orders0_.name as name3_0_1_ from orders orders0_ where orders0_.userid=?
Done
Hibernate: delete from orders where id=? and userid=?
Hibernate: delete from orders where id=? and userid=?

【讨论】:

    【解决方案3】:

    您可以使用标识符保持连接,并确保删除不会尝试使用集合上的可更新和可插入注释将 null 写入集合 FK。

    @Entity
    @Table(name = "users")
    public class User implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name="id", nullable = false)
        public Long id;
    
        @Column(name="name")
        private String name;
    
        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch =     FetchType.EAGER)
        @JoinColumn(name = "userId", updatable = false, insertable = false)
        private Set<Order> orders;
    }
    
    @Entity
    @Table(name = "orders")
    public class Order implements Serializable {
    
        @Id
        @Column(name="id")
        private Long id;
    
        @Column(name="userid")
        private Long userId;
    
        @Column(name="name")
        private String name;
    }
    

    @JoinColumn(name = "userId", 可更新 = false, 可插入 = false)

    part表示子表不会更新,但可以删除

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-25
      • 2018-12-03
      • 2020-10-19
      • 1970-01-01
      • 2019-09-30
      相关资源
      最近更新 更多