Files

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"