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
Apart from
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:
Standard CRUD functionality repositories usually have queries on the underlying datastore. With Spring Data, declaring those queries becomes a four-step process:
- Declare an interface extending Repository or one of its subinterfaces and type it to the domain class that it will handle.
- 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.
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:
You can configure the strategy at the namespace through the
query-lookup-strategy
attribute. 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
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
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.
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…By
, read…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
andOR
. You also get support for operators such asBetween
,LessThan
,GreaterThan
,Like
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 (usuallyString
s, 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
orDesc
).
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
Person
s have Address
es with ZipCode
s. In that case a method name ofListfindByAddressZipCode(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:ListfindByAddress_ZipCode(ZipCode zipCode);
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.PagefindByLastname(String lastname, Pageable pageable); List findByLastname(String lastname, Sort sort); List findByLastname(String lastname, Pageable pageable);
Liked it in parts, not yet gone through the links though.
ReplyDeleteAs 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
Hi Hitesh,
DeleteThanks 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.