New posts for Spring and Hibernate
This commit is contained in:
@@ -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` |
|
||||
Reference in New Issue
Block a user