Goal

The end result should be a Tapestry 5 (5.1 in this case) application, making use of Spring with Spring Transactions, and JPA. This example will be an incredibly simplified phone book application.

Updating web.xml

To start we need to add our Spring XML files to the context-params of web.xml:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

Then we need to update the Tapestry 5 filter to be org.apache.tapestry5.spring.TapestrySpringFilter

Finally we want to open our Entity Manager on page requests, so we add a filter (before the Tapestry filter) and filter-mapping:

<filter>
    <filter-name>JpaFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>JpaFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Spring Beans XML File

We would like to use JPA exception translation, annotations, and transactions. We also need a Dao for our Person object.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
    <context:annotation-config />
    
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="tapestryUnit" />
    </bean>
    <!-- Switch to this in JEE -->
    <!-- <jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/tapestryUnit" /> -->

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="personDao" class="com.company.myproject.dao.impl.PersonDaoImpl" />
</beans>

persistence.xml

Our persistence.xml is the same as any other. In this case we are not deploying to a JEE container, but just a web container, so we've defined the connection properties inside persistence.xml. We typically would use connection pooling, but for simplicity we have not. Note we will use Hibernate (though we don't have to!), and DB2.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="tapestryUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>com.company.myproject.entities.Person</class>
        <exclude-unlisted-classes />
        
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.DB2Dialect" />
            <property name="hibernate.connection.driver_class" value="com.ibm.db2.jcc.DB2Driver" />
            <property name="hibernate.connection.username" value="tapestry" />
            <property name="hibernate.connection.password" value="tapestry" />
            <property name="hibernate.connection.url" value="jdbc:db2://dbserver:50000/TESTS:currentSchema=PHNPRJ;" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

The DAO

Our DAO is just like any other JPA with Spring annotations DAO.

The entity the DAO will control is:

@Entity
@Table(name = "PERSON")
public class Person implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Version
    @Column(name = "VER")
    private Integer version;

    @Column(name = "FIRSTNAME", length = 15, nullable = false)
    private String firstName;

    @Column(name = "LASTNAME", length = 15, nullable = false)
    private String lastName;

    @Column(name = "PHONENR", length = 10)
    private String phoneNumber;

    // ... SNIPPED GETTERS/SETTERS ...

Note that as I'm using an identity column, the database must be created properly. hbm2ddl does not do this. Use this DDL:

CREATE TABLE PHNPRJ.PERSON (
    ID INT NOT NULL GENERATED ALWAYS AS IDENTITY,
    VER INT NOT NULL DEFAULT 0,
    FIRSTNAME VARCHAR(15) NOT NULL DEFAULT '',
    LASTNAME VARCHAR(15) NOT NULL DEFAULT '',
    PHONENR VARCHAR(10) DEFAULT '',
    CONSTRAINT PERSON_PK PRIMARY KEY (ID)
);

The DAO interface:

public interface PersonDao {
    public List<Person> getPeople();
    public Person read(Long id);
    public Person save(Person person);
    public void delete(Long id);
    public void delete(Person person);
}

The DAO:

@Repository
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class PersonDaoImpl implements PersonDao {
    @PersistenceContext
    private EntityManager entityManager;

    public List<Person> getPeople() {
        return entityManager.createQuery("from Person p order by p.lastName, p.firstName, p.phoneNumber").getResultList();
    }

    public Person read(Long id) {
        return (Person)entityManager.createQuery("from Person p where p.id = :id").setParameter("id", id).getSingleResult();
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public Person save(Person person) {
        if (person.getId() == null || person.getId() == 0) {
            entityManager.persist(person);
        } else {
            person = entityManager.merge(person);
        }
        return person;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void delete(Long id) {
        entityManager.remove(read(id));
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void delete(Person person) {
        entityManager.remove(person);
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
}

The Rest

Everything else is standard Tapestry. To get our DAO in a T5 page:

@Inject
private PersonDao personDao

We do not need anything special in our AppModule.

Note do not include the tapestry-hibernate module as we are not using hibernate.cfg.xml.

  • No labels