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 usingQueryDsl Predicate
objets.
- The
- 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 usingSpecification<t>
objects that use the JPA criteria API.
- The
Generally speaking, we can create repository that provides CRUD operation in two different ways:
- Create an interface that extends
CrudRepository
orJpaRepository
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
andget...By
. - If we want to specify the selected property, we must add the name of the property before the first
By
word. For examplefindNameBy
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 theTop
keyword before the firstBy
word. - If we want to select unique results, we have to add the
Distinct
keyword before the firstBy
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.