4

I have the following domain model

users
----
id (PK)
name

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

Orders.userid references id in User and is part of the composite primary key. (I need userid in the primary key since it is a partition key and in MySQL we cannot create the primary key without the partition key)

On using JPA to update User, if I try to clear the associated orders collection using

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

JPA first tries to set userid to null on the associated Orders and then delete the row. This fails since userid is non-nullable and part of the primary key. Is there a way to tell JPA that it simply needs to go an delete the Order rows without setting the userid column to null first?

UPDATE

Mapping: The mapping I have is something like this:

@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;
    }
}

Can you tell me what would be the change to the mapping?

UPDATE:

Tried the following mapping too:

@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;
    }
}

On insert, it complains saying "null id generated for:class Order" (Have also tried with insertable=true and updatable=true)

SKP
  • 49
  • 1
  • 1
  • 5
  • Fix your mapping, next to that this is really a bad way of clearing a managed collection. You shouldn't allow the collection be modified from outside the `User` object. Or maybe even delete the orders from the `User` altogether and only leave a `User` field on the order and simply create a query. – M. Deinum Feb 02 '16 at 07:10
  • @M.Deinum Thanks for the reply, I have updated the question with the mapping. Can you tell me what changes could be made? – SKP Feb 02 '16 at 11:45
  • Essentially, I am asking how to deal with child entities that have Non-Null foreign keys. Inserts would also try to first insert with a NULL foreign key value and then try to update it to the parents key. The insert in this case would fail since NULL would not be allowed by the database. – SKP Feb 02 '16 at 14:22
  • Do you really need a combined PK on the order? At least set it to `updatable=false` and `nullable=false`. – M. Deinum Feb 02 '16 at 14:25
  • @M.Deinum, yes, I do need a combined PK since the partitionkey needs to be part of the PK. Setting it to updatable=false and nullable=false does not seem to resolve the issue. – SKP Feb 03 '16 at 05:46

3 Answers3

4

You can keep the join using the identifier, and ensure the delete doesn't attempt to write null to the collection FK using the updatable and insertable annotations on the collection.

@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;
}

The

@JoinColumn(name = "userId", updatable = false, insertable = false)

part means the child table won't be updated, but can be deleted

Alfonzo
  • 86
  • 4
0

The cause here is that the association is uni-directional, so User is the owning side (because it's the only side).

Make the association bidirectional and make Order the association owner. That way you will avoid redundant updates (and not null constraint violations).

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • As soon as I remove the composite key association, which I assume will be the case when we make the relationship bidirectional (since the joinkey would otherwise be repeated twice), things work fine. However, this would mean I don't map exactly to the DB. The issue is with having a composite key in the entity. – SKP Feb 03 '16 at 05:48
  • I have edited the question with the bidirectional mapping I tried too. It again complains saying that a null id cannot be set on Order. – SKP Feb 03 '16 at 07:01
  • Are you sure that is the way to map embedded id with foreign key? [This answer](http://stackoverflow.com/a/31389630/4754790) could be helpful. Also it definitely should be insertable and updatable if you want `Order` to be the owner of the association. – Dragan Bozanovic Feb 03 '16 at 09:11
0

The following entity mapping works for me. Removed all getters and setters for brevity.

User Class

@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();
    }
}

Order Class

@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;
    }
}

Code to Remove

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

Sql generated

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=?
ArunM
  • 2,274
  • 3
  • 25
  • 47