Parancoe
The easy-to-use web framework

Chapter 2. Persistence

2.1. Introduction

The persistence layer of Parancoe is based on the Hibernate implementation of the Java Persistence API (JPA). Moreover it provides:

  • utility classes for easy definition of persistent entities

  • a DAO system for common methods and for easy definition of finder methods

  • a standard default configuration

2.2. Persistent entities

The recommended method for creating a persistent entity with Parancoe is to use the JPA annotations. As at present the JPA implementation used by Parancoe is the Hibernate one, you can read the Hibernate documentation for a complete reference about the JPA annotations.

Parancoe provides the org.parancoe.persistence.po.hibernate.EntityBase class, that can be used as the base class for your entities. The features inherited from the EntityBase are:

  • an auto-generated identifier of type Long;

  • versioning for optimistic locking;

  • properly defined hashCode and equals methods;

  • toString method, printing the class name and the identifier of the persistent instance.

The following code is an example of defining an entity class extending the EntityBase:

Example 2.1. The Person entity

package org.parancoe.example.po;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;


                    @Entity
public class Person
                    extends EntityBase {
    private String firstName;
    private String lastName;
    private Date birthDate;
    private String email;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

                    @Temporal(TemporalType.DATE)
    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
                


The entities are auto-discovered using the following tag in your Spring configuration (already included in the configuration produced by the Parancoe Web Archetype):

    <parancoe:discover-persistent-classes basePackage="org.parancoe.example.po"/>

            

So the only thing you need to do is to add the entities classes in the correct package. No per-class configuration is needed.

2.3. Entity DAOs

For using the persistent entities you need to write almost no code. Parancoe provides a Data Access Object (DAO) layer very powerful and easy to use.

2.3.1. Define a DAO for an entity

In Parancoe the DAOs are not classes, just interfaces, without an explicit implementation. For example, for defining the DAO for the Person entity, you need to write:

Example 2.2. The Person DAO

package org.parancoe.example.dao;

import org.parancoe.example.po.Person;
import org.parancoe.persistence.dao.generic.Dao;
import org.parancoe.persistence.dao.generic.GenericDao;

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
}
                    


The generic parameters of the GenericDao interface are the type of the persistent entity, and the type of its identifier.

You don't have to write an implementation of this interface. The implementation will be automatically added at runtime through Spring AOP. The current implementation provides the following methods to each DAO:

T read(PK id);
T get(PK id);
void create(T transientObject);
void store(T transientObject);
void delete(T persistentObject);
List<T> findAll();
List<T> searchByCriteria(Criterion... criterion);
List<T> searchByCriteria(DetachedCriteria criteria);
List<T> searchByCriteria(DetachedCriteria criteria, int firstResult, int maxResults);
Page<T> searchPaginatedByCriteria(int page, int pageSize, Criterion... criterion);
Page<T> searchPaginatedByCriteria(int page, int pageSize, DetachedCriteria criteria);
int deleteAll();
long count();    
long countByCriteria(DetachedCriteria criteria);
                

Note that even these methods are parametrized with the type of the entity ( T) and the type of the entity identifier ( PK). So, for example, the findAll method of the DAO of the Person entity doesn't return a generic List<Object>, but it can return a more usefull type-checked List<Person>.

The DAOs are auto-discovered using the following tag in your Spring configuration (already included in the configuration produced by the Parancoe Web Archetype):

    <parancoe:define-daos basePackage="org.parancoe.example.dao"/>

                

So the only thing you need to do is to add the DAO interfaces classes in the correct package. No per-DAO configuration is needed.

2.3.2. Use the DAO

To use a DAO you simply need to get it from the Spring application context. You could of course retrieve it by name from the context, using its unqualified name. For example you could retrieve the PersonDao throught the "personDao" name. But the easiest way for obtaining a reference to a DAO is to auto-wire it in a Spring managed bean.

For example, for using the PersonDao in a controller of your application:

Example 2.3. A controller using a DAO

package org.parancoe.example.controllers;

