简体   繁体   中英

Spring JPA OneToOne FK as PK Cascade.Remove

I've got two tables, b and a :

  • they have a one-to-one bidirectional relationship
  • a has a foreign key to b that defines this relationship
  • this foreign key is also considered as a primary key for a , and a JPA @ID
  • I want a cascade removal that deletes the related b when a is deleted
  • in MySQL, a 's b_id is NOT NULL

The problem is that when I delete my A object with JPA repository, I get a ConstraintViolationException on its foreign key. I would expect that both a and b rows are deleted (cleverly starting with a 's one).

How could I work around this knowing that I want to keep:

  • my DB schema the same
  • the cascade removal from a to b
  • the b id being the JPA @Id for a
CREATE TABLE `b` (
  `dbid` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`dbid`),
);

CREATE TABLE `a` (
  `b_id` int(11) NOT NULL,
  KEY `b_fk` (`b_id`),
  CONSTRAINT `b_fk` FOREIGN KEY (`b_id`) REFERENCES `b` (`dbid`),
);

@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE)
    @PrimaryKeyJoinColumn
    private B b;
}
@Entity
@Table(name = "b")
public class B {

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

    @OneToOne(mappedBy = "b")
    private A a;
}

[EDIT] After all discussions in answer comments and re-reading my question, the proposals with orphanRemoval indeed are in scope and work.

If you want to delete object of B , whenever the associated A is deleted (it's the fourt point of your wishlist:

I want a cascade removal that deletes the related b when a is deleted

then you need to change your mapping in A to:

@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
@PrimaryKeyJoinColumn
private B b;

In terms of just the MySQL side of your implementation , the records in table B have no 'knowledge' of any record in table A. In the database the relationship is unidirectional

The native cascade functionality exists to prevent foreign key errors, by telling the DB what to do when deleting a record would leave a foreign key pointing nowhere. Deleting a table A record would not cause a foreign key error in any table B records, so any native cascade functionality would not be triggered

To reiterate; You cannot keep the schema the same, and the cascade removal from a to b , because you don't actually have the cascade removal from a to b

You also mentioned in the comments that some table B records can exist without a table A records which isn't in the original question

To obtain the automatic deletion of table B records you describe, you have a few options with regards to the DB:

  1. Swap the relation over - Remove the current foreign key and add a nullable foreign key column in table B that references the primary key of table A. You can then put a cascade delete on this foreign key. Keep the new column null for the table B records that do not 'belong' to a table A record. You could also add a unique index to this column to secure a one to one relationship
  2. Add a DB trigger - On deletion of a table A record, add a DB trigger that removes the referenced table B record
  3. Add a DB procedure - Add a procedure that deletes a table A record and then the referenced table B record in turn, probably within a transaction. Going forwards, only delete table A records using the procedure
  4. Don't solve the problem at the DB level - Basically the same as option 3, but move the procedure logic out of the DB layer into the application logic

There may be something in JPA that solves your dilemma out of the box, but under the hood it will be doing one of the above (not option 1 and probably option 4)

In order to achieve what you have asked, I have tweaked your tables as follows:

    CREATE TABLE b (
       dbid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
    );

    CREATE TABLE a ( 
       b_id int(11) NOT NULL PRIMARY KEY REFERENCES b(dbid) ON DELETE CASCADE
    );

CASCADE DELETE wasn't added in your DDL.

This will enable cascade delete. To delete the b record on deletion of a I made following changes in class A :

@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
    @PrimaryKeyJoinColumn
    private B b;
}

That worked well. I hope it helps.

Find link here to the working solution .

Can you try in class B to add the following

@OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;

In addition, if in the database you have only a foreign key "a has a foreign key to b" can you also make a foreign key from b to a as well.

try with below code -

@OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM