A Comprehensive Example of a Spring MVC Application - Part 1

The Architecture

This simple spring application has 3 layers. The layers follow the simple rules:

  • Each layer can talk to the layer below it and only to the layer below it
  • Each layer has a clear and single purpose

The layers are:

  1. Web layer - the purpose of this layer is to convert the inbound REST API to what the below service layer accepts and back - whatever is returned from the service back to the REST API. There is no business logic in this layer - just the REST API conversion logic.
    This layer consists mostly of spring @Controller beans.
  2. Service layer - the purpose of this layer is to provide the business logic of the application. It is not aware of any web object such as locale, MultipartFile, etc., only data transfer objects. This layer is also the beginning and the end of the applicative transaction. The reason this layer is separated from the web layer is to allow invoking the services from multiple sources. For example, if later we discover that some services should run as background processes (scheduled jobs), then we don't want to re-write our business logic to fit a scheduled job - we just want to invoke the existing service only handling the job scheduling and invocation in our new code.
    This layer consists mostly of spring @Service beans which methods are annotated with @Transactional.
  3. Data access layer - the purpose of this layer is to access the database for querying and writing entities. In this reference implementation we are using both Hibernate and spring-data-jpa (more details about this below).
    This layer consists mostly of spring-data-jpa repositories which don't require and annotation.

 

The Entity

In this post the example application will handle the management of the entity person (because I'm really not that original). The Person is a simple Hibernate entity:

@Entity
@Table(name = "PERSONS")
@GenericGenerator(                         // define a simple portable id generator
        name = "portableIdGenerator",
        strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "EXAMPLE_SEQUENCE"),
                @Parameter(name = "initial_value", value = "1000"),
                @Parameter(name = "increment_size", value = "1")
        })
public class Person {
    @Id                                                  // the primary key is not writable by anyone except Hibernate
    @GeneratedValue(generator = "portableIdGenerator")
    private Long id;

    @Column(name = "FIRST_NAME")                        
    @Size(min = 1)                                       // use of JSR-303 validations
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    // getters, setters, constructors, equals, hashCode and toString implementations
}

 

The Data Access Layer

This layer's purpose, as mentioned, is to access the database and retrieve/persist the entities. From the entity you can see that I'm using Hibernate and JPA. Let's define the data source and the entity manager factory.

 

Implementing the DAL

In the example application we'll use an embedded in memory H2 database defined in Spring like so:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

	<jdbc:embedded-database id="dataSource" type="H2"/>

</beans>

 

In any case the data source definition is very simple and does not require us to define a connection pool or anything like that - all of that is done for us either by Spring in the example application and the in-memory database or by the web container in a real-life application because we are using a JNDI lookup.

 

The entity manager factory definition:

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
          p:dataSource-ref="dataSource"
          p:packagesToScan="com.hp.example"
            >
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" p:generateDdl="true"
                  p:showSql="true"/>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop>
                <prop key="hibernate.generate_statistics">false</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.jdbc.fetch_size">100</prop>
                <prop key="hibernate.jdbc.batch_size">500</prop>
                <prop key="hibernate.order_updates">true</prop>
                <prop key="hibernate.order_inserts">true</prop>
                <prop key="hibernate.default_batch_fetch_size">20</prop>
                <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
            </props>
        </property>
    </bean>

 

The above is just an example of an Hibernate + entity manager configuration. For convenience I turned on the "show SQL" feature - it will show up in the web container console. Real applications (as opposed to this example) will probably turn this off by default. In any case, there's nothing special about this definition.

 

The Data Access Layer main implementation class is the repository. In a classic Spring application this would be implemented with the @Repository. However, Spring data JPA provides a simpler cleaner (in my opinion) approach to implementing repositories - by using interfaces only and a naming convention for querying. The framework obviously supports custom queries for the non-obvious queries, and also support extending the repositories with custom code. However, I find it that most of the time, even in real application not just simple examples, it is possible to utilize the naming convention and to significantly simplify the code.

 

First let's define Spring data JPA support in our context (the full context is the applicationContext.xml and it can be found in the attached example sources):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <jpa:repositories base-package="com.hp.example.repositories"/>

</beans>

 

That's it - just define the bas package of the repository interfaces.

 

Let's examine the PersonRepository:

public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByFirstNameLike(String firstName);

    List<Person> findByLastNameLike(String lastName);

}

 

Yes - that's it. We have 2 queries - one for first name using "like" and one for the last name using "like". No implementation is required for this interface - Spring data JPA will proxy this interface at runtime and generate the underlying HQL required to fetch the data.

 

