Files
hibernate-demo/soft-delete/README.md
T

57 lines
3.7 KiB
Markdown

# 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).