Barebones Spring MVC part 6: Database integration

August 22, 2010

A major component of enterprise applications is information storage and retrieval via a relational database. Most of this behavior is confined to a special data tier, with a thin API into the application tier for interaction with the domain.

This example builds upon part 1: core to introduce a Hibernate-based data tier for storage and retrieval of data.

The following changes are required:

  1. Add multiple libraries to the Maven POM, including hibernate, hibernate-annotations, persistence-api, jta, spring-orm, commons-dbcp, and hsqldb.
  2. Create a persistable type SimpleType.
  3. Create a repository to provide database interaction.
  4. Integrate SimpleController with the repository.
  5. Create a new Spring context, persistence-context.xml.
  6. Optionally externalize the DataSource via JNDI.

A persistable type is created to represent the domain model. In this simple example, this will closely resemble the UI model, but it is important to draw a distinction between the two, as they serve two very different purposes. The pupose of a domain model is to represent the domain model. This can include potentially complex object hierarchies as well as database-specific metadata. The purpose of a UI model is to represent the UI model. An HTML UI will have forms and other data structures which will tend to be very flat, and contain very specific information meant to be rendered in a view. Attempting to merge the two models can get very difficult, as the domain model tends not to map directly to the UI model. Furthermore, this will cause changes in one to necessitate changes in the other. It is much simpler to create simple conversion logic in the service tier to translate between the two models.

SimpleType.java:

@Entity
public class SimpleType {

    @Id
    @Column
    private long id;

    @Column
    private String value1;

    @Column
    private String value2;

    @CollectionOfElements
    @Column
    private List<String> value3;

    // Accessors and mutators excluded for brevity
}

A repository serves the domain as an opaque entry point into the database. It provides accessors and mutators for database tables represented only by domain objects.

Repository.java:

public class Repository {

    @Autowired
    private SessionFactory sessionFactory;

    private Session session() {
        return sessionFactory.getCurrentSession();
    }

    public SimpleType getSimpleType(long id) {
        return (SimpleType) session().createCriteria(SimpleType.class).add(
                Restrictions.idEq(id)).uniqueResult();
    }

    public void save(SimpleType simpleType) {
        session().saveOrUpdate(simpleType);
    }
}

This repository is meant to work with Hibernate, and so uses a Hibernate SessionFactory.

In this example, the service tier is contained entirely within SimpleController, which is modified to interact with the new repository.

SimpleController.java:

@Controller
public class SimpleController {

    @Autowired
    private Repository repository;

    @ModelAttribute("values")
    public List<String> getSimpleValues() {
        List<String> simpleValues = new ArrayList<String>();
        simpleValues.add("Value A");
        simpleValues.add("Value B");
        simpleValues.add("Value C");
        return simpleValues;
    }

    @Transactional
    @RequestMapping(value = "/simpleForm", method = RequestMethod.GET)
    public void simpleForm(Model model) {
        SimpleType simpleType = repository.getSimpleType(0);
        SimpleForm simpleForm = simpleForm(simpleType);
        model.addAttribute(simpleForm);
    }

    @RequestMapping(value = "/simple", method = RequestMethod.GET)
    public String simple() {
        return "redirect:simpleForm";
    }

    @Transactional
    @RequestMapping(value = "/simple", method = RequestMethod.POST)
    public void simple(@ModelAttribute SimpleForm simpleForm, Model model) {
        SimpleType simpleType = simpleType(simpleForm);
        simpleForm = simpleForm(simpleType);

        model.addAttribute("value1", simpleForm.getValue1());
        model.addAttribute("value2", simpleForm.getValue2());
        model.addAttribute("value3", simpleForm.getValue3());
        model.addAttribute(simpleForm);
    }

    private SimpleForm simpleForm(SimpleType simpleType) {
        if (simpleType != null) {
            SimpleForm simpleForm = new SimpleForm();
            simpleForm.setValue1(simpleType.getValue1());
            simpleForm.setValue2(simpleType.getValue2());
            simpleForm.setValue3(simpleType.getValue3());

            if (simpleType.getValue3() != null) {
                simpleType.getValue3().size();
            }

            return simpleForm;
        } else {
            return new SimpleForm();
        }
    }

    private SimpleType simpleType(SimpleForm simpleForm) {
        SimpleType simpleType = repository.getSimpleType(1);
        if (simpleType == null) {
            simpleType = new SimpleType();
            repository.save(simpleType);
        }
        simpleType.setValue1(simpleForm.getValue1());
        simpleType.setValue2(simpleForm.getValue2());
        simpleType.setValue3(simpleForm.getValue3());

        return simpleType;
    }
}

SimpleController includes two helper methods, simpleForm() and simpleType(), which do the mapping between the UI model and the domain model.

Finally, a new global Spring context is created to manage the database-related objects.

persistence-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config />

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

    <bean class="com.earldouglas.example.springmvc.domain.Repository" />

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        init-method="createDatabaseSchema">
        <property name="dataSource" ref="hsqlDataSource" />
        <property name="packagesToScan" value="com.earldouglas.example.springmvc.domain" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.HSQLDialect
                </prop>
                <prop key="hibernate.show_sql">false</prop>
            </props>
        </property>
    </bean>

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

</beans>

This context is made a parent context via ContextLoaderListener in the Web deployment descriptor.

web.xml:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

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

A minor change is required in spring-mvc-context.xml to enable service tier transaction management.

spring-mvc-context.xml:

<tx:annotation-driven />

The DataSource can optionally be externalized from the Spring configuration via JNDI. Configuration specifics, such as database username and password, are then kept out of the Spring configuration and delegated to the application server. For Apache Tomcat, the DataSource is added to META-INF/context.xml:

context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/hsqlDataSource"
              auth="Container"
              type="javax.sql.DataSource"
              username="sa"
              password=""
              driverClassName="org.hsqldb.jdbcDriver"
              factory="org.apache.commons.dbcp.BasicDataSourceFactory"
              url="jdbc:hsqldb:mem:mydatabase"/>
</Context>

The DataSource bean is removed from the Spring configuration, and replaced by a JNDI-lookup:

persistence-context.xml:

<jee:jndi-lookup id="hsqlDataSource" jndi-name="java:comp/env/jdbc/hsqlDataSource" />