DAO

In this sub-chapter we will implement DAO (Data Access Object) layer for our application. By definition, DAO is an object that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, DAO provide some specific data operations without exposing details of the database. This isolation supports the Single responsibility principle:

Every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.

One of the advantages of the DAO layer is that if we need to change the underlying persistence mechanism, we only might have to change the Model and DAO layer, and not all the places in the domain logic (Service layer) where the DAO layer is used from.

Creating repositories that use JPA is cumbersome process that takes a lot of time and requres a lot of boilerplate code. Developers usually deal with boilerplate code by following these steps:

  • Create an abstract base repository class that provides CRUD operations for entities.
  • Create the concrete repository class that extends the abstract base repository class.

The problem of this approach is that we still have to write the code that creates our database queries and invokes them. Spring Data JPA comes as a rescue in this situation - it enable to us to create JPA repositories without writing any boilerplate code.

Spring Data JPA is a library/framework that adds an extra layer of abstraction on the top of our JPA provider. When we use Spring Data JPA, our DAO layer contains the following three layers:

  • Spring Data JPA - provides support for creating JPA repositories by extending Spring Data repository interfaces.
  • Spring Data Commons - provides the infrastructure that is shared by the datastore specific Spring Data projects.
  • The JPA Provider that implements the Java Persistence API.

We use Spring Data JPA following these steps:

  • Create a repository interface and extend one of the repository interfaces provided by Spring Data.
  • Add custom query methods to the created repository interface.
  • Inject the repository interface to another component and use the implementation that is provided automatically by Spring.

Spring Data repository interfaces are:

  • Spring Data Commons project provides the following interfaces:
    • The Repository<T, ID extends Serializable> interface is marker interface that has two purposes:
      • It captures the type of the managed entity and the type of the entity's id.
      • It helps the Spring container to discover the "concrete" repository interfaces during classpath scanning.
    • The CrudRepository<T, ID extends Serializable> interface provides CRUD operations of the managed entity.
    • The PagingAndSortingRepository<T, ID extends Serializable> interface declares the methods that are used to sort and paginate entities that are retrived from the database.
    • The QueryDslPredicateExecutor<T> interface is not a "repository interface". It declares the methods that are used to retrieve entities from the database by using QueryDsl Predicate objets.
  • Spring Data JPA project provides the following interfaces:
    • The JpaRepository<T, ID extends Serializable> interace is a JPA specific repository interface that combines the methods declareed by the common repository interfaces behind a single interface.
    • The JpaSpecificationExecutor<t> interface is not a "repository interface". It declares the methods that are used to retrieve entities from the database by using Specification<t> objects that use the JPA criteria API.

Generally speaking, we can create repository that provides CRUD operation in two different ways:

  • Create an interface that extends CrudRepository or JpaRepository interface.
  • Create an interface that extends the Repository interface and add the required methods to the created interface.

Extending the CrudRepository interface

If we create our repository by extending the CrudRepository interface, we have to provide two type parameters:

  • The type of the entity that is managed by our repository.
  • The type of the entity's id field.

In next code listing we can see example of ManagerRepository:

import org.springframework.data.repository.CrudRepository;

interface ManagerRepository extends CrudRepository<Manager, Long> {
}

And that's it - we have fully functional repository for our Manager entity. We do not need to provide any implementation of this interface.

Extending the RepositoryInterface

If we create our repository by extending the Repository interace, we have to provide two type parameters:

  • The type of the entity that is managed by our repository.
  • The type of the entity's id field.

And add the required methods to the repository interface, like it is shown in next code listing:

import org.springframework.data.repository.Repository;

import java.util.List;

interface ManagerRepository extends Repository<Manager, Long> {
    void delete(Manager toBeDeleted);

    List<Manager> findAll();

    Manager findOne(Long id);

    Manager save(Manager toBeSaved);
}

At first, it looks like the former method is more convenient to use because at least for CRUD methods we do not have to specify anything in our repositories. The advantages of the later approach is:

  • It doesn't expose all repository methods that are declared by the CrudRepository interface which is in line with Martin Fowler:

    The idea behind the minimal interface is to design an API that allows the client to do everything they need to do, but boils down the capabilities to the smallest reasonable set of methods that will do the job.

  • It allows us to use Optional (Guava / Java 8) objects as a return type for our CRUD (as well as other) methods. The null reference is the source of many problems because it is often used to denote the absence of a value.

Repositories

First of all, inside our model package we create a new package called repositories. This is the place where we will put our repository (DAO) classes.

Also as we will use Optional we must add the following dependency to our POM file:

<!-- Guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>${guava.version}</version>
</dependency>

where ${guava.version} is defined in <properties> tag of POM file:

<guava.version>18.0</guava.version>

In order to avoid adding the same required methods (CRUD) to all our repositories, we can create a base interface (like the one shown in next code listing) and add the common methods to that interface.

BaseRepository.java

