Spring Data

Spring Data is an umbrella project under the Spring portfolio, designed to provide consistent data access across a number of different data stores including relational and NoSQL Databases, and other types of data stores such as REST (HTTP), search engines, and Hadoop. Under Spring Data, there are subprojects for each specific approach and data store, put together by companies or developers of those technologies. Spring Data significantly simplifies the building of the data layer regardless of the underlying database and persistence technology.

The following table lists a few Spring Data subprojects with a short description of each:

Project

Description

Spring Data Commons

Contains a core Spring Data repository specification and supporting classes for all Spring Data projects. Specifies concepts such as repository, query, auditing, and history.

Spring Data JPA

Deals with JPA-based repositories.

Spring Data MongoDB

Provides easy integration with MongoDB, including support for query, criteria, and update DSLs.

Spring Data Redis

Integrates with the Redis in-memory data structure store, from Spring applications.

Spring Data Solr

Provides integration with Apache Solr, a powerful, open source search platform based on Apache Lucene.

Spring Data Gemfire

Provides easy integration with Pivotal Gemfire, a data management platform that provides real-time data access, reliable asynchronous event notifications, and guaranteed message delivery.

Spring Data KeyValue

Deals with key value-based data stores.

Spring Data REST

Exposes repositories with REST APIs.

The Spring Data portfolio contains community modules for more data stores that are not covered by the official Spring Data projects. Communities of several very popular open source and proprietary databases are contributing to these projects, which makes Spring Data an excellent source of proven solutions for building the data-access layer of enterprise applications regardless of the underlying data store. Cassandra, Neo4J, Couchbase, and ElasticSearch are some examples of community projects based on Spring Data.

Spring Data Commons

Spring Data standardizes data-access via all its store-specific modules (subprojects) through a consistent API called Spring Data Commons. Spring Data Commons is the foundational specification and a guideline for all Spring Data Modules. All Spring Data subprojects are store-specific implementations of Spring Data Commons.

Spring Data Commons defines the core components and general behaviors of Spring Data modules.

  • Spring Data repository specification
  • Query derivation methods
  • Web support
  • Auditing

We will examine each of these components, their setup, and usage in the following sections.

Spring Data repository specification

org.springframework.data.repository.Repository is the central interface of Spring Data abstraction. This marker interface is a part of Spring Data Commons and has two specialized extensions, CrudRepository and PagingAndSortingRepository.

public interface CrudRepository<T, ID extends Serializable>
    extends Repository<T, ID> {
    ...
}

A repository manages a domain entity (designed as a POJO). CrudRepository provides CRUD with the following CRUD operations for an entity.

  • save(One), save(List)
  • find, findOne, findAll
  • delete, deleteAll
  • count
  • exists

PagingAndSortingRepository adds pagination and sorting features over CrudRepository. It has the following two methods:

  • Page<T> findAll(Pageable)
  • Iterable<T> findAll(Sort)

Now is time to jump ahead and discuss the technology and store-specific modules of Spring Data. We are covering Spring Data JPA and Spring Data MongoDB to illustrate two totally different worlds in the database universe: relational and NoSQL. When we use a specific implementation, we use an implementation-specific repository but your method interfaces remain the same; hence, theoretically, a switch from a specific Spring Data implementation to another would not affect your client programs (service, controller, or test cases).

Spring Data JPA

Spring Data JPA is the JPA (Java Persistence Architecture)-based implementation of Spring Data, dealing with object-relational data access. For a developer, most of the programming is based on what is described in Spring Data Commons, whereas Spring Data JPA allows for some extra customizations specific to relational SQL and JPA. The main difference is in the repository setup and the query optimization using the @Query annotation.

Enabling Spring Data JPA

Enabling Spring Data JPA in your project is a simple two-step process:

  1. Add the spring-data-jpa dependency to your maven/gradle build file.
  2. Declare enable JPA repositories in your bean configuration.

In Maven, you can add a spring-data-jpa dependency as shown in the following code:

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>${spring-data-jpa.version}</version>
</dependency>