import org.parancoe.example.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/people/*.html")
public class PeopleController {

                        @Autowired
    private PersonDao personDao;
                        

    @RequestMapping
    public String list(Model model) {
        model.addAttribute("people",
                        personDao.findAll());
        return "people/list";
    }
}
                    


2.4. Add finder methods to the DAOs

The GenericDao interface provides the base methods you need for interacting with persistent entities. You will add to your DAO interface the methods your application needs for finding the data in the database. Again, you don't have to implements those methods, they will be solved at runtime through Spring AOP, using some simple rules:

  1. if it exists a named query with name <entityName>.methodName, then execute that query and return the result as the result of the method;

  2. else if the method name is in the form findBy[<And-separated list of attributes>][OrderBy[<And-separated list of attributes>], then the method signature is parsed for generating a query. The generated query will be executed and the result will be returned as the result of the method;

  3. else call the method on the base DAO implementation.

The return type of the finder methods can be a List<T> or a single T, where T is the type of the persistent entity. In the case of single T:

  • if the query will produce a single result, that result will be returned;

  • if the query will produce more than a single result, only the first one will be returned;

  • if the query will produce no results, null will be returned.

Some pratical examples will help you being very productive with this technique.

2.4.1. Find by field equality

For example, you need to find all people with first name equals to "John". Add to the interface the following method:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByFirstName(String firstName);
}
                

If the finder needs to compare on more fields, simply list them in the method name separated by the “And” keyword:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByFirstName(String firstName);
  List<Person> findByFirstNameAndLastNameAndBirthDate(
      String firstName, String lastName, Date birthDate);
}
                

2.4.2. Ordering results

If you need to order (ascending) the results, declare a method as the following:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByBirthDateOrderByLastName(Date birthDate);
}
                

Again, If you need to order on more than one field, list the fields you need:

List<Person> findByBirthDateOrderByLastNameAndFirstName(Date birthDate);
                

If you don’t want to filter by equality, but the result must contains all records, omit the list of fields:

List<Person> findByOrderByLastNameAndFirstName();
                

2.4.3. Find a single object

Define the finder method returning the entity type, not a List:

Person findByFirstNameAndLastName(String firstName, String lastName);
                

If the query will produce more than one result, only the first one in the list will be returned. If the query will produce no results, the method will return null.

2.4.4. Write the query in JPA-QL/HQL

You can write an JPA-QL/HQL query and add a method in the DAO interface that will execute it when invoked. Declare the method as you like in the DAO interface, and declare the query as a named query in the DAO’s entity. The name of the query must be <entityName>.methodName. The method parameters are passed in the same order as values for the query parameters. For example:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByPartialUncasedLastName(String partialLastName);
}
                

@Entity()
@NamedQueries({
  @NamedQuery(
    name="Person.findByPartialUncasedLastName",
    query="from Person p where lower(p.lastName) like lower(?) order by p.lastName")
})
public class Person extends EntityBase {
  // ...
}
                

Now You can invoke it:

List <Person> people = dao.findByPartialUncasedLastName("B%");
                

2.4.5. Methods with support for the pagination of results

You can annotate the finder method parameters with the @FirstResult and @MaxResults annotations. For example:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByLastName(String lastName,
                              @FirstResult int firstResult,
                              @MaxResults int maxResults);
}
                

The type of the annotate parameter must be int. You can apply this technique to finder methods associated to named queries, without the need to modify the query. For example:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByPartialUncasedLastName(String partialLastName,
                                            @FirstResult int firstResult,
                                            @MaxResults maxResults);
}
                

@Entity()
@NamedQueries({
  @NamedQuery(
    name="Person.findByPartialUncasedLastName",
    query="from Person p where lower(p.lastName) like lower(?) order by p.lastName")
})
public class Person extends EntityBase {
  // ...
}
                

If there are situations in which you don’t want to limit the number of records, you can pass a negative value for the maxRecords parameter:

List<Person> people = dao.findByPartialUncasedLastName("B%", 0, -1);
                

It will return all records, from the first (0) to the end.

2.4.6. Using different strategies for comparing parameter values

In totally dynamic finders you can choose the type of the comparison applied to each parameter using the @Compare annotation. For example:

@Dao(entity=Person.class)
public interface PersonDao extends GenericDao<Person, Long> {
  List<Person> findByLastName(
                 @Compare(CompareType.ILIKE) String lastName);
}
                

The supported comparison strategies are:

EQUAL
equality, it’s the default
LIKE
like, using ’%’
ILIKE
case-insensitive like
GE
greater than or equal
GT
greater than
LE
less than or equal
LT
less than
NE
not equal

2.5. Search with criteria

Very complex queries can be implemented using criteria in place of writing JPA-QL/HQL queries. This is particularly useful for implementing search forms, where some or all parameters are optional.

The methods of the base DAO that support this kind of queries are:

List<T> searchByCriteria(Criterion... criterion);
List<T> searchByCriteria(DetachedCriteria criteria);
List<T> searchByCriteria(DetachedCriteria criteria, int firstResult, int maxResults);
Page<T> searchPaginatedByCriteria(int page, int pageSize, Criterion... criterion);
Page<T> searchPaginatedByCriteria(int page, int pageSize, DetachedCriteria criteria);
long countByCriteria(DetachedCriteria criteria);
            

For example:

Example 2.4. Searching using a DetachedCriteria

DetachedCriteria personCriteria =
        DetachedCriteria.forClass(Person.class);
if (StringUtils.isNotBlank(firstNameValue)) {
    personCriteria.add(Restrictions.ilike("firstName",
            firstNameValue, MatchMode.ANYWHERE));
}
if (StringUtils.isNotBlank(lastNameValue)) {
    personCriteria.add(Restrictions.ilike("lastName",
            lastNameValue, MatchMode.ANYWHERE));
}
List<Person> result = personDao.searchByCriteria(personCriteria);
                


2.6. Transaction demarcation

Without any additional configuration the transaction is identified by the single DAO method call.

The application generated by the Parancoe Web Archetype is configured for using a transaction-per-request strategy.

The transaction will be commited at the end of the request, except if an uncaught exception has been thrown.

You can rollback the current transaction calling the rollbackTransaction method of any DAO.