Skip to content
Oliver Gierke edited this page Sep 19, 2012 · 6 revisions

Introduction

Mapping and conversion system

The Spring Data Commons module provides a sophisticated system to gather and use entity mapping and conversion functionality. The core model abstractions are PersistentEntity, PersistentProperty. These abstractions can be created and used through a MappingContext. In top of that we provide an EntityConverter abstraction consisting of EntityReader and EntityWriter.

Core mapping abstractions

TODO

Entity instantiation

As an important part of the entity conversion on the reading side is creating instances of the domain class an EntityInstantiator API allows plugging custom code for

Storing type information

By default the conversion system inspects the properties of the type being read to find out which nested types it has to instantiate. So assume the following domain classes:

class Address { … }

class Person {
  Address address;
}

If the domain model works with type hierarchies, inspecting the property types might not be sufficient to read data into an object graph. Assume the domain model slightly modified and a Person instance created as follows:

class Address { … }

class Person {
  Object address;
}

Person person = new Person();
person.address = new Address();

In this case there are two important things to consider: first, the type information of the address has to be stored when persisting the Person instance. Second, when reading the object we need to detect the type to be instantiated for the data eventually forming the Address instance.

To achieve this, Spring Data Commons provides the TypeMapper API. The interface has to be typed to the actual store's native data representation (e.g. a DBObject in MongoDB). It's core responsibility is writing type information to the store object and read it back in in turn. We also ship a configurable DefaultTypeMapper that takes a TypeAliasAccessor, a MappingContext as well as TypeInformationMapper implementations to delegate to.

The TypeAliasAccessor is the core interface to implement how a type alias gets actually persisted to the store (through writeTypeTo(…))and read back in (through readTypeAlias(…)). It is not responsible to interpret the token written, it's solely encapsulating the reading and writing aspect and has to be implemented in a store-specific way.

The type alias is some arbitrary token which can be mapped to a Java type in a unique way. This can be the fully-qualified Java type name or another unique hash-like token.

The TypeInformationMapper is responsible for mapping the arbitrary alias into a TypeInformation instance, i.e. actually resolving the type. Spring Data Commons ships with a variety of implementations for that out of the box:

  • SimpleTypeInformationMapper - stores the fully-qualified class name on writing and interpreting the token returned by the TypeAliasAccessor as class name and trying to load the class.
  • MappingContextTypeInformationMapper - uses a MappingContext which inspects the entities persisted for the @TypeAlias annotation. It will return the value configured if annotated or null if no type alias information is present with the PersistentEntity (see PersistentEntity#getTypeAlias())
  • ConfigurableTypeInformationMapper - takes a Map<? extends Class<?>, String> to register a manual mapping from a type to a given alias.

These implementations can be configured as a chain on the DefaultTypeMapper. The class will then consult them in a row using the alias or type of the one returning a non-null value. This allows some types being mapped into aliases by @TypeAlias whereas all others fall back on the fully-qualified class name.

However, there a a few constructors on the DefaultTypeMapper that make the setup quite easy. The one taking a TypeAliasAccessor registers a SimpleTypeInformationMapper by default. Assume we have a TypeAliasAccessor for a Maplike this:

class MapTypeAliasAccessor implements TypeAliasAccessor<Map<String, Object>> {

  public static final String TYPE_ALIAS_KEY = "_class";

  public Object readAliasFrom(Map<String, Object> source) {
    return source.get(TYPE_ALIAS_KEY);
  }

  public void writeTypeTo(Map<String, Object> sink, Object alias) {
    sink.put(TYPE_ALIAS_KEY, alias);
  }
}

We could the setup a DefaultTypeMapper instance and use it as follows:

MapTypeAliasAccessor accessor = new MapTypeAliasAccessor();
TypeMapper<Map<String, Object> mapper = new DefaultTypeMapper<Map<String, Object>(accessor);

Map<String, Object> store = new HashMap<String, Object>();
mapper.writeType(HashMap.class, store);

// Make sure we have the type information captured
assertThat(store.get(TYPE_ALIAS_KEY), is(HashMap.class.getName()));

// Make sure we can obtain the type from the plain store source
assertThat(mapper.readType(store), is(ClassTypeInformation.from(HashMap.class)));

If we hand it a MappingContext and our Address is annotated with @TypeAlias("A"), then the DefaultTypeMapper would work as follows:

MapTypeAliasAccessor accessor = new MapTypeAliasAccessor();
MappingContext<?, ?> context = … // obtain MappingContext
TypeMapper<Map<String, Object> mapper = new DefaultTypeMapper<Map<String, Object>(accessor, context, Collections.emptyList());

Map<String, Object> store = new HashMap<String, Object>();
mapper.writeType(Address.class, store);

// Make sure the alias is written, not the class name
assertThat(store.get(TYPE_ALIAS_KEY, is("A")));

// Make sure we discover Address to be the target ty
assertThat(mapper.readType(store), is(ClassTypeInformation.from(Address.class)));

Note, that storing type information for Person would still store the fully-qualified class name as the MappingContext does not find any type alias mapping information.

Implementation patterns

Usually EntityConverter implementations will setup a DefaultTypeMapper in their constructors and hand in a MappingContext so that type information is transparently stored as fully-qualified class names and consider the customization via @TypeAlias out-of-the box. The only store specific part is the TypeAliasAccessor implementation, which has to be adapted according to the store's data structure. Have a look at MongoDB's DBObjectTypeAliasAccessor for example.

Beyond that it's good practice to expose the TypeMapper as configurable property of the EntityConverter implementation so that uses gain full control over the type mapping setup if necessary.

Repository abstraction

Repository interfaces

We provide a set of repository interfaces that either declare a user's repository interface as a Spring Data interface or even pull in functionality that can be implemented generically.

  • Repository - A plain marker interface to let the Spring Data infrastructure pick up user defined repositories.
  • CrudRepository - Extends Repository and adds basic persistence methods like saving entities, finding entities and deleting them.
  • PagingAndSortingRepositories - Extends CrudRepository and adds method for access ing entities page by page and sorted by a given criteria.

Web integration

TODO

Building a store implementation

When building a store implementation for a data store we do not already support the most interesting parts are the mapping and conversion system as well as the repository abstraction. If the store you target already supports entity mapping (like JPA for example) you can implement the repository abstraction directly on top of it. Otherwise you need to integrate with the mapping and conversion system. The following sections will describe the important abstractions and classes you'll have to take a look and and extend/implement.

As example for an implementation of store support with Spring Data mapping have a look at Spring Data MongoDB, for a plain repository abstraction integration consider taking a look at Spring Data JPA.

Mapping and conversion system

TODO

Repository abstraction

Basic support

The very core of the repository abstraction is the factory to create repository instances. RepositoryFactorySupport requires the following methods to be implemented:

  • getEntityInformation(…) - return the EntityInformation which encapsulates ways to determine whether an entity is new, lookup the identifier of the entity as well as the type of the id. AbstractEntityInformation is the class you'll probably want to extend.
  • getRepositoryBaseClass(…) - returns the type of the backing instance of the repositories which usually implements CRUD methods etc. Needed to inspect the user's repository interface for query methods before actually creating the instance.
  • getTargetRepository(…) - returns an instance of the type returned by getRepositoryBaseClass(…). This instance will usually return one of the Spring Data Repository interfaces.

Query methods

TODO

Spring namespaces

TODO

Clone this wiki locally