Skip to content

Commit

Permalink
[Blazebit#1404, Blazebit#1405] Add JSONB integration and make sure th…
Browse files Browse the repository at this point in the history
…at GraphQL mutations work
  • Loading branch information
beikov committed Dec 12, 2021
1 parent d73026a commit abfbbc5
Show file tree
Hide file tree
Showing 134 changed files with 6,680 additions and 406 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ Not yet released
* Add `PERCENTILE_CONT`, `PERCENTILE_DISC` and `MODE` ordered set-aggregate functions
* Add JPA Criteria support for aggregate `FILTER` clause, window functions and ordered set aggregate functions
* Fire `EntityViewConfiguration` as event in Quarkus extension boot like in the CDI integration to allow customization
* Add Spring SPQR example application showcasing how GraphQL mutations can be used
* Add JSONB integration for deserializing entity views
* Add Spring SPQR integration and example application
* Add examples for SPQR, DGS and MicroProfile GraphQL showcasing how GraphQL mutations can be used

### Bug fixes

Expand All @@ -24,10 +26,12 @@ Not yet released
* Add `getById` implementation as needed by Spring Data 2.5 to the integration
* Ensure stream/iteration processing of results works with entity views containing no collections
* Fix support for flushing flat view collections with read-only declared and updatable subview types
* Ensure pending inserts for tables targeted by foreign keys by custom insert/update DML are flushed

### Backwards-incompatible changes

* Properly implement the `getOne` semantics to return a reference instead of querying an instance
* Split JAX-RS integration into JAX-RS for Jackson and JAX-RS for JSONB integrations

## 1.6.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
<suppress checks="." files="[\\/]com[\\/]blazebit[\\/]persistence[\\/]spring[\\/]data[\\/]webflux[\\/]impl[\\/]SortHandlerMethodArgumentResolver\.java$"/>
<suppress checks="." files="[\\/]com[\\/]blazebit[\\/]persistence[\\/]spring[\\/]data[\\/]webflux[\\/]impl[\\/]SortArgumentResolver\.java$"/>

<suppress checks="." files="[\\/]com[\\/]blazebit[\\/]persistence[\\/]integration[\\/]jsonb[\\/]jsonstructure[\\/].*\.java$"/>

<!-- The implementation, testsuite and examples don't have to fit our Javadoc requirements -->
<suppress checks="JavadocPackage" files="[\\/](parser|impl|integration|testsuite|processor|examples|org.springframework)[\\/].*\.java$"/>
<suppress checks="JavadocMethod" files="[\\/](parser|impl|integration|testsuite|processor|examples|org.springframework)[\\/].*\.java$"/>
Expand Down
44 changes: 43 additions & 1 deletion dist/bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,29 @@
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jackson</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jakarta</artifactId>
<artifactId>blaze-persistence-integration-jaxrs-jackson-jakarta</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb-jakarta</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
Expand All @@ -384,6 +402,18 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jsonb</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jsonb-jakarta</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql</artifactId>
Expand All @@ -396,6 +426,18 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql-spqr</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql-spqr-jakarta</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-quarkus</artifactId>
Expand Down
16 changes: 16 additions & 0 deletions dist/full/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql-spqr</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-spring-hateoas-webmvc</artifactId>
Expand All @@ -207,10 +211,22 @@
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jsonb</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb</artifactId>
</dependency>
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-quarkus</artifactId>
Expand Down
9 changes: 3 additions & 6 deletions dist/full/src/main/distribution/assembly.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,9 @@
<include>com.blazebit:blaze-persistence-integration-entity-view-*</include>
<include>com.blazebit:blaze-persistence-integration-spring-*</include>
<include>com.blazebit:blaze-persistence-integration-deltaspike-data-*</include>
<include>com.blazebit:blaze-persistence-integration-jaxrs</include>
<include>com.blazebit:blaze-persistence-integration-jaxrs-jakarta</include>
<include>com.blazebit:blaze-persistence-integration-jackson</include>
<include>com.blazebit:blaze-persistence-integration-jackson-jakarta</include>
<include>com.blazebit:blaze-persistence-integration-graphql</include>
<include>com.blazebit:blaze-persistence-integration-graphql-jakarta</include>
<include>com.blazebit:blaze-persistence-integration-jaxrs*</include>
<include>com.blazebit:blaze-persistence-integration-jackson*</include>
<include>com.blazebit:blaze-persistence-integration-graphql*</include>
</includes>
<outputFileNameMapping>${artifact.artifactId}-${project.version}.${artifact.extension}</outputFileNameMapping>
<outputDirectory>lib/integration/entity-view</outputDirectory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[[jaxrs-integration]]
== JAX-RS integration

The JAX-RS integration provides `MessageBodyReader` and `ParamConverter` implementations to integrate the following serialization integrations with JAX-RS
The JAX-RS integration module serves as the API which contains the `@EntityViewId` annotation.
The `MessageBodyReader` and `ParamConverter` implementations to integrate serialization frameworks with JAX-RS are available for

* <<jackson-integration,Jackson integration>>
* <<jsonb-integration,JSONB integration>>
* _At some point there will also be support for JAXB_

The integration is discovered automatically through the `javax.ws.rs.ext.Providers` ServiceLoader contract. Simply putting the artifact on the classpath is enough for the integration.
Expand All @@ -17,7 +19,7 @@ To use the Jackson integration directly you need the following Maven dependencie
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs</artifactId>
<artifactId>blaze-persistence-integration-jaxrs-jackson</artifactId>
<version>${blaze-persistence.version}</version>
<scope>runtime</scope>
</dependency>
Expand All @@ -29,7 +31,31 @@ or if you are using Jakarta JPA
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jakarta</artifactId>
<artifactId>blaze-persistence-integration-jaxrs-jackson-jakarta</artifactId>
<version>${blaze-persistence.version}</version>
<scope>runtime</scope>
</dependency>
----

To use the JSONB integration directly you need the following Maven dependencies:

[source,xml]
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb</artifactId>
<version>${blaze-persistence.version}</version>
<scope>runtime</scope>
</dependency>
----

or if you are using Jakarta JPA

[source,xml]
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb-jakarta</artifactId>
<version>${blaze-persistence.version}</version>
<scope>runtime</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ and entity views naturally define their state through getters.

The deserialization support is a different thing though for which special integrations are required.

By default, the deserialization will invoke `EntityViewManager.getReference(viewType, id)` to construct objects.
If the view type that should be deserialized is creatable i.e. annotated with `@CreatableEntityView` and no value for the `@IdMapping` attribute is provided in the payload,
it will be created via `EntityViewManager.create()`.
The rest of the payload values are then deserialized onto the constructed object via the setters the entity view types provide.

Read-only types only support deserialization when a value for the `@IdMapping` attribute is given and obviously need setters for the deserialization to work properly.
Adding setters makes the models mutable which might not be desirable, so consider this when wanting to deserialize payload to entity views.
A possible solution is to create an entity view subtype that has the sole purpose of providing setters so that it can be deserialized.

Since entity view objects are created via `EntityViewManager.getReference()` or `EntityViewManager.create()`, the objects can then be directly saved via `EntityViewManager.save()`.
For updatable entity views it is important to understand that `EntityViewManager.save()` will only flush non-null attributes i.e. do a partial flush.
This is due to dirty tracking thinking that the initial state is all null attributes. Through deserialization, some attributes are set to values which are then considered dirty.
That behavior is exactly what you would expect from an HTTP endpoint using the `PATCH` method.

If you need to flush the full state regardless of the dirty tracking information, you can use `EntityViewManager.saveFull()`,
but be aware that orphan removals will currently not work when using the `QUERY` flush strategy because the initial state is unknown.
If you need orphan removal in such a case, you are advised to switch to the `ENTITY` flush strategy for now.
You could also make use of the `EntityViewManager.saveTo()` and `EntityViewManager.saveFullTo()` variants to flush data to an entity that was loaded via e.g. `EntityManager.find()`.

An alternative to this would be to deserialize the state onto an existing updatable entity view that was loaded via e.g. `EntityViewManager.find()`.
With the initial state being known due to loading from the database, orphan removal will work correctly,
but be aware that providing `null` values for attributes in the JSON payload will obviously cause `null` to be set on the entity view attributes.

[[jackson-integration]]
=== Jackson integration

Expand Down Expand Up @@ -52,25 +75,47 @@ EntityViewAwareObjectMapper mapper = new EntityViewAwareObjectMapper(evm, existi
The `EntityViewAwareObjectMapper` class provides utility methods for integrating with JAX-RS, Spring WebMvc and Spring WebFlux,
but you can use your `ObjectMapper` directly as before as the module and visibility checker is registered in the existing mapper.

By default, the deserialization will invoke `EntityViewManager.getReference(viewType, id)` to construct objects.
If the view type that should be deserialized is creatable i.e. annotated with `@CreatableEntityView` and no value for the `@IdMapping` attribute is provided in the JSON,
it will be created via `EntityViewManager.create()`.
The rest of the JSON values are then deserialized onto the constructed object via the setters the entity view types provide.
[[jsonb-integration]]
=== JSONB integration

Read-only types only support deserialization when a value for the `@IdMapping` attribute is given and obviously need setters for the deserialization to work properly.
Adding setters makes the models mutable which might not be desirable, so consider this when wanting to deserialize JSON to entity views.
A possible solution is to create an entity view subtype that has the sole purpose of providing setters so that it can be deserialized.
[[jsonb-setup]]
==== Setup

Since entity view objects are created via `EntityViewManager.getReference()` or `EntityViewManager.create()`, the objects can then be directly saved via `EntityViewManager.save()`.
For updatable entity views it is important to understand that `EntityViewManager.save()` will only flush non-null attributes i.e. do a partial flush.
This is due to dirty tracking thinking that the initial state is all null attributes. Through deserialization, some attributes are set to values which are then considered dirty.
That behavior is exactly what you would expect from a HTTP endpoint using the `PATCH` method.
To use the JSONB integration directly you need the following Maven dependencies:

If you need to flush the full state regardless of the dirty tracking information, you can use `EntityViewManager.saveFull()`,
but be aware that orphan removals will currently not work when using the `QUERY` flush strategy because the initial state is unknown.
If you need orphan removal in such a case, you are advised to switch to the `ENTITY` flush strategy for now.
You could also make use of the `EntityViewManager.saveTo()` and `EntityViewManager.saveFullTo()` variants to flush data to an entity that was loaded via e.g. `EntityManager.find()`.
[source,xml]
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jsonb</artifactId>
<version>${blaze-persistence.version}</version>
<scope>compile</scope>
</dependency>
----

An alternative to this would be to deserialize the state onto an existing updatable entity view that was loaded via e.g. `EntityViewManager.find()`.
With the initial state being known due to loading from the database, orphan removal will work correctly,
but be aware that providing `null` values for attributes in the JSON payload will obviously cause `null` to be set on the entity view attributes.
or if you are using Jakarta JPA

[source,xml]
----
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-jsonb-jakarta</artifactId>
<version>${blaze-persistence.version}</version>
<scope>compile</scope>
</dependency>
----

If you are using JAX-RS, Spring WebMvc or Spring WebFlux, consider using the already existing integrations instead to avoid unnecessary work.

==== Usage

The integration happens by adding custom `JsonDeserializer` instances per entity view and a `PropertyVisibilityStrategy` to a `JsonbConfig`.

[source,java]
----
JsonbConfig jsonbConfig = new JsonbConfig();
EntityViewJsonbDeserializer.integrate(jsonbConfig, evm, idValueAccessor);
Jsonb jsonb = JsonbBuilder.create(jsonbConfig);
----

The resulting `Jsonb` instance will make use of the special deserializers for entity views and you can simply use it as usual e.g. `jsonb.fromString("...", MyEntityView.class)`.
4 changes: 4 additions & 0 deletions examples/deltaspike-data-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@
<groupId>${project.groupId}</groupId>
<artifactId>blaze-persistence-integration-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jackson</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down
12 changes: 11 additions & 1 deletion examples/microprofile-graphql/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@
<artifactId>smallrye-graphql-ui-graphiql</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>1.0.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blaze-persistence-integration-hibernate-base</artifactId>
Expand All @@ -139,7 +145,11 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blaze-persistence-integration-jaxrs</artifactId>
<artifactId>blaze-persistence-integration-jsonb</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>blaze-persistence-integration-jaxrs-jsonb</artifactId>
</dependency>

<!-- Test dependencies -->
Expand Down
Loading

0 comments on commit abfbbc5

Please sign in to comment.