# soft-delete Companion module for the ankurm.com article **Soft Delete in Hibernate 7: `@SoftDelete` vs `@SQLDelete` + `@SQLRestriction`**. Every claim in the article comes from a test here; each test writes what it observed to `output/`. Tests that touch a database run twice, against in-memory H2 and against a real PostgreSQL 17 container started by Testcontainers. ## Versions | Component | Version | Notes | |---|---|---| | Hibernate ORM | `7.4.14.Final` | newest 7.x GA on Maven Central on 2026-10-28 (`8.0.0.Beta4` is also listed; it is not GA). Overrides the `7.4.5.Final` that Spring Boot 4.1.1 manages | | Spring Boot / Spring Data JPA | `4.1.1` / `4.1.1` | parent POM; only the Spring Data test needs it | | H2 | `2.4.240` | in memory | | PostgreSQL | `17.11` | `postgres:17-alpine` through Testcontainers `2.0.5` (needs a running Docker daemon) | | Java | `25` (Temurin 25.0.5+8) | | `SoftDeleteType` in the shipped jar: `ACTIVE`, `DELETED`, `TIMESTAMP` (see `output/00-javap.txt`). `TIMESTAMP` is absent from `6.6.0.Final` and present in `7.0.0.Final` (both jars inspected with `javap`). ## Run ```bash ./scripts/run-all.sh # javap dump + mvn clean test; regenerates everything in output/ ``` ## Mapping styles | Class | Mapping | |---|---| | [`ArticleDeleted`](src/main/java/com/ankurm/softdelete/ArticleDeleted.java) | `@SoftDelete` (default, boolean `deleted`) | | [`ArticleActive`](src/main/java/com/ankurm/softdelete/ArticleActive.java) | `@SoftDelete(strategy = ACTIVE)` | | [`ArticleStamped`](src/main/java/com/ankurm/softdelete/ArticleStamped.java) | `@SoftDelete(strategy = TIMESTAMP, columnName = "deleted_at")` | | [`ArticleManual`](src/main/java/com/ankurm/softdelete/ArticleManual.java) | `@SQLDelete` + `@SQLRestriction` | | [`ArticleYesNo`](src/main/java/com/ankurm/softdelete/ArticleYesNo.java) | `converter = YesNoConverter.class` | | [`SoftBase`](src/main/java/com/ankurm/softdelete/SoftBase.java) / [`Note`](src/main/java/com/ankurm/softdelete/Note.java) | `@SoftDelete` on a `@MappedSuperclass` | | `Author`, `Book`, `Tag`, `Reader`, `Shelf`, `Item` | associations, collection-level `@SoftDelete`, cascades | | [`ArticleRepository`](src/main/java/com/ankurm/softdelete/ArticleRepository.java) | Spring Data JPA | ## Output | File | What it shows | |---|---| | `00-javap.txt` | `javap` of `SoftDelete`, `SoftDeleteType`, `SQLDelete`, `SQLRestriction` from the shipped jar | | `01-api-shape.txt` | enum values, default column names and annotation defaults read by reflection | | `02-ddl-and-sql.txt` | DDL and the SQL for persist / find / remove / count under all four styles; deleting an already-deleted row | | `03-restore-and-native.txt` | native queries bypass the predicate, HQL cannot restore, stale persistence context, restore per strategy | | `04-unique-constraints.txt` | six unique-index variants on H2 and PostgreSQL: plain, `(email, deleted)`, partial, `(email, deleted_at)`, `NULLS NOT DISTINCT` | | `05-associations.txt` | deleted parent, new child of a deleted parent, collections, many-to-many (join rows are soft-deleted too), collection-level `@SoftDelete`, cascades, purging | | `06-lazy-to-one.txt` | `@ManyToOne(fetch = LAZY)` towards a `@SoftDelete` entity is rejected at startup | | `07-bulk-and-sqldelete.txt` | HQL bulk delete (soft vs real DELETE), stale double delete, `converter`, `@MappedSuperclass` | | `08-spring-data.txt` | `deleteById`, `findById`, `count`, derived deletes, `save` of a soft-deleted instance, bulk and native `@Query`, `deleteAll` | ## Not covered (not run) `@SoftDelete` with inheritance hierarchies, second-level cache, Envers, SQL Server / Oracle / MySQL (filtered or function-based indexes there are described in the article but were not executed), and Spring Data on PostgreSQL (the Spring test runs on H2).