Parancoe Persistence Tutorial

The purpose of the Parancoe persistence module is to simplify the writing of the persistence layer of common applications. It provides support for easily writing and deploying the persistent objects (POs) and data access objects (DAOs) of your application.

When I say “easily”, I really mean very easily in the common cases. You can write a PO and its DAO in a few minutes.

Let’s go with an example.

The problem

In your application You need to manage some personal data (first name, last name, birth date, e-mail address, etc.). These data are stored in a table of a relational database and, of course, you need to create, load, update and find them.

As you can see, it’s a simple, but really common and frequent task.

Write the PO

The first step is to write the PO class:


package org.parancoe.example.po;

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

@javax.persistence.Entity()
public class Person extends EntityBase {
    private String firstName;
    private String lastName;
    private Date birthDate;
    private String email;

    /** Creates a new instance of Person */
    public Person() {
    }

    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;
    }
}

This defines a PO with an auto-generated id of type Long, support for optimistic locking, equals/hashcode methods operating on the id….and, of course, your specific fields. Moreover the code is mostly IDE-generated, so you have very few code to type.

Extending the EntityBase class is not a requisite, just a comfort. You can write your own POJOs (Plain Old Java Objects), mapped as you like.

If you use the JPA annotations for you mappings, your persistent POJOs will be auto-discovered by Parancoe.

Done! That’s all!

Of course, in order to make the things work you’ll need some more little configuration. For example, you have to configure the connection to your database. But it’s a “fixed” configuration, you don’t need to modify it for the task of adding a PO and its DAO. We’ll see later, in the “Fixed Configuration” section.

Write the DAO

To write a DAO you don’t need to implement also its class, but just to define its interface!


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> {
}

Done!

What did you obtain? A DAO for your Person PO with the following methods:

  • Long create(Person newInstance)
  • void createOrUpdate(Person o)
  • Person read(Long id)
  • void update(Person transientObject)
  • void delete(Person persistentObject)
  • int deleteAll()
  • List<Person> findAll()
  • List<Person> searchByCriteria(Criterion… criterion)
  • List<Person> searchByCriteria(DetachedCriteria criteria)
  • List<Person> searchByCriteria(DetachedCriteria criteria, int firstResult, int maxResults)

Add methods to the DAO

Do you need any search methods in your DAO? For example for searching people by their last name and first name, or by their birth date?

Simply add methods to the interface!


package org.parancoe.example.dao;

import java.util.Date;
import java.util.List;
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> {
    List<Person> findByFirstNameAndLastName(String firstName, String lastName);
    List<Person> findByBirthDate(Date birthDate);
}

Done!

No needs to write the methods or even to change the DAO configuration.

At present you can add methods searching for equality using the following convention:

List< entityType > findBy field1 And field2... ( fieldType1 f1, fieldType2 f2 , ...);

Complex Searches

Do you need to search your data in a complex manner? Simply add the method you like to your DAO and the query to your PO!

For example, do you need to search people older than 18, or older than a parametrized age?

Add the method to your DAO:


package org.parancoe.example.dao;

import java.util.Date;
import java.util.List;
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> {
    List<Person> findByFirstNameAndLastName(String firstName, String lastName);
    List<Person> findByBirthDate(Date birthDate);
    List<Person> findOlderThan(int age);
}

Then add a named query to your PO:


@javax.persistence.Entity()
@NamedQuery(name="Person.findOlderThan",
            query="from Person where (current_date() - birthDate) > :age")
public class Person extends EntityBase {
    private String firstName;
    private String lastName;
    private Date birthDate;
    private String email;

    ...
}

Done!

No need to write Java code or to change your XML configuration.

Fixed configuration

If you use the Parancoe Maven archetype for web applications, the following configuration will be auto-generated, and you don’t need to change it (except your database connection configuration, of course). See StartingParancoeWebProject.


