DDL and generated SQL: DELETED vs ACTIVE vs TIMESTAMP vs @SQLDelete ######## H2 ######## database: H2Dialect / H2 2.4.240 (2025-09-22) == DDL Hibernate generates create table article_active (active boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_deleted (deleted boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_manual (deleted boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_stamped (deleted_at timestamp(6) with time zone, id bigint generated by default as identity, title varchar(255), primary key (id)); == SoftDeleteType.DELETED (default) -- insert sql> insert into article_deleted (title,deleted,id) values (?,false,default) -- remove(first) sql> select ad1_0.id,ad1_0.title from article_deleted ad1_0 where ad1_0.deleted=false and ad1_0.id=? sql> update article_deleted set deleted=true where id=? and deleted=false raw rows : [1 | first | true, 2 | second | false] -- find(first) after delete -> null sql> select ad1_0.id,ad1_0.title from article_deleted ad1_0 where ad1_0.deleted=false and ad1_0.id=? -- HQL: select count(a) from ArticleDeleted a sql> select count(ad1_0.id) from article_deleted ad1_0 where ad1_0.deleted=false == SoftDeleteType.ACTIVE -- remove(x) sql> select aa1_0.id,aa1_0.title from article_active aa1_0 where aa1_0.active=true and aa1_0.id=? sql> update article_active set active=false where id=? and active=true raw rows : [1 | x | false] == SoftDeleteType.TIMESTAMP before delete: [1 | y | false] -- insert sql> insert into article_stamped (title,deleted_at,id) values (?,null,default) -- remove(y) sql> select as1_0.id,as1_0.title from article_stamped as1_0 where as1_0.deleted_at is null and as1_0.id=? sql> update article_stamped set deleted_at=localtimestamp where id=? and deleted_at is null after delete : [1 | y | true, 2 | z | false] -- find(y) after delete -> null sql> select as1_0.id,as1_0.title from article_stamped as1_0 where as1_0.deleted_at is null and as1_0.id=? == @SQLDelete + @SQLRestriction -- insert sql> insert into article_manual (deleted,title,id) values (?,?,default) -- remove(m) sql> select am1_0.id,am1_0.deleted,am1_0.title from article_manual am1_0 where am1_0.id=? and (am1_0.deleted = false) sql> update article_manual set deleted = true where id = ? raw rows : [1 | m | true, 2 | n | false] -- find(m) after delete -> null sql> select am1_0.id,am1_0.deleted,am1_0.title from article_manual am1_0 where am1_0.id=? and (am1_0.deleted = false) == Deleting a row somebody else already soft-deleted -- s1.flush() sql> update article_deleted set deleted=true where id=? and deleted=false flush failed: OptimisticLockException -> StaleStateException: Unexpected row count (expected row count 1 but was 0) [update article_deleted set deleted=true where id=? and deleted=false] ######## POSTGRES ######## database: PostgreSQLDialect / PostgreSQL 17.11 == DDL Hibernate generates create table article_active (active boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_deleted (deleted boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_manual (deleted boolean not null, id bigint generated by default as identity, title varchar(255), primary key (id)); create table article_stamped (deleted_at timestamp(6) with time zone, id bigint generated by default as identity, title varchar(255), primary key (id)); == SoftDeleteType.DELETED (default) -- insert sql> insert into article_deleted (title,deleted) values (?,false) -- remove(first) sql> select ad1_0.id,ad1_0.title from article_deleted ad1_0 where ad1_0.deleted=false and ad1_0.id=? sql> update article_deleted set deleted=true where id=? and deleted=false raw rows : [1 | first | true, 2 | second | false] -- find(first) after delete -> null sql> select ad1_0.id,ad1_0.title from article_deleted ad1_0 where ad1_0.deleted=false and ad1_0.id=? -- HQL: select count(a) from ArticleDeleted a sql> select count(ad1_0.id) from article_deleted ad1_0 where ad1_0.deleted=false == SoftDeleteType.ACTIVE -- remove(x) sql> select aa1_0.id,aa1_0.title from article_active aa1_0 where aa1_0.active=true and aa1_0.id=? sql> update article_active set active=false where id=? and active=true raw rows : [1 | x | false] == SoftDeleteType.TIMESTAMP before delete: [1 | y | false] -- insert sql> insert into article_stamped (title,deleted_at) values (?,null) -- remove(y) sql> select as1_0.id,as1_0.title from article_stamped as1_0 where as1_0.deleted_at is null and as1_0.id=? sql> update article_stamped set deleted_at=localtimestamp where id=? and deleted_at is null after delete : [1 | y | true, 2 | z | false] -- find(y) after delete -> null sql> select as1_0.id,as1_0.title from article_stamped as1_0 where as1_0.deleted_at is null and as1_0.id=? == @SQLDelete + @SQLRestriction -- insert sql> insert into article_manual (deleted,title) values (?,?) -- remove(m) sql> select am1_0.id,am1_0.deleted,am1_0.title from article_manual am1_0 where am1_0.id=? and (am1_0.deleted = false) sql> update article_manual set deleted = true where id = ? raw rows : [1 | m | true, 2 | n | false] -- find(m) after delete -> null sql> select am1_0.id,am1_0.deleted,am1_0.title from article_manual am1_0 where am1_0.id=? and (am1_0.deleted = false) == Deleting a row somebody else already soft-deleted -- s1.flush() sql> update article_deleted set deleted=true where id=? and deleted=false flush failed: OptimisticLockException -> StaleStateException: Unexpected row count (expected row count 1 but was 0) [update article_deleted set deleted=true where id=? and deleted=false]