The JpaRepository interface provides several "obvious" methods such as save, delete, find by primary key and find all.

 

In my design different repositories should not call each other. This is hard to do anyway, because most repositories should be interfaces only - only custom extensions to repositories have implementation classes - and these should be rare. The idea is that a repository should handle a single entity only.

 

More information about Spring data JPA can be found here: http://www.springsource.org/spring-data/jpa.

 

Testing the DAL

Testing of the DAL is really testing the queries defined in the different repositories. In general I think that it is OK to skip these tests, but sometimes, when the queries are really complicated, it makes sense to test the queries directly. Remember that we don't need to test Spring data JPA provided implementations, so we'll never test find by primary key, find all etc.

 

To test our DAL we'll use Spring Test. This project provides several conveniences, shortcuts and syntactic sugars to simplify our test code. In this case I'm using JUnit 4.11, but if you must you can use TestNG.

 

When testing the DAL we do not want to start the entire application context. In fact, when writing a modular application, not a simple single module application as this example, then in these situations you probably don't have access to the application context XML - this usually resides in the web application module only. In addition, in the unit tests there is no reason to use a real database. In unit tests we always want to test with an embedded in-memory database.

 

To accommodate for all these requirements and to simplify our tests we'll use Spring's Java Configuration to define our context. Here's the PersonRepositoryTest code:

@ContextConfiguration
public class PersonRepositoryTest {
    @Autowired
    private PersonRepository personRepository;  // this is the real repository bean created by spring data JPA

    @Test
    public void findByFirstNameLike() {
        // the in-memory database is pre-populated with data from the import.sql
        List<Person> persons = personRepository.findByFirstNameLike("Mo%");

        verifyPersonExistsInList(persons, "Moshe", "Cohen");
    }

    @Test
    public void findByLastNameLike() {
        List<Person> persons = personRepository.findByLastNameLike("%oh%");

        verifyPersonExistsInList(persons, "Moshe", "Cohen");
    }

    private void verifyPersonExistsInList(List<Person> persons, String firstName, String lastName) {
        Person p = new Person();
        p.setFirstName(firstName);
        p.setLastName(lastName);

        assertThat(firstName + " " + lastName + " is not in the list", p, isIn(persons));
    }

    @Configuration                                       // the Spring Java Configuration class
    @EnableJpaRepositories(
            basePackages = "com.hp.example.repositories")// enable Spring data JPA repositories using scanning
    @EnableTransactionManagement              // enable transaction management - required for Spring data JPA
    public static class PersonRepositoryTestConfiguration {
        @Bean
        public DataSource dataSource() {
            // define the data source with Spring JDBC embedded database builder
            return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
        }

        // this bean is named because the @EnableJpaRepository expects a bean with this name
        @Bean(name = "entityManagerFactory")
        public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
            // define the EMF with standard Spring local container EMF factory bean
            LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
            factoryBean.setDataSource(dataSource());
            factoryBean.setPackagesToScan("com.hp.example");

            JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter() {
                {
                    setShowSql(true);
                    setGenerateDdl(true);
                }
            };

            factoryBean.setJpaVendorAdapter(vendorAdapter);
            factoryBean.setJpaProperties(additionalProperties());  // notice the use of additional properties

            return factoryBean;
        }

        // the additional properties for JPA - specific for Hibernate
        private Properties additionalProperties() {
            Properties props = new Properties();

            props.setProperty("hibernate.format_sql", "true");
            props.setProperty("hibernate.cache.use_query_cache", "false");
            props.setProperty("hibernate.generate_statistics", "false");
            props.setProperty("hibernate.cache.use_second_level_cache", "false");
            props.setProperty("hibernate.jdbc.fetch_size", "100");
            props.setProperty("hibernate.jdbc.batch_size", "500");
            props.setProperty("hibernate.order_updates", "true");
            props.setProperty("hibernate.order_inserts", "true");
            props.setProperty("hibernate.default_batch_fetch_size", "20");
            props.setProperty("hibernate.hbm2ddl.auto", "create-drop");

            return props;
        }

        @Bean
        public PlatformTransactionManager transactionManager() {
            // create a JPA transaction manager
            JpaTransactionManager transactionManager = new JpaTransactionManager();
            transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
            return transactionManager;
        }
    }

}

 

In the next blog entries:

- The service layer

The Web Layer

Testing the Web Layer

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
About the Author


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation