Skip to content

DATACMNS-830 - Update documentation for Spring Data Commons 1.12. #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.12.0.BUILD-SNAPSHOT</version>
<version>1.12.0.DATACMNS-830-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
163 changes: 162 additions & 1 deletion src/main/asciidoc/repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,115 @@ In this first step you defined a common base interface for all your domain repos

NOTE: Note, that the intermediate repository interface is annotated with `@NoRepositoryBean`. Make sure you add that annotation to all repository interfaces that Spring Data should not create instances for at runtime.


[[repositories.multiple-modules]]
=== Using Repositories with multiple Spring Data modules

Using a unique Spring Data module in your application makes things simple hence, all repository interfaces in the defined scope are bound to the Spring Data module. Sometimes applications require using more than one Spring Data module. In such case, it's required for a repository definition to distinguish between persistence technologies. Spring Data enters strict repository configuration mode because it detects multiple repository factories on the class path. Strict configuration requires details on the repository or the domain class to decide about Spring Data module binding for a repository definition:

1. If the repository definition <<repositories.multiple-modules.types,extends the module-specific repository>>, then it's a valid candidate for the particular Spring Data module.
2. If the domain class is <<repositories.multiple-modules.annotations,annotated with the module-specific type annotation>>, then it's a valid candidate for the particular Spring Data module. Spring Data modules accept either 3rd party annotations (such as JPA's `@Entity`) or provide own annotations such as `@Document` for Spring Data MongoDB/Spring Data Elasticsearch.

[[repositories.multiple-modules.types]]
.Repository definitions using Module-specific Interfaces
====
[source, java]
----
interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
}

interface UserRepository extends MyBaseRepository<User, Long> {
}
----
`MyRepository` and `UserRepository` extend `JpaRepository` in their type hierarchy. They are valid candidates for the Spring Data JPA module.
====

.Repository definitions using generic Interfaces
====
[source, java]
----
interface AmbiguousRepository extends Repository<User, Long> {
}

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
}

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
}
----
`AmbiguousRepository` and `AmbiguousUserRepository` extend only `Repository` and `CrudRepository` in their type hierarchy. While this is perfectly fine using a unique Spring Data module, multiple modules cannot distinguish to which particular Spring Data these repositories should be bound.
====

[[repositories.multiple-modules.annotations]]
.Repository definitions using Domain Classes with Annotations
====
[source, java]
----
interface PersonRepository extends Repository<Person, Long> {
}

@Entity
public class Person {
}

interface UserRepository extends Repository<User, Long> {
}

@Document
public class User {
}
----
`PersonRepository` references `Person` which is annotated with the JPA annotation `@Entity` so this repository clearly belongs to Spring Data JPA. `UserRepository` uses `User` annotated with Spring Data MongoDB's `@Document` annotation.
====

.Repository definitions using Domain Classes with mixed Annotations
====
[source, java]
----
interface JpaPersonRepository extends Repository<Person, Long> {
}

interface MongoDBPersonRepository extends Repository<Person, Long> {
}

@Entity
@Document
public class Person {
}
----
This example shows a domain class using both JPA and Spring Data MongoDB annotations. It defines two repositories, `JpaPersonRepository` and `MongoDBPersonRepository`. One is intended for JPA and the other for MongoDB usage. Spring Data is no longer able to tell the repositories apart which leads to undefined behavior.
====

<<repositories.multiple-modules.types,Repository type details>> and <<repositories.multiple-modules.annotations,identifying domain class annotations>> are used for strict repository configuration identify repository candidates for a particular Spring Data module. Using multiple persistence technology-specific annotations on the same domain type is possible to reuse domain types across multiple persistence technologies, but then Spring Data is no longer able to determine a unique module to bind the repository.

The last way to distinguish repositories is scoping repository base packages. Base packages define the starting points for scanning for repository interface definitions which implies to have repository definitions located in the appropriate packages. By default, annotation-driven configuration uses the package of the configuration class. The <<repositories.create-instances.spring,base package in XML-based configuration>> mandatory.

.Annotation-driven configuration of base packages
====
[source, java]
----
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
----
====

[[repositories.query-methods.details]]
== Defining query methods

Expand Down Expand Up @@ -632,6 +741,58 @@ A corresponding attribute is available in the XML namespace.

This section documents a set of Spring Data extensions that enable Spring Data usage in a variety of contexts. Currently most of the integration is targeted towards Spring MVC.

[[core.extensions.querydsl]]
=== Querydsl Extension

http://www.querydsl.com/[Querydsl] is a framework which enables the construction of statically typed SQL-like queries via its fluent API.

Several Spring Data modules offer integration with Querydsl via `QueryDslPredicateExecutor`.

.QueryDslPredicateExecutor interface
====
[source, java]
----
public interface QueryDslPredicateExecutor<T> {

T findOne(Predicate predicate); <1>

Iterable<T> findAll(Predicate predicate); <2>

long count(Predicate predicate); <3>

boolean exists(Predicate predicate); <4>

// … more functionality omitted.
}
----
<1> Finds and returns a single entity matching the `Predicate`.
<2> Finds and returns all entities matching the `Predicate`.
<3> Returns the number of entities matching the `Predicate`.
<4> Returns if an entity that matches the `Predicate` exists.
====

To make use of Querydsl support simply extend `QueryDslPredicateExecutor` on your repository interface.

.Querydsl integration on repositories
====
[source, java]
----
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {

}
----
====

The above enables to write typesafe queries using Querydsl `Predicate` s.

[source, java]
----
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);
----

[[core.web]]
=== Web support

Expand Down Expand Up @@ -799,7 +960,7 @@ Assume we have 30 Person instances in the database. You can now trigger a reques
You see that the assembler produced the correct URI and also picks up the default configuration present to resolve the parameters into a `Pageable` for an upcoming request. This means, if you change that configuration, the links will automatically adhere to the change. By default the assembler points to the controller method it was invoked in but that can be customized by handing in a custom `Link` to be used as base to build the pagination links to overloads of the `PagedResourcesAssembler.toResource(…)` method.

[[core.web.type-safe]]
==== QueryDSL web support
==== Querydsl web support

For those stores having http://www.querydsl.com/[QueryDSL] integration it is possible to derive queries from the attributes contained in a `Request` query string.

Expand Down
163 changes: 163 additions & 0 deletions src/main/asciidoc/repository-projections.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
[[projections]]
= Projections

Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.

Look at the following domain model:

[source,java]
----
@Entity
public class Person {

@Id @GeneratedValue
private Long id;
private String firstName, lastName;

@OneToOne
private Address address;
}

@Entity
public class Address {

@Id @GeneratedValue
private Long id;
private String street, state, country;

}
----

This `Person` has several attributes:

* `id` is the primary key
* `firstName` and `lastName` are data attributes
* `address` is a link to another domain object

Now assume we create a corresponding repository as follows:

[source,java]
----
interface PersonRepository extends CrudRepository<Person, Long> {

Person findPersonByFirstName(String firstName);
}
----

Spring Data will return this domain object including all of its attributes. There are two options just to retrieve the `address` attribute. One option is to define a repository for `Address` objects like this:

[source,java]
----
interface AddressRepository extends CrudRepository<Address, Long> {}
----

In this situation, using `PersonRepository` will still return the whole `Person` object. Using `AddressRepository` will return just the `Address`.

However, what if you don't want to expose `address` details at all? You can offer the consumer of your repository service an alternative by defining one or more projections.

.Simple Projection
====
[source,java]
----
interface NoAddresses { <1>

String getFirstName(); <2>

String getLastName(); <3>
}
----
This projection has the following details:

<1> It's a plain Java interface making it declarative.
<2> It exports the `firstName`.
<3> It exports the `lastName`.
====

The `NoAddresses` projection only has getters for `firstName` and `lastName` meaning that it will not serve up any address information. The query method definition returns in this case `NoAdresses` instead of `Person`.

[source,java]
----
interface PersonRepository extends CrudRepository<Person, Long> {

NoAddresses findByFirstName(String firstName);
}
----

Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it's required to name getter methods according to the property name of the underlying type. If the underlying property is named `firstName`, then the getter method must be named `getFirstName` otherwise Spring Data is not able to look up the source property. This type of projection is also called _closed projection_. Closed projections expose a subset of properties hence they can be used to optimize the query in a way to reduce the selected fields from the data store. The other type is, as you might imagine, an _open projection_.

[[projections.remodelling-data]]
== Remodelling data

So far, you have seen how projections can be used to reduce the information that is presented to the user. Projections can be used to adjust the exposed data model. You can add virtual properties to your projection. Look at the following projection interface:

.Renaming a property
====
[source,java]
----
interface RenamedProperty { <1>

String getFirstName(); <2>

@Value("#{target.lastName}")
String getName(); <3>
}
----
This projection has the following details:

<1> It's a plain Java interface making it declarative.
<2> It exports the `firstName`.
<3> It exports the `name` property. Since this property is virtual it requires `@Value("#{target.lastName}")` to specify the property source.
====

The backing domain model does not have this property so we need to tell Spring Data from where this property is obtained.
Virtual properties are the place where `@Value` comes into play. The `name` getter is annotated with `@Value` to use SpEL expressions pointing to the backing property `lastName`. You may have noticed `lastName` is prefixed with `target.` which is the variable name pointing to the backing object. Using `@Value` on methods allows defining where and how the value is obtained.

Some applications require the full name of a person. Concatenating strings with `String.format("%s %s", person.getFirstName(), person.getLastName())` would be one possibility but this piece of code needs to be called in every place the full name is required. Virtual properties on projections leverage the need for repeating that code all over.

[source,java]
----
interface FullNameAndCountry {

@Value("#{target.firstName} #{target.lastName}")
String getFullName();

@Value("#{target.address.country}")
String getCountry();
}
----

In fact, `@Value` gives full access to the target object and its nested properties. SpEL expressions are extremly powerful as the definition is always applied to the projection method. Let's take SpEL expressions in projections to the next level.


Imagine you had the following domain model definition:

[source,java]
----
@Entity
public class User {

@Id @GeneratedValue
private Long id;
private String name;

private String password;
}
----

IMPORTANT: This example may seem a bit contrived, but it's possible with a richer domain model and many projections, to accidentally leak such details. Since Spring Data cannot discern the sensitivity of such data, it is up to the developers to avoid such situations. Storing a password as plain-text is discouraged. You really shouldn't do this. For this example, you could also replace `password` with anything else that is secret.

In some cases, you might keep the `password` as secret as possible and not expose it more than it should be. The solution is to create a projection using `@Value` together with a SpEL expression.

[source,java]
----
interface PasswordProjection {
@Value("#{(target.password == null || target.password.empty) ? null : '******'}")
String getPassword();
}
----

The expression checks whether the password is `null` or empty and returns `null` in this case, otherwise six asterisks to indicate a password was set.