<?xml version="1.0" encoding="UTF-8"?>
<!-- database.xml - Database configuration -->
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:tx="http://www.springframework.org/schema/tx" 
       xmlns:parancoe="http://www.parancoe.org/schema/parancoe" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.parancoe.org/schema/parancoe http://www.parancoe.org/schema/parancoe.xsd">

    <tx:annotation-driven/>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost/myAppDB"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
        <property name="configLocation" value="classpath:hibernate-mappings.cfg.xml"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <!-- Authomatic discovering of persistent classes -->
    <parancoe:discover-persistent-classes basePackage="org.parancoe.example"/>

    <!-- Authomatic DAO definition from persistent classes -->
    <parancoe:define-daos basePackage="org.parancoe.example"/>
</beans>

<?xml version="1.0" encoding="UTF-8"?>
<!-- beanRefFactory.xml Meshing configurations -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> 
    <bean id="org.parancoe.persistence" 
          class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
            <list>
                <value>org/parancoe/persistence/dao/generic/genericDao.xml</value>
                <value>database.xml</value>
                <value>dao.xml</value>
                <value>applicationContext.xml</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

Using the DAO

The only requisite for using the DAO is to call it’s methods inside a transaction. You can manage your transactions as you like. But, as we are using Spring and annotations yet, maybe the preferred way is to use the Spring declarative support for the transactions. So you’ll put your business logic using the DAO inside methods annotated as transactional.

For example:


package org.parancoe.example.bo;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import org.parancoe.example.dao.PersonDao;
import org.parancoe.example.po.Person;
import org.springframework.transaction.annotation.Transactional;

public class PersonBO {
    private PersonDao dao;

    /**
     * Creates a new instance of PersonBO
     */
    public PersonBO() {
    }

    public PersonDao getDao() {
        return dao;
    }

    public void setDao(PersonDao dao) {
        this.dao = dao;
    }

    @Transactional()
    public void populateArchive() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        List<Person> searches = null;
        searches = dao.findByFirstNameAndLastName("Mario", "Rossi");
        if (searches.isEmpty()) {
            Person p = new Person();
            p.setFirstName("Mario");
            p.setLastName("Rossi");
            p.setBirthDate(sdf.parse("25/04/1970"));
            dao.create(p);
        }
        searches = dao.findByFirstNameAndLastName("Francesca", "Verdi");
        if (searches.isEmpty()) {
            Person p = new Person();
            p.setFirstName("Francesca");
            p.setLastName("Verdi");
            p.setBirthDate(sdf.parse("30/08/1990"));
            dao.create(p);
        }
        searches = dao.findByFirstNameAndLastName("Giovanni", "Bianchi");
        if (searches.isEmpty()) {
            Person p = new Person();
            p.setFirstName("Giovanni");
            p.setLastName("Bianchi");
            p.setBirthDate(sdf.parse("15/03/1980"));
            dao.create(p);
        }
    }

    @Transactional(readOnly=true)
    public Person retrievePerson(Long id) {
        return dao.read(id);
    }
}

Then configure you business object in your applicationContext.xml :


<bean id="personBO" class="org.parancoe.example.bo.PersonBO">    
   <property name="dao" ref="personDao"/>
</bean>    

...and just use it:


BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance("beanRefFactory.xml");
BeanFactoryReference bf = bfl.useBeanFactory("org.parancoe.example");
PersonBO personBO = (PersonBO)bf.getFactory().getBean("personBO");
// Popoulating the database
personBO.populateArchive();
// Finding by id (not really useful)
Person p = personBO.retrievePerson(new Long(1));
if (p != null) {
    System.out.println(p.getFirstName()+" "+p.getLastName()+" "+p.getBirthDate());
} else {
    System.out.println("Person (1) not found");
}

Note: it just work! Apparently no session or transaction management, no database programming, mostly no SQL/HQL programming.

You can find the full example in Parancoe Subversion repository.

http://parancoe.googlecode.com/svn/trunk/examples/basicPersistence/

Posted by Lucio Benfante on Friday, June 09, 2006