Skip to content

Commit 35f7e00

Browse files
committed
Merge branch 'release/0.1.0'
2 parents 845442c + 19a8f00 commit 35f7e00

Some content is hidden

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

66 files changed

+2997
-17
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# EditorConfig is awesome: http://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
# Unix-style newlines with a newline ending every file
7+
[*]
8+
end_of_line = lf
9+
insert_final_newline = true
10+
charset = utf-8
11+
indent_style = space
12+
indent_size = 2

.travis.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
11
language: java
2+
script: ./mvnw install --fail-at-end
3+
matrix:
4+
include:
5+
- jdk: openjdk8
6+
env: JACOCO=true COVERALLS=true
7+
- jdk: oraclejdk8
8+
- jdk: oraclejdk9
9+
- jdk: openjdk8
10+
env: GDMSESSION=sonar
11+
- jdk: openjdk8
12+
env: SONAR=publish

README.md

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

5-
## Dependencies
6-
7-
* Java >= 7
3+
[![Build Status](https://travis-ci.org/wavesoftware/java-mapstruct-jpa.svg?branch=master)](https://travis-ci.org/wavesoftware/java-mapstruct-jpa) [![Quality Gate](https://sonar.wavesoftware.pl/api/badges/gate?key=pl.wavesoftware.utils:mapstruct-jpa)](https://sonar.wavesoftware.pl/dashboard/index/pl.wavesoftware.utils:mapstruct-jpa) [![Coverage Status](https://coveralls.io/repos/github/wavesoftware/java-mapstruct-jpa/badge.svg?branch=master)](https://coveralls.io/github/wavesoftware/java-mapstruct-jpa?branch=master) [![Maven Central](https://img.shields.io/maven-central/v/pl.wavesoftware.utils/mapstruct-jpa.svg)](https://mvnrepository.com/artifact/pl.wavesoftware.utils/mapstruct-jpa)
4+
5+
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.
6+
7+
## Features
8+
9+
* Domain model graph with cycles - via `CyclicGraphContext`
10+
* JPA aware mapping with update capability - via `JpaMappingContext` factory
11+
* [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
12+
13+
### Domain model graph with cycles
14+
15+
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`
16+
17+
```java
18+
@Mapper
19+
interface PetMapper {
20+
Pet map(PetData data, @Context CyclicGraphContext context);
21+
PetData map(Pet pet, @Context CyclicGraphContext context);
22+
}
23+
```
24+
25+
### JPA aware mapping with update capability
26+
27+
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.
28+
29+
`JpaMappingContext` factory requires:
30+
* Supplier of `StoringMappingContext` to handle cycles - `CyclicGraphContext` can be used here,
31+
* `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,
32+
* `IdentifierCollector` should collect managed entity ID from source object
33+
34+
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.
35+
36+
All of this can be managed by some DI container like Spring or Guice.
37+
38+
**Mapping facade as Spring service:**
39+
40+
```java
41+
@Service
42+
@RequiredArgsConstructor
43+
final class MapperFacadeImpl implements MapperFacade {
44+
45+
private final PetMapper petMapper;
46+
private final MapStructContextProvider<CompositeContext> contextProvider;
47+
48+
@Override
49+
public PetJPA map(Pet pet) {
50+
return petMapper.map(pet, contextProvider.createNewContext());
51+
}
52+
53+
@Override
54+
public Pet map(PetJPA jpa) {
55+
return petMapper.map(jpa, contextProvider.createNewContext());
56+
}
57+
}
58+
```
59+
60+
**Context provider as Spring service:**
61+
62+
```java
63+
@Service
64+
@RequiredArgsConstructor
65+
final class CompositeContextProvider extends AbstractCompositeContextProvider {
66+
67+
@Getter
68+
private final JpaMappingContextFactory jpaMappingContextFactory;
69+
private final List<MappingProvider<?, ?, ?>> mappingProviders;
70+
@Getter
71+
private final IdentifierCollector identifierCollector;
72+
73+
@Override
74+
protected Iterable<MappingProvider> getMappingProviders() {
75+
return Collections.unmodifiableSet(mappingProviders);
76+
}
77+
78+
}
79+
```
80+
81+
**Example mapping provider for Pet as Spring service:**
82+
83+
```java
84+
@Service
85+
@RequiredArgsConstructor
86+
final class PetMappingProvider implements MappingProvider<Pet, PetJPA, CompositeContext> {
87+
88+
private final PetMapper petMapper;
89+
90+
@Override
91+
public Mapping<Pet, PetJPA, CompositeContext> provide() {
92+
return AbstractCompositeContextMapping.mapperFor(
93+
Pet.class, PetJPA.class,
94+
petMapper::updateFromPet
95+
);
96+
}
97+
}
98+
```
99+
100+
**Identifier collector implementation as Spring service:**
101+
102+
```java
103+
@Service
104+
final class IdentifierCollectorImpl implements IdentifierCollector {
105+
@Override
106+
public Optional<Object> getIdentifierFromSource(Object source) {
107+
if (source instanceof AbstractEntity) {
108+
AbstractEntity entity = AbstractEntity.class.cast(source);
109+
return Optional.ofNullable(
110+
entity.getReference()
111+
);
112+
}
113+
return Optional.empty();
114+
}
115+
}
116+
```
117+
118+
**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)
119+
120+
**HINT:** An example for Guice can be seen in this repository in test packages.
121+
122+
### N+1 problem solution via special uninitialized collection classes
123+
124+
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.
125+
126+
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`.
127+
128+
```java
129+
@Mapper
130+
interface PetMapper {
131+
// [..]
132+
default List<Pet> petJPASetToPetList(Set<PetJPA> set,
133+
@Context CompositeContext context) {
134+
if (!Hibernate.isInitialized(set)) {
135+
return new UninitializedList<>(PetJPA.class);
136+
}
137+
return set.stream()
138+
.map(j -> map(j, context))
139+
.collect(Collectors.toList());
140+
}
141+
// [..]
142+
}
143+
```
144+
145+
**Disclaimer:** In future we plan to provide an automatic solution using dynamic proxy objects.
146+
147+
## Dependencies
148+
149+
* Java >= 8
150+
* [MapStruct JDK8](https://github.com/mapstruct/mapstruct/tree/master/core-jdk8) >= 1.2.0
8151
* [EID Exceptions](https://github.com/wavesoftware/java-eid-exceptions) library
9-
10-
### Contributing
11-
152+
153+
### Contributing
154+
12155
Contributions are welcome!
13-
156+
14157
To contribute, follow the standard [git flow](http://danielkummer.github.io/git-flow-cheatsheet/) of:
15-
158+
16159
1. Fork it
17160
1. Create your feature branch (`git checkout -b feature/my-new-feature`)
18161
1. Commit your changes (`git commit -am 'Add some feature'`)
19162
1. Push to the branch (`git push origin feature/my-new-feature`)
20163
1. Create new Pull Request
21-
164+
22165
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).

pom.xml

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>pl.wavesoftware.utils</groupId>
66
<artifactId>mapstruct-jpa</artifactId>
7-
<version>0.1.0-SNAPSHOT</version>
7+
<version>0.1.0</version>
88
<packaging>jar</packaging>
99

1010
<name>JPA mapping utilities for MapStruct</name>
@@ -67,7 +67,7 @@
6767
<sonar.working.directory>${project.build.directory}/sonar</sonar.working.directory>
6868
<sonar.host.url>https://sonar.wavesoftware.pl</sonar.host.url>
6969
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
70-
<java.source.version>7</java.source.version>
70+
<java.source.version>8</java.source.version>
7171
<sonar.java.source>${java.source.version}</sonar.java.source>
7272
<maven.compiler.source>1.${java.source.version}</maven.compiler.source>
7373
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
@@ -80,10 +80,12 @@
8080
</properties>
8181

8282
<dependencies>
83+
<!-- provided -->
8384
<dependency>
84-
<groupId>pl.wavesoftware</groupId>
85-
<artifactId>eid-exceptions</artifactId>
86-
<version>1.2.0</version>
85+
<groupId>javax.persistence</groupId>
86+
<artifactId>javax.persistence-api</artifactId>
87+
<version>2.2</version>
88+
<scope>provided</scope>
8789
</dependency>
8890
<dependency>
8991
<groupId>org.projectlombok</groupId>
@@ -92,7 +94,28 @@
9294
<scope>provided</scope>
9395
<optional>true</optional>
9496
</dependency>
97+
<dependency>
98+
<groupId>org.mapstruct</groupId>
99+
<artifactId>mapstruct-jdk8</artifactId>
100+
<version>[1.2.0.Final,2.0.0)</version>
101+
<scope>provided</scope>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.mapstruct</groupId>
105+
<artifactId>mapstruct-processor</artifactId>
106+
<version>[1.2.0.Final,2.0.0)</version>
107+
<scope>provided</scope>
108+
<optional>true</optional>
109+
</dependency>
110+
111+
<!-- runtime -->
112+
<dependency>
113+
<groupId>pl.wavesoftware</groupId>
114+
<artifactId>eid-exceptions</artifactId>
115+
<version>1.2.0</version>
116+
</dependency>
95117

118+
<!-- test -->
96119
<dependency>
97120
<groupId>junit</groupId>
98121
<artifactId>junit</artifactId>
@@ -102,7 +125,25 @@
102125
<dependency>
103126
<groupId>org.assertj</groupId>
104127
<artifactId>assertj-core</artifactId>
105-
<version>2.5.0</version>
128+
<version>3.9.1</version>
129+
<scope>test</scope>
130+
</dependency>
131+
<dependency>
132+
<groupId>org.mockito</groupId>
133+
<artifactId>mockito-core</artifactId>
134+
<version>2.18.3</version>
135+
<scope>test</scope>
136+
</dependency>
137+
<dependency>
138+
<groupId>com.google.inject</groupId>
139+
<artifactId>guice</artifactId>
140+
<version>4.2.0</version>
141+
<scope>test</scope>
142+
</dependency>
143+
<dependency>
144+
<groupId>org.hibernate</groupId>
145+
<artifactId>hibernate-core</artifactId>
146+
<version>5.2.17.Final</version>
106147
<scope>test</scope>
107148
</dependency>
108149
</dependencies>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package pl.wavesoftware.lang;
2+
3+
import java.util.Objects;
4+
import java.util.function.Consumer;
5+
6+
/**
7+
* Represents an operation that accepts tree input arguments and returns no
8+
* result. This is the tree-arity specialization of {@link Consumer}.
9+
* Unlike most other functional interfaces, {@code TriConsumer} is expected
10+
* to operate via side-effects.
11+
*
12+
* <p>This is a <a href="package-summary.html">functional interface</a>
13+
* whose functional method is {@link #accept(Object, Object, Object)}.
14+
*
15+
* @param <T> the type of the first argument to the operation
16+
* @param <U> the type of the second argument to the operation
17+
* @param <V> the type of the third argument to the operation
18+
*
19+
* @see Consumer
20+
* @author <a href="mailto:krzysztof.suszynski@wavesoftware.pl">Krzysztof Suszynski</a>
21+
* @since 25.04.18
22+
*/
23+
@FunctionalInterface
24+
public interface TriConsumer<T, U, V> {
25+
/**
26+
* Performs this operation on the given arguments.
27+
*
28+
* @param t the first input argument
29+
* @param u the second input argument
30+
* @param v the third input argument
31+
*/
32+
void accept(T t, U u, V v);
33+
34+
/**
35+
* Returns a composed {@code TriConsumer} that performs, in sequence, this
36+
* operation followed by the {@code after} operation. If performing either
37+
* operation throws an exception, it is relayed to the caller of the
38+
* composed operation. If performing this operation throws an exception,
39+
* the {@code after} operation will not be performed.
40+
*
41+
* @param after the operation to perform after this operation
42+
* @return a composed {@code TriConsumer} that performs in sequence this
43+
* operation followed by the {@code after} operation
44+
* @throws NullPointerException if {@code after} is null
45+
*/
46+
default TriConsumer<T, U, V> andThen(TriConsumer<? super T, ? super U, ? super V> after) {
47+
Objects.requireNonNull(after);
48+
49+
return (f, s, t) -> {
50+
accept(f, s, t);
51+
after.accept(f, s, t);
52+
};
53+
}
54+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @author <a href="mailto:krzysztof.suszynski@wavesoftware.pl">Krzysztof Suszyński</a>
3+
* @since 2018-05-03
4+
*/
5+
@ParametersAreNonnullByDefault
6+
package pl.wavesoftware.lang;
7+
8+
import javax.annotation.ParametersAreNonnullByDefault;

0 commit comments

Comments
 (0)