package pms.model.repositories;

import com.google.common.base.Optional;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;

import java.io.Serializable;
import java.util.List;

@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

    T save(T persisted);

    Optional<T> findOne(ID id);

    List<T> findAll();

    void delete(T deleted);
}

As we can see, we annotated this interface with the @NoRepositoryBean annotation in order for Spring Data JPA not to try to create an implementation for our base repository interface.

Now we can create repositories for our managed entities by extending BaseRepository interface.

ManagerRepository.java

package pms.model.repositories;

import com.google.common.base.Optional;
import pms.model.entities.Manager;

import java.util.List;

public interface ManagerRepository extends BaseRepository<Manager, Long> {

    Optional<Manager> findByName(String name);

    List<Manager> findByNameLike(String likeName);
}

In previous code listing we have an example of query generation from the method name. General rules for naming query methods are:

  • The name of our query method must start with one of the following prefixes: find...By, read...By, query...By, count...By and get...By.
  • If we want to specify the selected property, we must add the name of the property before the first By word. For example findNameBy means that we want to select the name of entity that is managed by our repository interface.
  • If we want to limit the number of returned query results, we can add the First or the Top keyword before the first By word.
  • If we want to select unique results, we have to add the Distinct keyword before the first By word.
  • We must add the search criteria to our query method after the first By word. We can combine mulitple search conditions using keywords like: Like, And, Or, Before etc. If our query method specifies n search conditions, we must add n method parameters to it.

DepartmentRepository.java

package pms.model.repositories;

import com.google.common.base.Optional;
import pms.model.entities.Department;

/**
 * Created by Milan.Nikic on 8/13/2015.
 */

public interface DepartmentRepository extends BaseRepository<Department, Long> {

    Optional<Department> findByName(String name);
}

EmployeeRepository.java

package pms.model.repositories;

import com.google.common.base.Optional;
import pms.model.entities.Department;
import pms.model.entities.Employee;

import java.util.List;


public interface EmployeeRepository extends BaseRepository<Employee, Long> {

    Optional<Employee> findByName(String name);

    List<Employee> findByNameLike(String likeName);

    List<Employee> findByDepartment(Department department);
}

ProjectRepository.java

package pms.model.repositories;

import com.google.common.base.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import pms.model.entities.Manager;
import pms.model.entities.Project;

import java.util.Date;
import java.util.List;

public interface ProjectRepository extends BaseRepository<Project, Long> {

    Optional<Project> findByTitle(String title);

    List<Project> findByTitleLike(String likeTitle);

    List<Project> findByPriority(String priority);

    List<Project> findByDeadlineBefore(Date deadline);

    List<Project> findByDeadlineAfter(Date deadline);

    List<Project> findByPriorityAndDeadlineBefore(String priority, Date deadline);

    List<Project> findByManager(Manager manager);

    List<Project> findByPriorityAndManager(String priority, Manager manager);

    List<Project> findByPriorityAndManagerAndDeadlineBefore(String priority, Manager manager, Date deadline);
}

Creating query generation strategy allow us to create simple queries fast and the method name is self-descriptive - describes the selected value and used search conditions. But this query generation strategy has some weakness:

  • The method names of complex querie methods can be very long.
  • There is no suport for dynamic queries.

But Spring Data JPA provides us with posibility to create query methods using @Query annotation. It supports both JPQL and SQL queries, and the query that is specified by using @Query annotation precededs all other query generation strategies.

For example, the last method from previous code listing we can create like this:

1) Using JPQL

@Query("select p from Project p where p.priority = ?1 and p.manager.id = ?2 and p.deadline < ?3")
List<Project> findByPriorityManagerAndDeadlineBefore(String priority, Long managerID, Date deadline);

2) Using JPQL with named parameters

@Query("select p from Project p where p.priority = :priority and p.manager.id = :managerID and p.deadline < :deadline")
List<Project> findByPriorityManagerAndDeadlineBefore(@Param("priority") String priority, @Param("managerID") Long managerID, @Param("deadline") Date deadline);

3) Using SQL

@Query(value = "select * from PROJECT p where p.priority = ?0 and p.manager_ID = ?1 and p.deadline < ?2", nativeQuery = true)
List<Project> findByPriorityManagerAndDeadlineBefore(String priority, Long managerID, Date deadline);

4) Using SQL with named parameters

@Query(value = "select * from PROJECT p where p.priority = :priority and p.manager_ID = :managerID and p.deadline < :deadline", nativeQuery = true)
List<Project> findByPriorityManagerAndDeadlineNativeNamedParameters(@Param("priority") String priority, @Param("managerID") Long managerID, @Param("deadline") Date deadline);

There is only one thing to do before we can proceed with next sub-chapter. In PmsApplication class add the following class annotation: @EnableJpaRepositories(basePackages = "pms.model.repositories"). It tells to Spring Data in which packages our repositories can be found.