Spring Data Repositories

Introduction

Recently I got an opportunity to work with spring data repositories. Its an excellent feature of Spring. you no longer need to manage writing basic crud operation logic for your application with this architecture. In a way like Hibernate sans the clumsy .hbm files and any sort of database awareness. 

The key components of this architecture are :
  • Repository - This class would correspond to the Database and extends  PagingAndSortingRepository 
  • Domain - The object for which crud operations are needed
  • Delegate - This class would be a handler for handling the request and returning the response. The implementation of handler would have the exact logic of the business involved.
The goal of Spring Data repository abstraction is to significantly reduce the amount of 
unintelligent code required to implement data access layers for various persistence stores.

Key Concepts

The central interface here would be the Repository. The domain class and id type of the domain class is used as arguments for repository. This interface is typically a marker interface. The CrudRepository provides sophisticated CRUD functionality for the entity class that is being managed. 




Apart from CrudRepository there is a PagingAndSortingRepository abstraction that adds additional methods to ease paginated access to entities:



Accessing the second page of User by a page size of 20 you could simply do something like this:

Query methods

Standard CRUD functionality repositories usually have queries on the underlying datastore. With Spring Data, declaring those queries becomes a four-step process:
  1. Declare an interface extending Repository or one of its subinterfaces and type it to the domain class that it will handle.
  2. Declare query methods on the interface.
3. Set up Spring to create proxy instances for those interfaces in the applicationContext.xml
4. Get the repository instance injected and use it.

Defining query methods

The repository proxy has two ways to derive a store-specific query from the method name. It can derive the query from the method name directly, or by using an additionally created query. Available options depend on the actual store. The available strategies are listed below:

Query lookup strategies

You can configure the strategy at the namespace through the query-lookup-strategy attribute. 

CREATE

CREATE attempts to construct a store-specific query from the query method name. The general approach is to remove a given set of well-known prefixes from the method name and parse the rest of the method. 

USE_DECLARED_QUERY

USE_DECLARED_QUERY tries to find a declared query and will throw an exception in case it can't find one. The query can be defined by an annotation somewhere or declared by other means. If the repository infrastructure does not find a declared query for the method at bootstrap time, it fails.

CREATE_IF_NOT_FOUND (default)

CREATE_IF_NOT_FOUND combines CREATE and USE_DECLARED_QUERY. It looks up a declared query first, and if no declared query is found, it creates a custom method name-based query. This is the default lookup strategy and thus will be used if you do not configure anything explicitly. It allows quick query definition by method names but also custom-tuning of these queries by introducing declared queries as needed.

Query creation

Defining queries with this strategy is a cakewalk. All you need to do is pick up an appropriate name for your query method which abides by the naming convention.
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…Byread…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or .

Example 
public interface PersonRepository extends Repository {

  List findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List findByLastnameOrderByFirstnameAsc(String lastname);
  List findByLastnameOrderByFirstnameDesc(String lastname);
}

The actual result of parsing the method depends on the persistence store for which you create the query. A few more salient features of this query method are:
  • You can combine property expressions with AND and OR. You also get support for operators such as BetweenLessThanGreaterThanLike for the property expressions. 
  • The method parser supports setting an IgnoreCase flag for individual properties, for example,findByLastnameIgnoreCase(…)) or for all properties of a type that support ignoring case (usually Strings, for example, findByLastnameAndFirstnameAllIgnoreCase(…)). 
  • You can apply static ordering by appending an OrderBy clause to the query method that references a property and by providing a sorting direction (Asc or Desc). 

Property expressions

Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Assume Persons have Addresses with ZipCodes. In that case a method name of
List findByAddressZipCode(ZipCode zipCode);
Although this should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the Person class has an addressZip property as well. The algorithm would match in the first split round already and essentially choose the wrong property and finally fail (as the type of addressZip probably has no code property). To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would end up like so:
List findByAddress_ZipCode(ZipCode zipCode);

Special parameter handling

To handle parameters to your query you simply define method parameters as already seen in the examples above. Besides that the infrastructure will recognize certain specific types like Pageable and Sort to apply pagination and sorting to your queries dynamically.

Page findByLastname(String lastname, Pageable pageable);

List findByLastname(String lastname, Sort sort);

List findByLastname(String lastname, Pageable pageable);

References

  1. http://docs.spring.io/spring-data/data-commons/docs/1.6.2.RELEASE/reference/html/repositories.html
  2. http://projects.spring.io/spring-data/

Comments

  1. Liked it in parts, not yet gone through the links though.
    As I see, the approach looks good when project requires access from one table at a time. In reality, most projects would require complex joins of tables to render results.

    So, question is, can above be extended to support table joins (say, PersonRepository and EmployeeRepository). If yes, how smooth is the way to achieve it - one of my major complains with Hibernate. If no, how does integration of Spring Data work with other ORM approach (used to achieve joins) in the project.

    Also, would be curious to know about keywords associated to Limit the SQL ResultSet. Guess, PagingAndSortingRepository would take care of it.

    Thanks for the informative post,
    Joshi

    ReplyDelete
    Replies
    1. Hi Hitesh,

      Thanks a lot for your feedback. My experience with Spring Data Repoisitory was with Mongo DB as the database. Mongo DB being a non-relational database did not give me the scope to explore the Joins scenario. However Arregation and Composition relations are definately doable and I have used them sucessfully with this architecture.

      Although I have read about @Qualifier annotation which is used to distinguish multiple tables from one another. The request parameters then have to be prefixed with ${qualifier}_. So for a method signature would be:

      public String showUsers(Model model,
      @Qualifier("foo") Pageable first,
      @Qualifier("bar") Pageable second) { … }

      you have to populate foo_page and bar_page etc. You could see an example @ http://docs.spring.io/spring-data/data-commons/docs/1.6.3.RELEASE/reference/html/repositories.html

      Hope this is helpful.

      Delete

Post a Comment

Popular posts from this blog

jQgrid reload with new data

Introduction to jQgrid

Rich Client based UI technologies- A Comparison of Thick Client UI tools