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
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.
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.
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.
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"; } }
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:
if it exists a
named query with name
<entityName>.methodName
, then execute that query
and return the result as the result of the method;
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;
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.
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); }
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();
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.
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%");
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.
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:
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);
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.