Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
158 lines
12 KiB
Plaintext
158 lines
12 KiB
Plaintext
Associations and soft delete
|
|
|
|
######## H2 ########
|
|
database: H2Dialect / H2 2.4.240 (2025-09-22)
|
|
|
|
== A. soft-delete the parent of a @OneToMany (no cascade)
|
|
-- remove(author)
|
|
sql> select a1_0.id,a1_0.name from author a1_0 where a1_0.deleted=false and a1_0.id=?
|
|
sql> update author set deleted=true where id=? and deleted=false
|
|
books: [1 | The Dispossessed | 1, 2 | Earthsea | 1] authors: [1 | Le Guin | true]
|
|
-- load a book, touch its (soft-deleted) author
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
book.getAuthor() == null ? true
|
|
-- HQL: select b.title from Book b join b.author a (inner join)
|
|
sql> select b1_0.title from book b1_0 join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false
|
|
rows: []
|
|
-- HQL: select b.title from Book b left join b.author a
|
|
sql> select b1_0.title from book b1_0 where b1_0.deleted=false
|
|
rows: 2
|
|
|
|
== B. a NEW book can still point at the soft-deleted author
|
|
-- persist(new Book(.., getReference(Author, deletedId)))
|
|
sql> insert into book (author_id,title,deleted,id) values (?,?,false,default)
|
|
books: [1 | The Dispossessed | 1, 2 | Earthsea | 1, 3 | Orphan | 1]
|
|
|
|
== C. soft-delete a child, then load the live parent's collection
|
|
-- author.getBooks().size()
|
|
sql> select a1_0.id,a1_0.name from author a1_0 where a1_0.deleted=false and a1_0.id=?
|
|
sql> select b1_0.author_id,b1_0.id,b1_0.title from book b1_0 where b1_0.author_id=? and b1_0.deleted=false
|
|
books seen through the collection: 1
|
|
|
|
== D. plain @ManyToMany to a soft-deleted Tag
|
|
DDL: [create table book_tag (deleted boolean not null, Book_id bigint not null, tags_id bigint not null, primary key (Book_id, tags_id));]
|
|
join table rows (book_id | tags_id | deleted): [6 | 1 | false, 6 | 2 | false]
|
|
-- book.getTags().size()
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> select t1_0.Book_id,t1_1.id,t1_1.label from book_tag t1_0 join tag t1_1 on t1_1.id=t1_0.tags_id where t1_0.Book_id=? and t1_1.deleted=false and t1_0.deleted=false
|
|
tags seen through the collection: 1
|
|
-- remove(book): what happens to its join-table rows?
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> update book_tag set deleted=true where Book_id=? and deleted=false
|
|
sql> update book set deleted=true where id=? and deleted=false
|
|
join table rows (book_id | tags_id | deleted): [6 | 1 | true, 6 | 2 | true]
|
|
-- restore ONLY the book row (update book set deleted = false), then book.getTags().size()
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> select t1_0.Book_id,t1_1.id,t1_1.label from book_tag t1_0 join tag t1_1 on t1_1.id=t1_0.tags_id where t1_0.Book_id=? and t1_1.deleted=false and t1_0.deleted=false
|
|
tags seen: 0 (the book_tag rows are still deleted = true)
|
|
after also restoring book_tag: tags seen = 1
|
|
|
|
== E. @SoftDelete on the COLLECTION (join-table rows)
|
|
DDL: [create table reader_subscription (deleted boolean not null, Reader_id bigint not null, subscriptions_id bigint not null, primary key (Reader_id, subscriptions_id));, comment on column reader_subscription.deleted is 'Soft-delete indicator';, alter table if exists reader_subscription add constraint FK8mnw2gfmlf0orqbofu5tgdq7p foreign key (subscriptions_id) references tag;, alter table if exists reader_subscription add constraint FKcmo9696q8cos9fjs9hgewj9f2 foreign key (Reader_id) references reader;]
|
|
join rows before: [1 | 3 | false]
|
|
-- reader.getSubscriptions().clear()
|
|
sql> select r1_0.id,r1_0.name from reader r1_0 where r1_0.id=?
|
|
sql> select s1_0.Reader_id,s1_1.id,s1_1.label from reader_subscription s1_0 join tag s1_1 on s1_1.id=s1_0.subscriptions_id where s1_0.Reader_id=? and s1_1.deleted=false and s1_0.deleted=false
|
|
sql> update reader_subscription set deleted=true where Reader_id=? and deleted=false
|
|
join rows after : [1 | 3 | true]
|
|
-- subscribe to the same tag again
|
|
sql> select r1_0.id,r1_0.name from reader r1_0 where r1_0.id=?
|
|
sql> select t1_0.id,t1_0.label from tag t1_0 where t1_0.deleted=false and t1_0.id=?
|
|
sql> select s1_0.Reader_id,s1_1.id,s1_1.label from reader_subscription s1_0 join tag s1_1 on s1_1.id=s1_0.subscriptions_id where s1_0.Reader_id=? and s1_1.deleted=false and s1_0.deleted=false
|
|
sql> insert into reader_subscription (Reader_id,subscriptions_id,deleted) values (?,?,false)
|
|
result: ConstraintViolationException -> JdbcSQLIntegrityConstraintViolationException: SQLState 23505, Unique index or primary key violation: "PUBLIC.CONSTRAINT_F INDEX PUBLIC.PRIMARY_KEY_F ON PUBLIC.READER_SUBSCRIPTION(READER_ID, SUBSCRIPTIONS_ID) VALUES ( /* key:1 */ CAST(1 AS BIGINT), CAST(3 AS BIGINT))"; SQL statement:
|
|
join rows still: [1 | 3 | true]
|
|
|
|
== F. CascadeType.ALL + orphanRemoval on a soft-deleted parent
|
|
-- remove an item from shelf.items (orphanRemoval)
|
|
sql> select s1_0.id,s1_0.label from shelf s1_0 where s1_0.deleted=false and s1_0.id=?
|
|
sql> select i1_0.shelf_id,i1_0.id,i1_0.name from item i1_0 where i1_0.shelf_id=? and i1_0.deleted=false
|
|
sql> update item set deleted=true where id=? and deleted=false
|
|
-- remove(shelf)
|
|
sql> select s1_0.id,s1_0.label from shelf s1_0 where s1_0.deleted=false and s1_0.id=?
|
|
sql> select i1_0.shelf_id,i1_0.id,i1_0.name from item i1_0 where i1_0.shelf_id=? and i1_0.deleted=false
|
|
sql> update item set deleted=true where id=? and deleted=false
|
|
sql> update shelf set deleted=true where id=? and deleted=false
|
|
items: [1 | i1 | true, 2 | i2 | true] shelves: [1 | true]
|
|
|
|
== G. purging a soft-deleted parent that still has children
|
|
physical DELETE of the soft-deleted author: ConstraintViolationException -> JdbcSQLIntegrityConstraintViolationException: SQLState 23503, Referential integrity constraint violation: "FKKLNRV3WELER2FTKWEEWLKY958: PUBLIC.BOOK FOREIGN KEY(AUTHOR_ID) REFERENCES PUBLIC.AUTHOR(ID) (CAST(1 AS BIGINT))"; SQL statement:
|
|
|
|
######## POSTGRES ########
|
|
database: PostgreSQLDialect / PostgreSQL 17.11
|
|
|
|
== A. soft-delete the parent of a @OneToMany (no cascade)
|
|
-- remove(author)
|
|
sql> select a1_0.id,a1_0.name from author a1_0 where a1_0.deleted=false and a1_0.id=?
|
|
sql> update author set deleted=true where id=? and deleted=false
|
|
books: [1 | The Dispossessed | 1, 2 | Earthsea | 1] authors: [1 | Le Guin | true]
|
|
-- load a book, touch its (soft-deleted) author
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
book.getAuthor() == null ? true
|
|
-- HQL: select b.title from Book b join b.author a (inner join)
|
|
sql> select b1_0.title from book b1_0 join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false
|
|
rows: []
|
|
-- HQL: select b.title from Book b left join b.author a
|
|
sql> select b1_0.title from book b1_0 where b1_0.deleted=false
|
|
rows: 2
|
|
|
|
== B. a NEW book can still point at the soft-deleted author
|
|
-- persist(new Book(.., getReference(Author, deletedId)))
|
|
sql> insert into book (author_id,title,deleted) values (?,?,false)
|
|
books: [1 | The Dispossessed | 1, 2 | Earthsea | 1, 3 | Orphan | 1]
|
|
|
|
== C. soft-delete a child, then load the live parent's collection
|
|
-- author.getBooks().size()
|
|
sql> select a1_0.id,a1_0.name from author a1_0 where a1_0.deleted=false and a1_0.id=?
|
|
sql> select b1_0.author_id,b1_0.id,b1_0.title from book b1_0 where b1_0.author_id=? and b1_0.deleted=false
|
|
books seen through the collection: 1
|
|
|
|
== D. plain @ManyToMany to a soft-deleted Tag
|
|
DDL: [create table book_tag (deleted boolean not null, Book_id bigint not null, tags_id bigint not null, primary key (Book_id, tags_id));]
|
|
join table rows (book_id | tags_id | deleted): [6 | 1 | false, 6 | 2 | false]
|
|
-- book.getTags().size()
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> select t1_0.Book_id,t1_1.id,t1_1.label from book_tag t1_0 join tag t1_1 on t1_1.id=t1_0.tags_id where t1_0.Book_id=? and t1_1.deleted=false and t1_0.deleted=false
|
|
tags seen through the collection: 1
|
|
-- remove(book): what happens to its join-table rows?
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> update book_tag set deleted=true where Book_id=? and deleted=false
|
|
sql> update book set deleted=true where id=? and deleted=false
|
|
join table rows (book_id | tags_id | deleted): [6 | 1 | true, 6 | 2 | true]
|
|
-- restore ONLY the book row (update book set deleted = false), then book.getTags().size()
|
|
sql> select b1_0.id,a1_0.id,a1_0.name,b1_0.title from book b1_0 left join author a1_0 on a1_0.id=b1_0.author_id and a1_0.deleted=false where b1_0.deleted=false and b1_0.id=?
|
|
sql> select t1_0.Book_id,t1_1.id,t1_1.label from book_tag t1_0 join tag t1_1 on t1_1.id=t1_0.tags_id where t1_0.Book_id=? and t1_1.deleted=false and t1_0.deleted=false
|
|
tags seen: 0 (the book_tag rows are still deleted = true)
|
|
after also restoring book_tag: tags seen = 1
|
|
|
|
== E. @SoftDelete on the COLLECTION (join-table rows)
|
|
DDL: [create table reader_subscription (deleted boolean not null, Reader_id bigint not null, subscriptions_id bigint not null, primary key (Reader_id, subscriptions_id));, comment on column reader_subscription.deleted is 'Soft-delete indicator';, alter table if exists reader_subscription add constraint FK8mnw2gfmlf0orqbofu5tgdq7p foreign key (subscriptions_id) references tag;, alter table if exists reader_subscription add constraint FKcmo9696q8cos9fjs9hgewj9f2 foreign key (Reader_id) references reader;]
|
|
join rows before: [1 | 3 | false]
|
|
-- reader.getSubscriptions().clear()
|
|
sql> select r1_0.id,r1_0.name from reader r1_0 where r1_0.id=?
|
|
sql> select s1_0.Reader_id,s1_1.id,s1_1.label from reader_subscription s1_0 join tag s1_1 on s1_1.id=s1_0.subscriptions_id where s1_0.Reader_id=? and s1_1.deleted=false and s1_0.deleted=false
|
|
sql> update reader_subscription set deleted=true where Reader_id=? and deleted=false
|
|
join rows after : [1 | 3 | true]
|
|
-- subscribe to the same tag again
|
|
sql> select r1_0.id,r1_0.name from reader r1_0 where r1_0.id=?
|
|
sql> select t1_0.id,t1_0.label from tag t1_0 where t1_0.deleted=false and t1_0.id=?
|
|
sql> select s1_0.Reader_id,s1_1.id,s1_1.label from reader_subscription s1_0 join tag s1_1 on s1_1.id=s1_0.subscriptions_id where s1_0.Reader_id=? and s1_1.deleted=false and s1_0.deleted=false
|
|
sql> insert into reader_subscription (Reader_id,subscriptions_id,deleted) values (?,?,false)
|
|
result: ConstraintViolationException -> PSQLException: SQLState 23505, ERROR: duplicate key value violates unique constraint "reader_subscription_pkey"
|
|
join rows still: [1 | 3 | true]
|
|
|
|
== F. CascadeType.ALL + orphanRemoval on a soft-deleted parent
|
|
-- remove an item from shelf.items (orphanRemoval)
|
|
sql> select s1_0.id,s1_0.label from shelf s1_0 where s1_0.deleted=false and s1_0.id=?
|
|
sql> select i1_0.shelf_id,i1_0.id,i1_0.name from item i1_0 where i1_0.shelf_id=? and i1_0.deleted=false
|
|
sql> update item set deleted=true where id=? and deleted=false
|
|
-- remove(shelf)
|
|
sql> select s1_0.id,s1_0.label from shelf s1_0 where s1_0.deleted=false and s1_0.id=?
|
|
sql> select i1_0.shelf_id,i1_0.id,i1_0.name from item i1_0 where i1_0.shelf_id=? and i1_0.deleted=false
|
|
sql> update item set deleted=true where id=? and deleted=false
|
|
sql> update shelf set deleted=true where id=? and deleted=false
|
|
items: [1 | i1 | true, 2 | i2 | true] shelves: [1 | true]
|
|
|
|
== G. purging a soft-deleted parent that still has children
|
|
physical DELETE of the soft-deleted author: ConstraintViolationException -> PSQLException: SQLState 23503, ERROR: update or delete on table "author" violates foreign key constraint "fkklnrv3weler2ftkweewlky958" on table "book"
|