You can enable JPA repositories, as shown in the following line, if you are using XML:

  <jpa:repositories base-package="com.taskify.dao" />

In the case of Java configuration, you just annotate to enable JPA repositories.

@Configuration
@ComponentScan(basePackages = {"com.taskify"})
@EnableJpaRepositories(basePackages = "com.taskify.dao")
public class JpaConfiguration {
  ...
}

JpaRepository

After enabling JPA repositories, Spring scans the given package for Java classes annotated with @Repository, and creates fully-featured proxy objects ready to be used. These are your DAO, where you just define the methods, Spring gives you proxy-based implementations at runtime. See a simple example:

public interface TaskDAO extends JpaRepository<Task, Long>{

  List<Task> findByAssigneeId(Long assigneeId);

  List<Task> findByAssigneeUserName(String userName);
}

Spring generates smart implementations that actually perform the required database operations for these methods inside the proxy implementation, looking at the method names and arguments.

Spring Data MongoDB

MongoDB is one of the most popular document-oriented NoSQL databases. It stores data in BSON (Binary JSON) format, allowing you to store an entire complex object in nested structures, avoiding the need to break data into a lot of relational tables. Its nested object structure maps directly to object-oriented data structures and eliminates the need for any object-relational mapping, as is the case with JPA/Hibernate.

Spring Data MongoDB is the Spring Data module for MongoDB. It allows Java objects to be mapped directly into MongoDB documents. It also provides a comprehensive API and infrastructural support for connecting to MongoDB and manipulating its document collections.

Enabling Spring Data MongoDB

Spring Data MongoDB can be enabled with the following steps:

  1. Add spring-data-mongodb to your build file (maven/gradle).
  2. Register a Mongo instance in your Spring metadata configuration.
  3. Add a mongoTemplate Spring Bean to your Spring metadata.

Adding the spring-data-mongodb dependency with Maven should look like this:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${spring.framework.version}</version>
</dependency>

You can register a Mongo instance in your XML metadata, as shown in the following line:

<mongo:mongo host="192.168.36.10" port="27017" />

This Mongo instance is a proxy of your actual MongoDB instance running on a server.

A simplistic mongoTemplate looks like the listing given in the following code:

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg ref="mongo" />
  <constructor-arg name="databaseName" value="Taskify" />
</bean>

MongoRepository

MongoRepository is the MongoDB-specific repository for Spring Data MongoDB. It looks very similar to JpaRepository. Take a look at a sample MongoRepository class:

public interface TaskDAO extends MongoRepository<Task, String>{

  List<Task> findByAssigneeId(String assigneeId);

  @Query("{ 'status' : 'Complete' }")
  List<Task> findCompletedTasks();

  @Query(value = "{ 'status' : 'Open', assignee.id: ?0 }")
  List<Task> findOpenTasksByAssigneeId(String assigneeId);
  ...
}

Domain objects and entities

Data-driven applications often design domain objects as entities and then persist them into databases either as relational tables or document structures of key-value pairs at runtime. Spring Data deals with domain entities like any other persistence framework. In order to illustrate the usage of a repository, we will refer to the following three related entities, designed as Plain Old Java Objects (POJOs) in your program.

Domain objects and entities

The following are the Java representations. The first one is annotated for JPA and the other two for MongoDB. JPA entities are annotated with @Entity. Columns are mapped against each field. Remember that, instead of annotations, you can use XML-based mapping for JPA entities too. XML mapping offers several benefits including centralized control and maintainability. This example uses annotations for simplicity, assuming that the reader is already familiar with JPA or Hibernate mappings.

@Entity
@Table(name = "TBL_USER", uniqueConstraints = @UniqueConstraint(name = "UK_USER_USERNAME", columnNames = {"USER_NAME" }) )
public class User {

  @Id
  @SequenceGenerator(name = "SEQ_USER", sequenceName = "SEQ_USER", allocationSize = 1, initialValue=1001)
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_USER")
  private Long id;

  @Column(name = "NAME", length = 200)
  private String name;

  @Column(name = "USER_NAME", length = 25)
  private String userName;

