New posts for Spring and Hibernate

This commit is contained in:
2026-11-06 01:05:27 +05:30
parent 51913ad9d4
commit dc13293046
136 changed files with 5268 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
# mapstruct
Companion project for the article **[MapStruct with Spring Boot 4 and Java Records: DTO Mapping Done Right](https://ankurm.com/mapstruct-spring-boot-4-java-records-dto-mapping/)** on **[ankurm.com](https://ankurm.com)**.
Every console block quoted in the article came out of `output/`. Transcripts 01-16 are written by the test suite
(so a claim that stops being true turns the build red); 17-24 are the compiler's own words, captured by
`scripts/compile-errors.sh` from the deliberately broken sources in `src/broken/`; 25 is read out of the
processor jar by `scripts/capture-facts.sh`.
There is deliberately **no `docs/` folder**: the deeper material lives in collapsible "going deeper"
sections inside the article itself.
## Versions
| | |
|---|---|
| Spring Boot | 4.1.2 (newest GA in Maven Central's metadata when this was written; the sibling modules use 4.1.1) |
| Spring Framework | 7.0.10 |
| MapStruct | 1.6.3 (newest GA; 1.7.0.Beta2 exists and is not used) |
| Jackson | 3.1.7 (`tools.jackson`) |
| Dozer (test scope, timing only) | 7.0.0 |
| JDK | 25 |
| Maven | 3.9 |
No Lombok. Records make it unnecessary here, and `scripts/compile-errors.sh` shows what Lombok does to the
processor path when you do use it (transcript 24).
## Quickstart
```bash
export JAVA_HOME=/path/to/jdk-25
mvn test # runs the scenarios and rewrites output/01-16
./scripts/run-all.sh # everything, including 17-25 (needs the jars from one `mvn test` in ~/.m2)
```
The compiler plugin gets the generator through `annotationProcessorPaths` (see `pom.xml`). The generated
sources land in `target/generated-sources/annotations/`, where the tests read them back to print them.
## Source layout
| Path | What it holds |
|---|---|
| `domain/` | mutable entity-style classes: `Customer`, `Address`, `Order`, `OrderLine` |
| `dto/` | records: `CustomerDto`, `CustomerRequest`, `CustomerPatch`, `AddressDto`, `OrderDto`, `ReceiptDto` (with a builder), ... |
| `mapper/` | `CentralConfig` (Spring beans, constructor injection, unmapped target = error) and the mappers |
| `web/` | a small controller and an in-memory repository, so the records can be seen over HTTP |
| `src/test/.../probe/` | throwaway mappers that answer "what happens if...": hook order, `@ObjectFactory`, cycles, timing |
| `src/broken/` | sources that must not compile; never part of the Maven build |
## Captured output
| File | What it shows |
|---|---|
| `01-records-as-source-and-target.txt` | a record target has no setters; the generated code calls the constructor |
| `02-generated-implementation.txt` | the generated `AddressMapperImpl` and `CustomerMapperImpl.toDto`, read from `target/` |
| `03-entity-to-record.txt` | nested objects, List/Set/Map, expression, constant, defaultValue, qualifiedByName, `@Context` |
| `04-record-to-entity.txt` | a record as source, `@AfterMapping`, `@InheritInverseConfiguration`, `nullValueCheckStrategy` |
| `05-null-strategies.txt` | `IGNORE` versus the default on a patch; `RETURN_DEFAULT` for whole objects and collections |
| `06-update-in-place.txt` | `@MappingTarget` updates mutate nested objects and collections, including immutable ones |
| `07-hooks-on-records.txt` | `@BeforeMapping` / `@AfterMapping` call order for a record target and a bean target |
| `08-object-factory-trap.txt` | an `@ObjectFactory` that returns a record: nothing is copied, no error |
| `09-builder-and-decorator.txt` | post-processing a record through its builder, and through `@DecoratedWith` |
| `10-context-and-cycles.txt` | `@Context` cycle tracking works for beans and cannot work for records |
| `11-testing-mappers-plain.txt` | `Mappers.getMapper` versus constructing the generated class |
| `12-testing-mappers-contextconfiguration.txt` | a Spring context of just the generated classes |
| `13-testing-mappers-springboottest.txt` | the same mapper inside `@SpringBootTest` |
| `14-property-level-test-strategy.txt` | one mutation per record component, and a completeness check |
| `15-spring-boot-4-records-over-http.txt` | MockMvc, Jackson 3, records in and out |
| `16-indicative-timing-vs-handwritten-and-dozer.txt` | **indicative** timings; differs on every run |
| `17-error-unmapped-target.txt` | javac: `Unmapped target property` |
| `18-error-ambiguous-mapping-methods.txt` | javac: `Ambiguous mapping methods found` |
| `19-error-missing-source-property.txt` | javac: `No property named ... Did you mean ...?` |
| `20-update-method-on-a-record-compiles-to-a-no-op.txt` | `@MappingTarget` on a record compiles, and generates an empty method |
| `21-error-cannot-map-type.txt` | javac: `Can't map property` |
| `22-error-decorator-constructor.txt` | javac: the generated decorator subclass has no constructor to call |
| `23-processor-on-the-plain-classpath.txt` | JDK 25 compiles without running the processor if it is only on `-cp` |
| `24-lombok-processor-order.txt` | Lombok before/after MapStruct, with and without `lombok-mapstruct-binding` |
| `25-processor-jar-facts.txt` | the processor service entry and its `-A` options, read with `javap` |