Search code examples
javajpaone-to-manyopenjpa

Why is OpenJPA calling UPDATE before DELETE?


I'm trying to delete a parent entity that has a one-to-many parent-child relationship using entityManager.remove(parent). However, from the SQL log I see OpenJPA calling UPDATE first and trying to set null for a NOT NULL field.

executing prepstmnt 773394836 UPDATE child SET parent_id = ? WHERE parent_id = ? [params=(null) null, (long) 16]
executing prepstmnt 1127292223 DELETE FROM parent WHERE id = ? [params=(long) 16]
executing prepstmnt 1297966852 DELETE FROM child WHERE lookup_id = ? AND parent_id = ? [params=(int) 1, (long) 16]

How do I configure my classes to prevent this first UPDATE call?

My classes:

@Entity
@Table(name = "parent")
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,
        orphanRemoval = true)
    @JoinColumn(name = "parent_id")
    private List<Child> children;

    // getters, etc.
}

@Entity
@Table(name = "child")
@IdClass(ChildPrimaryKey.class)
public class Child {

    @Id
    @Column(name = "parent_id", nullable = false, updatable = false)
    private long parentId;

    @Id
    @Column(name = "lookup_id")
    private int lookupId;

    @ManyToOne
    @JoinColumn(name = "parent_id", referencedColumnName = "id",
        nullable = false, insertable = false, updatable = false)
    private Parent parent;

    // getters, etc.
}

My DB tables (in MySQL):

CREATE  TABLE IF NOT EXISTS `parent` (
  `id` BIGINT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(50) NOT NULL ,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

CREATE  TABLE IF NOT EXISTS `child` (
  `lookup_id` INT NOT NULL ,
  `parent_id` BIGINT NOT NULL ,
  PRIMARY KEY (`parent_id`, `lookup_id`) ,
  INDEX `fk_library_code_idx` (`parent_id` ASC) ,
  CONSTRAINT `fk_library_code`
    FOREIGN KEY (`parent_id` )
    REFERENCES `parent` (`id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = InnoDB;

Solution

  • I'm not exactly sure what is causing JPA to decide to execute those queries, however, I do notice a couple of issues with your mappings.

    First, you're not specifying the mappedBy element on your @OneToMany annotation (i.e. you're not telling OpenJPA that Child is the owner of the relationship). This would be my first guess as to why you're seeing this behavior. From the API specification:

    If the relationship is bidirectional, the mappedBy element must be used to specify the relationship field or property of the entity that is the owner of the relationship.

    To fix this, change your Parent's @OneToMany to include the mappedBy element and drop the @JoinColumn:

      @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, 
                 fetch = FetchType.EAGER, orphanRemoval = true)
      private List<Child> children;
    

    Additionally, I've noticed you are trying to use Parent's ID as part of Child's ID and have two fields on Child mapping to the same PK column (parent and parentId). The preferred way to handle this is with @MapsId:

      @Id
      private long parentId;
    
      // ...
    
      @MapsId("parentId")
      @ManyToOne
      @JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false)
      private Parent parent;