  @Column(name = "PASSWORD", length = 20)
  private String password;

  @Column(name = "DOB")
  @Temporal(TemporalType.TIMESTAMP)
  private Date dateOfBirth;

  @ManyToOne(optional = true)
  @JoinColumn(name = "FILE_ID", referencedColumnName = "ID")
  private File profileImage;

  public User() {}

  public User(Long id, String name, String userName, String password, Date dateOfBirth) {
    super();
    this.id = id;
    this.name = name;
    this.userName = userName;
    this.password = password;
    this.dateOfBirth = dateOfBirth;
  }

  public Long getId() {
    return id;
  }
  ...
}

The following is the task entity, annotated as a MongoDB document. Mongo entities are annotated with @Document. It requires an ID field, either annotated with @Id or with the name id.

@Document(collection = "tasks")
public class Task {

  @Idprivate String id;
  private String name;
  private int priority;
  private String status;
  private User createdBy;
  private Date createdDate;
  private User assignee;
  private Date completedDate;
  private String comments;

  public Task() {}
  ...
}

The file entity is annotated as a JPA entity.

@Entity
@Table(name = "TBL_FILE")
public class File {

  @Id
  @SequenceGenerator(name = "SEQ_FILE", sequenceName = "SEQ_FILE", allocationSize = 1)
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_FILE")
  private Long id;

  @Column(name = "FILE_NAME", length = 200)
  private String fileName;
  ...
}

Query resolution methods

In addition to the declared query (find, count, delete, remove, and exists) methods at interface level, CrudRepository supports declared queries using the @Query annotation methods with any name, which helps to derive the actual SQL queries from the SpEL (Spring Expression Language) expression given as a parameter. Of these two query deriving options, Spring Data adopts one based on the following query lookup strategies:

Query Lookup Strategy

Description

CREATE

Generates module-specific queries from the method name.

USE_DECLARED_QUERY

Uses a query declared by an annotation or some other means.

CREATE_IF_NOT_FOUND

This strategy combines the first two. This is the default strategy.

The query lookup strategy is normally set while enabling JPA repositories.

<jpa:repositories base-package="com.taskify.dao" query-lookup-strategy="create-if-not-found"/>

The query generation strategy (CREATE) works around the properties of the entity, including their dependencies, in a nested direction. As a developer, you define method names based on a specific format that can be interpreted and realized by Spring Data. The general structure of the query method is shown here:

[return Type] [queryType][limitKeyword]By[criteria][OrderBy][sortDirection]

  • return type can be the entity <T> itself (in the case of a unique result), a list <T>, a stream <T>, page <T>, primitive numbers, Java wrapper types, void, future <T>, CompletableFuture<T>, ListenableFuture<T>, and so on. The last three are for Spring's asynchronous method execution and should be annotated with @Async.
  • queryType can be find, read, query, count, exists, delete, and so on.
  • limitKeyword supports distinct, First[resultSize], and Top[resultSize]. An example is First5.
  • criteria is built by combining one or more property expressions (using camel-casing) with standard operators such as Or, And, Between, GreaterThan, LessThan, IsNull, StartsWith, and Exists. Criteria can be suffixed by IgnoreCase or AllIgnoreCase, to apply case insensitivity.
  • OrderBy is used as it is, suffixed by property expressions.
  • sortDirection can be either of Asc or Desc. This is used only with OrderBy.

Let's see some examples for better clarity. The following sample code illustrates how to construct query (or delete) methods so that Spring Data can generate the actual SQL query at runtime.

public interface UserDAO extends JpaRepository<User, Long> {

  // Returns unique user with given user-name
  User findByUserName(String userName);

  // Returns a paginated list of users whose name starts with // given value
  Page<User> findByNameStartsWith(String name, Pageable pageable);

  // Returns first 5 users whose name starts with given value, 
  // order by name descending
  List<User> findTop5ByNameStartsWithOrderByNameDesc(String name);

  // Returns number of users whose birth date is before the given // value
  Long countUsersDateOfBirthLessThan(Date dob);

  // Deletes the User of given id
  void deleteById(Long userId);

