Skip to content

Commit 04b8ef7

Browse files
authored
Merge pull request #2 from wavesoftware/feature/documentation
Feature: Documentation
2 parents 5090334 + 99e1899 commit 04b8ef7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+646
-125
lines changed

README.md

Lines changed: 152 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,163 @@
11
# JPA mapping utilities for MapStruct
2-
3-
TODO: write readme file
42

5-
## Dependencies
6-
7-
* Java >= 7
3+
A set of utilities focused on mapping JPA managed entities with MapStruct. There are different utilities for different purposes and also a all-in-one utility for maximizing ease of use.
4+
5+
## Features
6+
7+
* Domain model graph with cycles - via `CyclicGraphContext`
8+
* JPA aware mapping with update capability - via `JpaMappingContext` factory
9+
* [N+1 problem](https://stackoverflow.com/questions/97197/what-is-the-n1-select-query-issue) solution via special uninitialized collection classes, that throws exceptions if used
10+
11+
### Domain model graph with cycles
12+
13+
If you need to map a domain model with cycles in entity graph for ex.: (Pet.owner -> Person, Person.pets -> Pet) you can use a `CyclicGraphContext` as a MapStruct `@Context`
14+
15+
```java
16+
@Mapper
17+
interface PetMapper {
18+
Pet map(PetData data, @Context CyclicGraphContext context);
19+
PetData map(Pet pet, @Context CyclicGraphContext context);
20+
}
21+
```
22+
23+
### JPA aware mapping with update capability
24+
25+
If you also need support for mapping JPA managed entities and be able to update them (not create new records) there more to be done. There is provided `JpaMappingContext` with factory. It requires couple more configuration to instantiate this context.
26+
27+
`JpaMappingContext` factory requires:
28+
* Supplier of `StoringMappingContext` to handle cycles - `CyclicGraphContext` can be used here,
29+
* `Mappings` object that will provides mapping for given source and target class - mapping is information how to update existing object (managed entity) with data from source object,
30+
* `IdentifierCollector` should collect managed entity ID from source object
31+
32+
The easiest way to setup all of this is to extend `AbstractJpaContextProvider`, implement `IdentifierCollector` and implement a set of `MappingProvider` for each type of entity. To provide implementations of `MappingProvider` you should create update methods in your MapStruct mappers. It utilize `CompositeContext` which can incorporate any number of contexts as a composite.
33+
34+
All of this can be managed by some DI container like Spring or Guice.
35+
36+
**Mapping facade as Spring service:**
37+
38+
```java
39+
@Service
40+
@RequiredArgsConstructor
41+
final class MapperFacadeImpl implements MapperFacade {
42+
43+
private final PetMapper petMapper;
44+
private final MapStructContextProvider<CompositeContext> contextProvider;
45+
46+
@Override
47+
public PetJPA map(Pet pet) {
48+
return petMapper.map(pet, contextProvider.createNewContext());
49+
}
50+
51+
@Override
52+
public Pet map(PetJPA jpa) {
53+
return petMapper.map(jpa, contextProvider.createNewContext());
54+
}
55+
}
56+
```
57+
58+
**Context provider as Spring service:**
59+
60+
```java
61+
@Service
62+
@RequiredArgsConstructor
63+
final class CompositeContextProvider extends AbstractCompositeContextProvider {
64+
65+
@Getter
66+
private final JpaMappingContextFactory jpaMappingContextFactory;
67+
private final List<MappingProvider<?, ?, ?>> mappingProviders;
68+
@Getter
69+
private final IdentifierCollector identifierCollector;
70+
71+
@Override
72+
protected Iterable<MappingProvider> getMappingProviders() {
73+
return Collections.unmodifiableSet(mappingProviders);
74+
}
75+
76+
}
77+
```
78+
79+
**Example mapping provider for Pet as Spring service:**
80+
81+
```java
82+
@Service
83+
@RequiredArgsConstructor
84+
final class PetMappingProvider implements MappingProvider<Pet, PetJPA, CompositeContext> {
85+
86+
private final PetMapper petMapper;
87+
88+
@Override
89+
public Mapping<Pet, PetJPA, CompositeContext> provide() {
90+
return AbstractCompositeContextMapping.mapperFor(
91+
Pet.class, PetJPA.class,
92+
petMapper::updateFromPet
93+
);
94+
}
95+
}
96+
```
97+
98+
**Identifier collector implementation as Spring service:**
99+
100+
```java
101+
@Service
102+
final class IdentifierCollectorImpl implements IdentifierCollector {
103+
@Override
104+
public Optional<Object> getIdentifierFromSource(Object source) {
105+
if (source instanceof AbstractEntity) {
106+
AbstractEntity entity = AbstractEntity.class.cast(source);
107+
return Optional.ofNullable(
108+
entity.getReference()
109+
);
110+
}
111+
return Optional.empty();
112+
}
113+
}
114+
```
115+
116+
**HINT:** Complete working example in Spring can be seen in [coi-gov-pl/spring-clean-architecture hibernate module](https://github.com/coi-gov-pl/spring-clean-architecture/tree/develop/pets/persistence-hibernate/src/main/java/pl/gov/coi/cleanarchitecture/example/spring/pets/persistence/hibernate/mapper)
117+
118+
**HINT:** An example for Guice can be seen in this repository in test packages.
119+
120+
### N+1 problem solution via special uninitialized collection classes
121+
122+
The N+1 problem is wide known and prominent problem when dealing with JPA witch utilizes lazy loading of data. Solution to this is that developers should fetch only data that they will need (for ex.: using `JOIN FETCH` in JPQL). In many cases that is not enough. It easy to slip some loop when dealing with couple of records.
123+
124+
My solution is to detect that object is not loaded fully and provide a stub that will fail fast if data is not loaded and been tried to be used by other developer. To do that simple use `Uninitialized*` classes provided. There are `UninitializedList`, `UninitializedSet`, and `UninitializedMap`.
125+
126+
```java
127+
@Mapper
128+
interface PetMapper {
129+
// [..]
130+
default List<Pet> petJPASetToPetList(Set<PetJPA> set,
131+
@Context CompositeContext context) {
132+
if (!Hibernate.isInitialized(set)) {
133+
return new UninitializedList<>(PetJPA.class);
134+
}
135+
return set.stream()
136+
.map(j -> map(j, context))
137+
.collect(Collectors.toList());
138+
}
139+
// [..]
140+
}
141+
```
142+
143+
**Disclaimer:** In future we plan to provide an automatic solution using dynamic proxy objects.
144+
145+
## Dependencies
146+
147+
* Java >= 8
148+
* [MapStruct JDK8](https://github.com/mapstruct/mapstruct/tree/master/core-jdk8) >= 1.2.0
8149
* [EID Exceptions](https://github.com/wavesoftware/java-eid-exceptions) library
9-
10-
### Contributing
11-
150+
151+
### Contributing
152+
12153
Contributions are welcome!
13-
154+
14155
To contribute, follow the standard [git flow](http://danielkummer.github.io/git-flow-cheatsheet/) of:
15-
156+
16157
1. Fork it
17158
1. Create your feature branch (`git checkout -b feature/my-new-feature`)
18159
1. Commit your changes (`git commit -am 'Add some feature'`)
19160
1. Push to the branch (`git push origin feature/my-new-feature`)
20161
1. Create new Pull Request
21-
162+
22163
Even if you can't contribute code, if you have an idea for an improvement please open an [issue](https://github.com/wavesoftware/java-mapstruct-jpa/issues).

src/main/java/pl/wavesoftware/lang/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @author <a href="[email protected]">Krzysztof Suszyński</a>
2+
* @author <a href="mailto:[email protected]">Krzysztof Suszyński</a>
33
* @since 2018-05-03
44
*/
55
@ParametersAreNonnullByDefault

src/main/java/pl/wavesoftware/utils/mapstruct/jpa/AbstractCompositeContextMapping.java

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,71 @@
33
import pl.wavesoftware.lang.TriConsumer;
44

55
/**
6-
* @author <a href="[email protected]">Krzysztof Suszyński</a>
6+
* An abstract class that can be extended to provide logic in how to apply data from
7+
* {@link I} input object to an target {@link O} output object. It uses {@link CompositeContext}
8+
* as a MapStruct context.
9+
*
10+
* <p>
11+
* It's designed to be used easily with
12+
* <a href="http://mapstruct.org/documentation/stable/reference/html/#updating-bean-instances">
13+
* MapStruct update methods</a>.
14+
* <pre>
15+
* &#064;Service
16+
* &#064;RequiredArgsConstructor
17+
* final class PetCompositeContextMapping extends AbstractCompositeContextMapping&lt;Pet,PetData&gt; {
18+
* private final PetMapper petMapper;
19+
* &#064;Override
20+
* public void accept(Pet pet, PetData data, CompositeContext context) {
21+
* petMapper.updateFromPet(pet, data, context);
22+
* }
23+
* }
24+
* </pre>
25+
*
26+
* There is also a convenience method {@link #mappingFor(Class, Class, TriConsumer)} which can be
27+
* used to create mapping easily with {@link AbstractJpaContextProvider}:
28+
*
29+
* <br>
30+
* <pre>
31+
* &#064;RequiredArgsConstructor
32+
* final class OwnerMappingProvider implements MappingProvider&lt;Owner, OwnerJPA, CompositeContext&gt; {
33+
* private final OwnerMapper ownerMapper;
34+
*
35+
* &#064;Override
36+
* public Mapping&lt;Owner, OwnerJPA, CompositeContext&gt; provide() {
37+
* return AbstractCompositeContextMapping.mappingFor(
38+
* Owner.class, OwnerJPA.class,
39+
* ownerMapper::updateFromOwner
40+
* );
41+
* }
42+
* }
43+
* </pre>
44+
*
45+
* @param <I> a type of input object to map from
46+
* @param <O> a type of output object to map to
47+
* @author <a href="mailto:[email protected]">Krzysztof Suszyński</a>
748
* @since 2018-05-02
849
*/
9-
public abstract class AbstractCompositeContextMapping<I, O> extends AbstractMapping<I, O, CompositeContext> {
50+
public abstract class AbstractCompositeContextMapping<I, O>
51+
extends AbstractMapping<I, O, CompositeContext> {
52+
1053
protected AbstractCompositeContextMapping(Class<I> sourceClass,
1154
Class<O> targetClass) {
1255
super(sourceClass, targetClass, CompositeContext.class);
1356
}
1457

15-
public static <I, O> AbstractCompositeContextMapping<I, O> mapperFor(
58+
/**
59+
* A convenience method which can be used to create mapping easily with
60+
* {@link AbstractJpaContextProvider}.
61+
*
62+
* @param inputClass a class of an input object
63+
* @param outputClass a class of an output object
64+
* @param consumer a consumer of 3 values: input object, output object
65+
* and {@link CompositeContext} object
66+
* @param <I> a type of input object to map from
67+
* @param <O> a type of output object to map to
68+
* @return a mapping for given values
69+
*/
70+
public static <I, O> Mapping<I, O, CompositeContext> mappingFor(
1671
Class<I> inputClass,
1772
Class<O> outputClass,
1873
TriConsumer<I, O, CompositeContext> consumer) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package pl.wavesoftware.utils.mapstruct.jpa;
2+
3+
import javax.persistence.EntityManager;
4+
import java.util.function.Supplier;
5+
6+
/**
7+
* A base abstract class that can be used as a provider for configured and fully working
8+
* JPA aware mapping context with update capability. It's designed to be implemented and
9+
* configured in your DI container to used in your mapper facade to produce new context each
10+
* time you are doing a mapping.
11+
*
12+
* @author <a href="mailto:[email protected]">Krzysztof Suszyński</a>
13+
* @since 2018-05-03
14+
*/
15+
public abstract class AbstractJpaContextProvider
16+
implements MapStructContextProvider<CompositeContext> {
17+
18+
private final JpaMappingContextProviderImpl provider =
19+
new JpaMappingContextProviderImpl();
20+
21+
/**
22+
* A method to that returns a supplier of JPA's {@link EntityManager} bounded to
23+
* current transaction context.
24+
*
25+
* @return a supplier of current <code>EntityManager</code>
26+
*/
27+
protected abstract Supplier<EntityManager> getEntityManager();
28+
29+
/**
30+
* Returns a collection of mapping providers to be used in mapping. Each mapping provider
31+
* is for other mapping (for ex.: Pet to PetData etc.).
32+
*
33+
* @return a collection of mapping providers
34+
*/
35+
protected abstract Iterable<MappingProvider<?, ?, ?>> getMappingProviders();
36+
37+
/**
38+
* Returns a identifier collector that can fetch an ID to be used to fetch a managed entity
39+
* from {@link EntityManager}. It will try to fetch the ID from source mapping object, so
40+
* it must contain some kind of way to provide it.
41+
*
42+
* @return a identifier collector
43+
*/
44+
protected abstract IdentifierCollector getIdentifierCollector();
45+
46+
@Override
47+
public CompositeContext createNewContext() {
48+
return provider.provide(
49+
getEntityManager(), getMappingProviders(), getIdentifierCollector()
50+
);
51+
}
52+
}

src/main/java/pl/wavesoftware/utils/mapstruct/jpa/AbstractMapping.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
import lombok.RequiredArgsConstructor;
55

66
/**
7+
* An abstract mapping that holds all classes representations.
8+
*
9+
* @param <I> a type of input object for map from
10+
* @param <O> a type of output (target) object for map to
11+
* @param <C> a type of context to be used in the mapping
12+
*
713
* @author <a href="mailto:[email protected]">Krzysztof Suszynski</a>
814
* @since 25.04.18
915
*/

src/main/java/pl/wavesoftware/utils/mapstruct/jpa/CompositeContext.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import java.util.Optional;
1313

1414
/**
15-
* A composite mapping context that can utilize multiple mapping contexts.
15+
* A composite mapping context that can utilize multiple mapping contexts to provide
16+
* the joined processing. User can add any number of {@link MappingContext} to use in
17+
* this composite class.
1618
*
1719
* @author <a href="mailto:[email protected]">Krzysztof Suszynski</a>
1820
* @since 02.05.18
@@ -75,11 +77,10 @@ private static final class CompositeContextBuilderImpl implements CompositeConte
7577
private final List<MappingContext> mappingContexts = new ArrayList<>();
7678

7779
@Override
78-
public CompositeContextBuilderImpl addContext(MappingContext... mappingContexts) {
80+
public void addContext(MappingContext... mappingContexts) {
7981
this.mappingContexts.addAll(
8082
Arrays.asList(mappingContexts)
8183
);
82-
return this;
8384
}
8485

8586
@Override
Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
package pl.wavesoftware.utils.mapstruct.jpa;
22

33
/**
4+
* This is a builder for composite context.
5+
*
6+
* @see CompositeContext
47
* @author <a href="mailto:[email protected]">Krzysztof Suszynski</a>
58
* @since 04.05.18
69
*/
710
public interface CompositeContextBuilder {
8-
CompositeContextBuilder addContext(MappingContext... mappingContexts);
11+
/**
12+
* Adds context to the builder.
13+
*
14+
* @param mappingContexts A mapping contexts to be added to builder and then created
15+
* the composite context from it.
16+
*/
17+
void addContext(MappingContext... mappingContexts);
18+
19+
/**
20+
* Builds a composite context from provided set of other mapping contexts.
21+
*
22+
* @return a builded instance of composite context
23+
*/
924
CompositeContext build();
1025
}

src/main/java/pl/wavesoftware/utils/mapstruct/jpa/CyclicGraphContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* cycles.
1919
*
2020
* @author Andreas Gudian
21-
* @author <a href="[email protected]">Krzysztof Suszyński</a>
21+
* @author <a href="mailto:[email protected]">Krzysztof Suszyński</a>
2222
* @since 2018-04-12
2323
*/
2424
public final class CyclicGraphContext implements MapStructContext {

src/main/java/pl/wavesoftware/utils/mapstruct/jpa/IdentifierCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* }
2222
* </pre>
2323
*
24-
* @author <a href="[email protected]">Krzysztof Suszyński</a>
24+
* @author <a href="mailto:[email protected]">Krzysztof Suszyński</a>
2525
* @since 2018-05-02
2626
*/
2727
public interface IdentifierCollector {

0 commit comments

Comments
 (0)