442 lines
18 KiB
Plaintext
442 lines
18 KiB
Plaintext
[[mapping]]
|
|
= POJO Mapping
|
|
|
|
This section covers:
|
|
|
|
* <<mapping.entities>>
|
|
* <<mapping.repositories>>
|
|
* <<Mapping PDX Serializer>>
|
|
|
|
[[mapping.entities]]
|
|
== Entity Mapping
|
|
|
|
{sdg-name} provides support to map entities that are stored in a Region. The mapping metadata is defined by
|
|
using annotations on application domain classes, as the following example shows:
|
|
|
|
.Mapping a domain class to a {data-store-name} Region
|
|
====
|
|
[source,java]
|
|
----
|
|
@Region("People")
|
|
public class Person {
|
|
|
|
@Id Long id;
|
|
|
|
String firstname;
|
|
String lastname;
|
|
|
|
@PersistenceConstructor
|
|
public Person(String firstname, String lastname) {
|
|
// …
|
|
}
|
|
|
|
…
|
|
}
|
|
----
|
|
====
|
|
|
|
The `@Region` annotation can be used to customize the Region in which an instance of the `Person` class is stored.
|
|
The `@Id` annotation can be used to annotate the property that should be used as the cache Region key, identifying
|
|
the Region entry. The `@PersistenceConstructor` annotation helps to disambiguate multiple potentially available
|
|
constructors, taking parameters and explicitly marking the constructor annotated as the constructor to be used to
|
|
construct entities. In an application domain class with no or only a single constructor, you can omit the annotation.
|
|
|
|
In addition to storing entities in top-level Regions, entities can be stored in Sub-Regions as well,
|
|
as the following example shows:
|
|
|
|
[source,java]
|
|
----
|
|
@Region("/Users/Admin")
|
|
public class Admin extends User {
|
|
…
|
|
}
|
|
|
|
@Region("/Users/Guest")
|
|
public class Guest extends User {
|
|
…
|
|
}
|
|
----
|
|
|
|
Be sure to use the full path of the {data-store-name} Region, as defined with the {sdg-name} XML namespace
|
|
by using the `id` or `name` attributes of the `<*-region>` element.
|
|
|
|
[[mapping.entities.region]]
|
|
=== Entity Mapping by Region Type
|
|
|
|
In addition to the `@Region` annotation, {sdg-name} also recognizes type-specific Region mapping annotations:
|
|
`@ClientRegion`, `@LocalRegion`, `@PartitionRegion`, and `@ReplicateRegion`.
|
|
|
|
Functionally, these annotations are treated exactly the same as the generic `@Region` annotation in the {sdg-acronym}
|
|
mapping infrastructure. However, these additional mapping annotations are useful in {sdg-name}'s
|
|
annotation configuration model. When combined with the `@EnableEntityDefinedRegions` configuration annotation
|
|
on a Spring `@Configuration` annotated class, it is possible to generate Regions in the local cache, whether
|
|
the application is a client or peer.
|
|
|
|
These annotations let you be more specific about what type of Region your application entity class should be mapped to
|
|
and also has an impact on the data management policies of the Region (for example, partition -- also known as sharding
|
|
-- versus replicating data).
|
|
|
|
Using these type-specific Region mapping annotations with the {sdg-acronym} annotation configuration model saves you
|
|
from having to explicitly define these Regions in configuration.
|
|
|
|
[[mapping.repositories]]
|
|
== Repository Mapping
|
|
|
|
As an alternative to specifying the Region in which the entity is stored by using the `@Region` annotation
|
|
on the entity class, you can also specify the `@Region` annotation on the entity's `Repository` interface.
|
|
See <<gemfire-repositories>> for more details.
|
|
|
|
However, suppose you want to store a `Person` record in multiple {data-store-name} Regions (for example, `People`
|
|
and `Customers`). Then you can define your corresponding `Repository` interface extensions as follows:
|
|
|
|
[source,java]
|
|
----
|
|
@Region("People")
|
|
public interface PersonRepository extends GemfireRepository<Person, String> {
|
|
…
|
|
}
|
|
|
|
@Region("Customers")
|
|
public interface CustomerRepository extends GemfireRepository<Person, String> {
|
|
...
|
|
}
|
|
----
|
|
|
|
Then, using each Repository individually, you can store the entity in multiple {data-store-name} Regions,
|
|
as the following example shows:
|
|
|
|
[source,java]
|
|
----
|
|
@Service
|
|
class CustomerService {
|
|
|
|
CustomerRepository customerRepo;
|
|
|
|
PersonRepository personRepo;
|
|
|
|
Customer update(Customer customer) {
|
|
customerRepo.save(customer);
|
|
personRepo.save(customer);
|
|
return customer;
|
|
}
|
|
----
|
|
|
|
You can even wrap the `update` service method in a Spring managed transaction, either as a local cache transaction
|
|
or a global transaction.
|
|
|
|
[[mapping.pdx-serializer]]
|
|
== MappingPdxSerializer
|
|
|
|
{sdg-name} provides a custom {x-data-store-javadoc}/org/apache/geode/pdx/PdxSerializer.html[`PdxSerializer`]
|
|
implementation, called `MappingPdxSerializer`, that uses Spring Data mapping metadata to customize entity serialization.
|
|
|
|
The serializer also lets you customize entity instantiation by using the Spring Data `EntityInstantiator` abstraction.
|
|
By default, the serializer use the `ReflectionEntityInstantiator`, which uses the persistence constructor of
|
|
the mapped entity. The persistence constructor is either the default constructor, a singly declared constructor,
|
|
or a constructor explicitly annotated with `@PersistenceConstructor`.
|
|
|
|
To provide arguments for constructor parameters, the serializer reads fields with the named constructor parameter,
|
|
explicitly identified by using Spring's `@Value` annotation, from the supplied
|
|
{x-data-store-javadoc}/org/apache/geode/pdx/PdxReader.html[`PdxReader`],
|
|
as shown in the following example:
|
|
|
|
.Using `@Value` on entity constructor parameters
|
|
====
|
|
[source,java]
|
|
----
|
|
public class Person {
|
|
|
|
public Person(@Value("#root.thing") String firstName, @Value("bean") String lastName) {
|
|
…
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
An entity class annotated in this way has the "`thing`" field read from the `PdxReader` and passed as the argument value
|
|
for the constructor parameter, `firstname`. The value for `lastName` is a Spring bean with the name "`bean`".
|
|
|
|
In addition to the custom instantiation logic and strategy provided by `EntityInstantiators`,
|
|
the `MappingPdxSerializer` also provides capabilities well beyond {data-store-name}'s own
|
|
{x-data-store-javadoc}/org/apache/geode/pdx/ReflectionBasedAutoSerializer.html[`ReflectionBasedAutoSerializer`].
|
|
|
|
While {data-store-name}'s `ReflectionBasedAutoSerializer` conveniently uses Java Reflection to populate entities
|
|
and uses regular expressions to identify types that should be handled (serialized and deserialized) by the serializer,
|
|
it cannot, unlike `MappingPdxSerializer`, perform the following:
|
|
|
|
* Register custom `PdxSerializer` objects per entity field or property names and types.
|
|
* Conveniently identifies ID properties.
|
|
* Automatically handles read-only properties.
|
|
* Automatically handles transient properties.
|
|
* Allows more robust type filtering in a `null` and type-safe manner (for example, not limited to
|
|
only expressing types with regex).
|
|
|
|
We now explore each feature of the `MappingPdxSerializer` in a bit more detail.
|
|
|
|
[[mapping.pdx-serializer.custom-serialization]]
|
|
=== Custom PdxSerializer Registration
|
|
|
|
The `MappingPdxSerializer` gives you the ability to register custom `PdxSerializers` based on an entity's field
|
|
or property names and types.
|
|
|
|
For example, suppose you have defined an entity type modeling a `User` as follows:
|
|
|
|
[source,java]
|
|
----
|
|
package example.app.security.auth.model;
|
|
|
|
public class User {
|
|
|
|
private String name;
|
|
|
|
private Password password;
|
|
|
|
...
|
|
}
|
|
----
|
|
|
|
While the user's name probably does not require any special logic to serialize the value, serializing the password
|
|
on the other hand might require additional logic to handle the sensitive nature of the field or property.
|
|
|
|
Perhaps you want to protect the password when sending the value over the network, between a client and a server,
|
|
beyond TLS alone, and you only want to store the salted hash. When using the `MappingPdxSerializer`, you can register
|
|
a custom `PdxSerializer` to handle the user's password, as follows:
|
|
|
|
.Registering custom `PdxSerializers` by POJO field/property type
|
|
====
|
|
[source,java]
|
|
----
|
|
Map<?, PdxSerializer> customPdxSerializers = new HashMap<>();
|
|
|
|
customPdxSerializers.put(Password.class, new SaltedHashPasswordPdxSerializer());
|
|
|
|
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
|
|
----
|
|
====
|
|
|
|
After registering the application-defined `SaltedHashPasswordPdxSerializer` instance with the `Password`
|
|
application domain model type, the `MappingPdxSerializer` will then consult the custom `PdxSerializer`
|
|
to serialize and deserialize all `Password` objects regardless of the containing object (for example, `User`).
|
|
|
|
However, suppose you want to customize the serialization of `Passwords` only on `User` objects.
|
|
To do so, you can register the custom `PdxSerializer` for the `User` type by specifying the fully qualified name
|
|
of the `Class's` field or property, as the following example shows:
|
|
|
|
.Registering custom `PdxSerializers` by POJO field/property name
|
|
====
|
|
[source,java]
|
|
----
|
|
Map<?, PdxSerializer> customPdxSerializers = new HashMap<>();
|
|
|
|
customPdxSerializers.put("example.app.security.auth.model.User.password", new SaltedHashPasswordPdxSerializer());
|
|
|
|
mappingPdxSerializer.setCustomPdxSerializers(customPdxSerializers);
|
|
----
|
|
====
|
|
|
|
Notice the use of the fully-qualified field or property name (that is `example.app.security.auth.model.User.password`)
|
|
as the custom `PdxSerializer` registration key.
|
|
|
|
NOTE: You could construct the registration key by using a more logical code snippet, such as the following:
|
|
`User.class.getName().concat(".password");`. We recommended this over the example shown earlier.
|
|
The preceding example tried to be as explicit as possible about the semantics of registration.
|
|
|
|
[[mapping.pdx-serializer.id-properties]]
|
|
=== Mapping ID Properties
|
|
|
|
Like {data-store-name}'s `ReflectionBasedAutoSerializer`, {sdg-acronym}'s `MappingPdxSerializer` is also able to
|
|
determine the identifier of the entity. However, `MappingPdxSerializer` does so by using Spring Data's mapping metadata,
|
|
specifically by finding the entity property designated as the identifier using Spring Data's
|
|
{spring-data-commons-javadoc}/org/springframework/data/annotation/Id.html[`@Id`] annotation.
|
|
Alternatively, any field or property named "`id`", not explicitly annotated with `@Id`, is also designated as
|
|
the entity's identifier.
|
|
|
|
For example:
|
|
|
|
[source,java]
|
|
----
|
|
class Customer {
|
|
|
|
@Id
|
|
Long id;
|
|
|
|
...
|
|
}
|
|
----
|
|
|
|
In this case, the `Customer` `id` field is marked as the identifier field in the PDX type metadata by using
|
|
{x-data-store-javadoc}/org/apache/geode/pdx/PdxWriter.html#markIdentityField-java.lang.String-[`PdxWriter.markIdentifierField(:String)`]
|
|
when the `PdxSerializer.toData(..)` method is called during serialization.
|
|
|
|
[[mapping.pdx-serializer.read-only-properties]]
|
|
=== Mapping Read-only Properties
|
|
|
|
What happens when your entity defines a read-only property?
|
|
|
|
First, it is important to understand what a "`read-only`" property is. If you define a POJO by following the
|
|
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans] specification (as Spring does),
|
|
you might define a POJO with a read-only property, as follows:
|
|
|
|
[source,java]
|
|
----
|
|
package example;
|
|
|
|
class ApplicationDomainType {
|
|
|
|
private AnotherType readOnly;
|
|
|
|
public AnotherType getReadOnly() [
|
|
this.readOnly;
|
|
}
|
|
|
|
...
|
|
}
|
|
----
|
|
|
|
The `readOnly` property is read-only because it does not provide a setter method. It only has a getter method.
|
|
In this case, the `readOnly` property (not to be confused with the `readOnly` `DomainType` field)
|
|
is considered read-only.
|
|
|
|
As a result, the `MappingPdxSerializer` will not try to set a value for this property when populating an instance of
|
|
`ApplicationDomainType` in the `PdxSerializer.fromData(:Class<ApplicationDomainType>, :PdxReader)` method
|
|
during deserialization, particularly if a value is present in the PDX serialized bytes.
|
|
|
|
This is useful in situations where you might be returning a view or projection of some entity type and you only want
|
|
to set state that is writable. Perhaps the view or projection of the entity is based on authorization or some other
|
|
criteria. The point is, you can leverage this feature as is appropriate for your application's use cases
|
|
and requirements. If you want the field or property to always be written, simply define a setter method.
|
|
|
|
[[mapping.pdx-serializer.transient-properties]]
|
|
=== Mapping Transient Properties
|
|
|
|
Likewise, what happens when your entity defines `transient` properties?
|
|
|
|
You would expect the `transient` fields or properties of your entity not to be serialized to PDX when serializing
|
|
the entity. That is exactly what happens, unlike {data-store-name}'s own `ReflectionBasedAutoSerializer`,
|
|
which serializes everything accessible from the object through Java Reflection.
|
|
|
|
The `MappingPdxSerializer` will not serialize any fields or properties that are qualified as being transient, either
|
|
by using Java's own `transient` keyword (in the case of class instance fields) or by using the
|
|
{spring-data-commons-javadoc}/org/springframework/data/annotation/Transient.html[`@Transient`]
|
|
Spring Data annotation on either fields or properties.
|
|
|
|
For example, you might define an entity with transient fields and properties as follows:
|
|
|
|
[source,java]
|
|
----
|
|
package example;
|
|
|
|
class Process {
|
|
|
|
private transient int id;
|
|
|
|
private File workingDirectory;
|
|
|
|
private String name;
|
|
|
|
private Type type;
|
|
|
|
@Transient
|
|
public String getHostname() {
|
|
...
|
|
}
|
|
|
|
...
|
|
}
|
|
----
|
|
|
|
Neither the `Process` `id` field nor the readable `hostname` property are written to PDX.
|
|
|
|
[[mapping.pdx-serializer.type-filtering]]
|
|
=== Filtering by Class Type
|
|
|
|
Similar to {data-store-name}'s `ReflectionBasedAutoSerializer`, {sdg-acronym}'s `MappingPdxSerializer` lets you filter
|
|
the types of objects that are serialized and deserialized.
|
|
|
|
However, unlike {data-store-name}'s `ReflectionBasedAutoSerializer`, which uses complex regular expressions to express
|
|
which types the serializer handles, {sdg-acronym}'s `MappingPdxSerializer` uses the much more robust
|
|
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html[`java.util.function.Predicate`] interface
|
|
and API to express type-matching criteria.
|
|
|
|
TIP: If you like to use regular expressions, you can implement a `Predicate` using Java's
|
|
https://docs.oracle.com/javase/8/docs/api/java/util/regex/package-summary.html[regular expression support].
|
|
|
|
The nice part about Java's `Predicate` interface is that you can compose `Predicates` by using convenient
|
|
and appropriate API methods, including:
|
|
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#and-java.util.function.Predicate-[`and(:Predicate)`],
|
|
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#or-java.util.function.Predicate-[`or(:Predicate)`],
|
|
and https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#negate--[`negate()`].
|
|
|
|
The following example shows the `Predicate` API in action:
|
|
|
|
[source,java]
|
|
----
|
|
Predicate<Class<?>> customerTypes =
|
|
type -> Customer.class.getPackage().getName().startsWith(type.getName()); // Include all types in the same package as `Customer`
|
|
|
|
Predicate includedTypes = customerTypes
|
|
.or(type -> User.class.isAssignble(type)); // Additionally, include User sub-types (e.g. Admin, Guest, etc)
|
|
|
|
mappingPdxSerializer.setIncludeTypeFilters(includedTypes);
|
|
|
|
mappingPdxSerializer.setExcludeTypeFilters(
|
|
type -> !Reference.class.getPackage(type.getPackage()); // Exclude Reference types
|
|
----
|
|
|
|
NOTE: Any `Class` object passed to your `Predicate` is guaranteed not to be `null`.
|
|
|
|
{sdg-acronym}'s `MappingPdxSerializer` includes support for both include and exclude class type filters.
|
|
|
|
[[mapping.pdx-serializer.type-filtering.execludes]]
|
|
==== Exclude Type Filtering
|
|
|
|
By default, {sdg-acronym}'s `MappingPdxSerializer` registers pre-defined `Predicates` that filter, or exclude types
|
|
from the folliowing packages:
|
|
|
|
* `java.*`
|
|
* `com.gemstone.gemfire.*`
|
|
* `org.apache.geode.*`
|
|
* `org.springframework.*`
|
|
|
|
In addition, the `MappingPdxSerializer` filters `null` objects when calling `PdxSerializer.toData(:Object, :PdxWriter)`
|
|
and `null` class types when calling `PdxSerializer.fromData(:Class<?>, :PdxReader)` methods.
|
|
|
|
It is very easy to add exclusions for other class types, or an entire package of types, by simply defining a `Predicate`
|
|
and adding it to the `MappingPdxSerializer` as shown earlier.
|
|
|
|
The `MappingPdxSerializer.setExcludeTypeFilters(:Predicate<Class<?>>)` method is additive, meaning it composes
|
|
your application-defined type filters with the existing, pre-defined type filter `Predicates` indicated above
|
|
using the `Predicate.and(:Predicate<Class<?>>)` method.
|
|
|
|
However, what if you want to include a class type (for example, `java.security Principal`) implicitly excluded by
|
|
the exclude type filters? See <<mapping.pdx-serializer.type-filtering.includes>>.
|
|
|
|
[[mapping.pdx-serializer.type-filtering.includes]]
|
|
==== Include Type Filtering
|
|
|
|
If you want to include a class type explicitly, or override a class type filter that implicitly excludes a class type
|
|
required by your application (for example, `java.security.Principal`, which is excluded by default with the `java.*`
|
|
package exclude type filter on `MappingPdxSerializer`), then just define the appropriate `Predicate` and add it to
|
|
the serializer using `MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>)` method, as follows:
|
|
|
|
[source,java]
|
|
----
|
|
Predicate<Class<?>> principalTypeFilter =
|
|
type -> java.security.Principal.class.isAssignableFrom(type);
|
|
|
|
mappingPdxSerializer.setIncludeTypeFilters(principalTypeFilters);
|
|
----
|
|
|
|
Again, the `MappingPdxSerializer.setIncludeTypeFilters(:Predicate<Class<?>>)` method,
|
|
like `setExcludeTypeFilters(:Predicate<Class<?>>)`, is additive and therefore composes any passed type filter
|
|
using `Predicate.or(:Predicate<Class<?>>)`. This means you may call `setIncludeTypeFilters(:Predicate<Class<?>>)`
|
|
as many time as necessary.
|
|
|
|
When include type filters are present, then the `MappingPdxSerializer` makes a decision of whether to de/serialize
|
|
an instance of a class type when the class type is either not implicitly excluded OR when the class type
|
|
is explicitly included, whichever returns true. Then, an instance of the class type will be serialized
|
|
or deserialized appropriately.
|
|
|
|
For example, when a type filter of `Predicate<Class<Principal>>` is explicitly registered as shown previously,
|
|
it cancels out the implicit exclude type filter on `java.*` package types.
|