  // Asynchronously returns a list of users whose name contains // the given value
  @Async
  Future<List<User>> findByNameContains(String name);
}

The preceding example showing JpaRepository and MongoRepository works in the same way; you just need to extend from it, without changing the method signatures. You have seen the constraining query and filter methods traversing root-level properties of the entity, combining operators appropriately. Besides root-level properties, you can traverse and filter by nested properties as well, to define query constraints, in other words, limiting the result. Take a look at the following example:

public interface TaskDAO extends MongoRepository<Task, String>{

  List<Task> findByAssigneeId(Long assigneeId);

  List<Task> findByAssigneeUserName(String userName);
}

The methods listed in the preceding example are traversing nested properties of the task entity:

  • findByAssigneeId = task.assignee.id
  • findByAssigneeUserName = task.assignee.userName

You can traverse into any level of nested elements of your entity, depending on how complex your entity and requirements are.

Using the @Query annotation

Besides the autogeneration of queries based on method names as demonstrated in the previous section, Spring Data allows you to declare queries for entities locally, directly in the repository itself, over the method names. You declare the query using SpEL, and Spring Data interprets it at runtime and (the proxy repository) generates the queries for you. This is an implementation of the query resolution strategy: USE_DECLARED_QUERY.

Let's take a look at some self-explanatory examples:

public interface TaskDAO extends JpaRepository<Task, Long>{	
  
  @Query("select t from Task t where status = 'Open'")
  List<Task> findOpenTasks();

  @Query("select t from Task t where status = 'Complete'")
  List<Task> findCompletedTasks();

  @Query("select count(t) from Task t where status = 'Open'")
  int findAllOpenTasksCount();

  @Query("select count(t) from Task t where status = 'Complete'")
  int findAllCompletedTasksCount();

  @Query("select t from Task t where status = 'Open' and assignee.id = ?1")
  List<Task> findOpenTasksByAssigneeId(Long assigneeId);

  @Query("select t from Task t where status = 'Open' and assignee.userName = ?1")
  List<Task> findOpenTasksByAssigneeUserName(String userName);

  @Query("select t from Task t where status = 'Complete' and assignee.id = ?1")
  List<Task> findCompletedTasksByAssigneeId(Long assigneeId);

  @Query("select t from Task t where status = 'Complete' and assignee.userName = ?1")
  List<Task> findCompletedTasksByAssigneeUserName(String userName);
}

You can see from the preceding example that we can traverse into nested properties to constrain the queries, in the criteria part of it. You can also have both query generation strategies (CREATE and USE_DECLARED_QUERY) in the same repository.

The preceding example was based on Spring Data JPA; the Spring Data MongoDB equivalent is given in the following code. You can see how the @Query annotation values differ in comparison to the MongoDB structure.

public interface TaskDAO extends MongoRepository<Task, String>{

  @Query("{ 'status' : 'Open' }")
  List<Task> findOpenTasks();

  @Query("{ 'status' : 'Complete' }")
  List<Task> findCompletedTasks();

  @Query(value = "{ 'status' : 'Open' }", count = true)
  int findAllOpenTasksCount();

  @Query(value = "{ 'status' : 'Complete' }", count = true)
  int findAllCompletedTasksCount();

  @Query(value = "{ 'status' : 'Open', assignee.id: ?0 }")
  List<Task> findOpenTasksByAssigneeId(String assigneeId);

  @Query(value = "{ 'status' : 'Open', assignee.userName: ?0 }")
  List<Task> findOpenTasksByAssigneeUserName(String userName);

  @Query(value = "{ 'status' : 'Complete', assignee.id: ?0 }")
  List<Task> findCompletedTasksByAssigneeId(String assigneeId);

  @Query(value = "{ 'status' : 'Open', assignee.userName: ?0 }")
  List<Task> findCompletedTasksByAssigneeUserName(String userName);
}

Spring Data web support extensions

Spring Data provides a smart extension called SpringDataWebSupport to Spring MVC applications, integrating a few productivity components automatically if you enable it. It primarily resolves domain entities as Pageable and Sort instances with request-mapping controller methods directly from request parameters, if you are using Spring Data repository programming model for data access.

You need to enable SpringDataWebSupport for your project before you can use the features. You can annotate @EnableSpringDataWebSupport, as shown in the following code, if you are using a Java configuration:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.taskify"})
@EnableSpringDataWebSupport
@EnableJpaRepositories(basePackages = "com.taskify.dao")
public class ApplicationConfiguration {
 ...
}

In the case of XML metadata, you can register SpringDataWebConfiguration as a Spring bean, as shown in the following code:

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

Once you set up SpringDataWebSupport, you can start using Spring Data entities as request arguments with request-mapping methods, as shown in the following code:

@RestController
@RequestMapping("/api/v1/user")
@CrossOrigin
public class UserController {

  @RequestMapping(path = "/{id}", method = RequestMethod.GET)
  public User getUser(@PathVariable("id") User user) {
    return user;
  }
  ...
}

In the preceding method, you can see that Spring Data loads the User entity data using UserRepository transparently for you. Similarly, you can accept Pageable and Sort instances against JSON or XML post requests. Wise usage of the SpringDataWebSupport extension makes your code cleaner and more maintainable.

Auditing with Spring Data

Tracking data modifications is a critical feature of serious business applications. Administrators and managers are anxious to know when and who changed certain business information saved in the database. Spring Data provides smart and easy methods for auditing data entities transparently. Spring Data ships the following meaningful annotations for capturing modified user and time data entities in the system:

Annotation

Expected type

@CreatedBy

The principal user who created the entity. Typically, it is another entity that represents the domain user.

@CreatedDate

Records when the entity is created. Supported types: java.util.Date, calendar, JDK 8 date/time types, Joda DateTime.

@LastModifiedBy

The user principal who last updated the entity. It is the same type as @CreatedBy.

@LastModifiedDate

Records when the entity was last updated. Supported types are the same as for @CreatedDate.

A typical JPA entity should look like the following code:

@Entity
@Table(name = "tbl_task")
public class Task {

  @Id
  private Long id;
  ...
  @ManyToOne(optional = true)
  @JoinColumn(name = "CREATED_USER_ID", referencedColumnName = "ID")
  @CreatedBy
  private User createdBy;

  @Column(name = "CREATED_DATE")
  @Temporal(TemporalType.TIMESTAMP)
  @CreatedDate
  private Date createdDate;

  @ManyToOne(optional = true)
  @JoinColumn(name = "MODIFIED_USER_ID", referencedColumnName = "ID")
  @LastModifiedBy
  private User modifiedBy;

  @Column(name = "MODIFIED_DATE")
  @Temporal(TemporalType.TIMESTAMP)
  @LastModifiedDate
  private Date modifiedDate;
  ...
}

If you are using XML instead of annotations to map your entities, you can either implement an auditable interface, which forces you to implement the audit metadata fields, or extend AbstractAuditable, a convenient base class provided by Spring Data.

Since you are recording the information of the user who is creating and modifying entities, you need to help Spring Data to capture that user information from the context. You need to register a bean that implements AuditAware<T>, where T is the same type of field that you annotated with @CreatedBy and @LastModifiedBy. Take a look at the following example:

@Component
public class SpringDataAuditHelper implements AuditorAware<User> {

  ...
  @Override
  public User getCurrentAuditor() {
    // Return the current user from the context somehow.
  }

}

If you are using Spring Security for authentication, then the getCurrentAuditor method should get and return the user from the SecurityContextHolder class, as follows:

@Component
public class SpringDataAuditHelper implements AuditorAware<User> {

  ...
  @Override
  public User getCurrentAuditor() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }
    return ((User) authentication.getPrincipal()).getUser();
  }
}

Now your auditing infrastructure is ready, any modification you make in your auditable entities will be tracked transparently by Spring Data.

So far you have mastered the mighty Spring Data and you know how to create elegant and clean yet really powerful data access layers with Spring Data repositories, so now it is time to think about how to ensure the data integrity and reliability of your application. Spring Transaction is the answer; let's explore it in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset