Spring 3 maven 2 web application step by step

Before writting this I admin that there are lots of tutorials like this but what I found is that they are not step by step guides, with missing vital parts of process of making it all happen from scratch. This makes you revert to the documentation and therefore defies the point of going through the tutorial. With this one I hope to make it a bit clearer, though I do not attempt to make it IDE specific, therefore there will be no guidance on how to do it in specific IDE. The layout will be to work with standard Maven project structure.

What is in this tutorial?

  • Maven Spring project configurations
  • Persistence using Hibernate and HSQLDB
  • Cache support using Ehcahe
  • Spring MVC configurations
  • Example integration tests for DB and Caching (Please not there are no Unit tests as I assume that the reader is familiar with those already)

Link to the full set of sources for this tutorial:

denis-pavlov-spring3mvc-mvn2-example-updated.zip

First things first - the POM:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  2.     <modelversion>4.0.0</modelversion>  
  3.     <groupid>dp.examples</groupid>  
  4.     <artifactid>shoppingcart</artifactid>  
  5.     <version>0.0.1-SNAPSHOT</version>  
  6.     <packaging>war</packaging>  
  7.     <name>ShoppingCart</name>  
  8.   
  9.     <build>  
  10.         <plugins>  
  11.             <plugin>  
  12.                 <artifactid>maven-compiler-plugin</artifactid>  
  13.                 <configuration>  
  14.                     <source>1.5  
  15.                     <target>1.5</target>  
  16.                 </configuration>  
  17.             </plugin>  
  18.         </plugins>  
  19.     </build>  
  20.   
  21.     <!-- Shared version number properties -->  
  22.     <properties>  
  23.         <org.springframework.version>3.1.2.RELEASE</org.springframework.version>  
  24.   
  25.         <commons-dbcp.version>1.4</commons-dbcp.version>  
  26.         <sqlite-jdbc.version>3.7.2</sqlite-jdbc.version>  
  27.         <hsqldb-jdbc.version>2.2.9</hsqldb-jdbc.version>  
  28.   
  29.         <hibernate-core.version>4.1.9.Final</hibernate-core.version>  
  30.         <hibernate-search.version>4.2.0.Final</hibernate-search.version>  
  31.   
  32.         <xalan.version>2.7.0</xalan.version>  
  33.   
  34.         <backport-util-concurrent.version>3.1</backport-util-concurrent.version>  
  35.   
  36.         <ehcache.version>2.6.6</ehcache.version>  
  37.   
  38.     </properties>  
  39.   
  40.     <dependencies>  
  41.   
  42.         <dependency>  
  43.             <groupid>log4j</groupid>  
  44.             <artifactid>log4j</artifactid>  
  45.             <version>1.2.15</version>  
  46.             <exclusions>  
  47.                 <exclusion>  
  48.                     <groupid>javax.mail</groupid>  
  49.                     <artifactid>mail</artifactid>  
  50.                 </exclusion>  
  51.                 <exclusion>  
  52.                     <groupid>javax.jms</groupid>  
  53.                     <artifactid>jms</artifactid>  
  54.                 </exclusion>  
  55.                 <exclusion>  
  56.                     <groupid>com.sun.jdmk</groupid>  
  57.                     <artifactid>jmxtools</artifactid>  
  58.                 </exclusion>  
  59.                 <exclusion>  
  60.                     <groupid>com.sun.jmx</groupid>  
  61.                     <artifactid>jmxri</artifactid>  
  62.                 </exclusion>  
  63.             </exclusions>  
  64.             <scope>runtime</scope>  
  65.         </dependency>  
  66.   
  67.         <!-- 
  68.               JSP taglib support 
  69.           -->  
  70.         <dependency>  
  71.             <groupid>taglibs</groupid>  
  72.             <artifactid>standard</artifactid>  
  73.             <version>1.1.2</version>  
  74.         </dependency>  
  75.         <dependency>  
  76.             <groupid>javax.servlet</groupid>  
  77.             <artifactid>jstl</artifactid>  
  78.             <version>1.2</version>  
  79.         </dependency>  
  80.   
  81.   
  82.         <!--  
  83.               Core utilities used by other modules.  
  84.               Define this if you use Spring Utility APIs (org.springframework.core.*/org.springframework.util.*)  
  85.           -->  
  86.         <dependency>  
  87.             <groupid>org.springframework</groupid>  
  88.             <artifactid>spring-core</artifactid>  
  89.             <version>${org.springframework.version}</version>  
  90.         </dependency>  
  91.   
  92.         <!--  
  93.               Expression Language (depends on spring-core)  
  94.               Define this if you use Spring Expression APIs (org.springframework.expression.*)  
  95.           -->  
  96.         <dependency>  
  97.             <groupid>org.springframework</groupid>  
  98.             <artifactid>spring-expression</artifactid>  
  99.             <version>${org.springframework.version}</version>  
  100.         </dependency>  
  101.   
  102.         <!--  
  103.               Bean Factory and JavaBeans utilities (depends on spring-core)  
  104.               Define this if you use Spring Bean APIs (org.springframework.beans.*)  
  105.           -->  
  106.         <dependency>  
  107.             <groupid>org.springframework</groupid>  
  108.             <artifactid>spring-beans</artifactid>  
  109.             <version>${org.springframework.version}</version>  
  110.         </dependency>  
  111.   
  112.         <!--  
  113.               Aspect Oriented Programming (AOP) Framework (depends on spring-core, spring-beans)  
  114.               Define this if you use Spring AOP APIs (org.springframework.aop.*)  
  115.           -->  
  116.         <dependency>  
  117.             <groupid>org.springframework</groupid>  
  118.             <artifactid>spring-aop</artifactid>  
  119.             <version>${org.springframework.version}</version>  
  120.         </dependency>  
  121.   
  122.         <!--  
  123.               Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans)  
  124.               This is the central artifact for Spring's Dependency Injection Container and is generally always defined  
  125.           -->  
  126.         <dependency>  
  127.             <groupid>org.springframework</groupid>  
  128.             <artifactid>spring-context</artifactid>  
  129.             <version>${org.springframework.version}</version>  
  130.         </dependency>  
  131.   
  132.         <!--  
  133.               Various Application Context utilities, including EhCache, JavaMail, Quartz, and Freemarker integration  
  134.               Define this if you need any of these integrations  
  135.           -->  
  136.         <dependency>  
  137.             <groupid>org.springframework</groupid>  
  138.             <artifactid>spring-context-support</artifactid>  
  139.             <version>${org.springframework.version}</version>  
  140.         </dependency>  
  141.   
  142.         <!--  
  143.               Transaction Management Abstraction (depends on spring-core, spring-beans, spring-aop, spring-context)  
  144.               Define this if you use Spring Transactions or DAO Exception Hierarchy  
  145.               (org.springframework.transaction.*/org.springframework.dao.*)  
  146.           -->  
  147.         <dependency>  
  148.             <groupid>org.springframework</groupid>  
  149.             <artifactid>spring-tx</artifactid>  
  150.             <version>${org.springframework.version}</version>  
  151.         </dependency>  
  152.   
  153.         <!--  
  154.               JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, spring-tx)  
  155.               Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*)  
  156.           -->  
  157.         <dependency>  
  158.             <groupid>org.springframework</groupid>  
  159.             <artifactid>spring-jdbc</artifactid>  
  160.             <version>${org.springframework.version}</version>  
  161.         </dependency>  
  162.   
  163.         <!--  
  164.               Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis.  
  165.               (depends on spring-core, spring-beans, spring-context, spring-tx)  
  166.               Define this if you need ORM (org.springframework.orm.*)  
  167.           -->  
  168.         <dependency>  
  169.             <groupid>org.springframework</groupid>  
  170.             <artifactid>spring-orm</artifactid>  
  171.             <version>${org.springframework.version}</version>  
  172.         </dependency>  
  173.   
  174.         <!--  
  175.               Object-to-XML Mapping (OXM) abstraction and integration with JAXB, JiBX, Castor, XStream, and XML Beans.  
  176.               (depends on spring-core, spring-beans, spring-context)  
  177.               Define this if you need OXM (org.springframework.oxm.*)  
  178.           -->  
  179.         <dependency>  
  180.             <groupid>org.springframework</groupid>  
  181.             <artifactid>spring-oxm</artifactid>  
  182.             <version>${org.springframework.version}</version>  
  183.         </dependency>  
  184.   
  185.         <!--  
  186.               Web application development utilities applicable to both Servlet and Portlet Environments  
  187.               (depends on spring-core, spring-beans, spring-context)  
  188.               Define this if you use Spring MVC, or wish to use Struts, JSF, or another web framework with Spring (org.springframework.web.*)  
  189.           -->  
  190.         <dependency>  
  191.             <groupid>org.springframework</groupid>  
  192.             <artifactid>spring-web</artifactid>  
  193.             <version>${org.springframework.version}</version>  
  194.         </dependency>  
  195.   
  196.         <!--  
  197.               Spring MVC for Servlet Environments (depends on spring-core, spring-beans, spring-context, spring-web)  
  198.               Define this if you use Spring MVC with a Servlet Container such as Apache Tomcat (org.springframework.web.servlet.*)  
  199.           -->  
  200.         <dependency>  
  201.             <groupid>org.springframework</groupid>  
  202.             <artifactid>spring-webmvc</artifactid>  
  203.             <version>${org.springframework.version}</version>  
  204.         </dependency>  
  205.   
  206.         <!--  
  207.               Spring MVC for Portlet Environments (depends on spring-core, spring-beans, spring-context, spring-web)  
  208.               Define this if you use Spring MVC with a Portlet Container (org.springframework.web.portlet.*)  
  209.           -->  
  210.         <dependency>  
  211.             <groupid>org.springframework</groupid>  
  212.             <artifactid>spring-webmvc-portlet</artifactid>  
  213.             <version>${org.springframework.version}</version>  
  214.         </dependency>  
  215.   
  216.         <!--  
  217.               Support for testing Spring applications with tools such as JUnit and TestNG  
  218.               This artifact is generally always defined with a 'test' scope for the integration testing framework and unit testing stubs  
  219.           -->  
  220.         <dependency>  
  221.             <groupid>org.springframework</groupid>  
  222.             <artifactid>spring-test</artifactid>  
  223.             <version>${org.springframework.version}</version>  
  224.             <scope>test</scope>  
  225.         </dependency>  
  226.   
  227.         <!-- hibernate -->  
  228.         <dependency>  
  229.             <groupid>org.hibernate</groupid>  
  230.             <artifactid>hibernate-core</artifactid>  
  231.             <version>${hibernate-core.version}</version>  
  232.             <exclusions>  
  233.                 <exclusion>  
  234.                     <groupid>xml-apis</groupid>  
  235.                     <artifactid>xml-apis</artifactid>  
  236.                 </exclusion>  
  237.             </exclusions>  
  238.         </dependency>  
  239.         <dependency>  
  240.             <groupid>xalan</groupid>  
  241.             <artifactid>xalan</artifactid>  
  242.             <version>${xalan.version}</version>  
  243.         </dependency>  
  244.         <!-- dependency>  
  245.            <groupId>org.hibernate</groupId>  
  246.            <artifactId>hibernate-search</artifactId>  
  247.            <version>${hibernate-search.version}</version>  
  248.        </dependency -->  
  249.   
  250.         <!-- connection pooling -->  
  251.         <dependency>  
  252.             <groupid>commons-dbcp</groupid>  
  253.             <artifactid>commons-dbcp</artifactid>  
  254.             <version>${commons-dbcp.version}</version>  
  255.             <scope>runtime</scope>  
  256.         </dependency>  
  257.   
  258.         <!-- HSQLDB JDBC library -->  
  259.         <dependency>  
  260.             <groupid>org.hsqldb</groupid>  
  261.             <artifactid>hsqldb</artifactid>  
  262.             <version>${hsqldb-jdbc.version}</version>  
  263.             <scope>runtime</scope>  
  264.         </dependency>  
  265.   
  266.         <!-- EhCache -->  
  267.         <dependency>  
  268.             <groupid>net.sf.ehcache</groupid>  
  269.             <artifactid>ehcache-core</artifactid>  
  270.             <version>${ehcache.version}</version>  
  271.         </dependency>  
  272.   
  273.         <!-- Unit and integration testing -->  
  274.         <dependency>  
  275.             <groupid>junit</groupid>  
  276.             <artifactid>junit</artifactid>  
  277.             <version>4.9</version>  
  278.             <scope>test</scope>  
  279.         </dependency>  
  280.   
  281.   
  282.     </dependencies>  
  283. </project>  

At this point we have done all we need maven wise and can safely proceed to next step.

BTW if you are using eclipse and started of with a maven2 project and now trying to convert it to web projects I think you need to read this

We are making a webapp, so the second thing is web.xml (That should be in the src/main/webapp/WEB-INF/):

  1. <!--?xml version="1.0" encoding="UTF-8"?-->  
  2.   
  3. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee   
  4.          http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  5.     
  6.   <context-param>  
  7.     <param-name>log4jConfigLocation</param-name>  
  8.     <param-value>classpath:META-INF/properties/log4j.properties</param-value>  
  9.   </context-param>  
  10.   <listener>  
  11.     <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
  12.   </listener>  
  13.     
  14.   <!-- Spring context -->  
  15.   <context-param>  
  16.     <param-name>contextConfigLocation</param-name>  
  17.     <param-value>classpath*:WEB-INF/spring/*.xml</param-value>  
  18.   </context-param>  
  19.   <listener>  
  20.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  21.   </listener>  
  22.     
  23.   <!-- Spring webapp  -->  
  24.   <servlet>  
  25.     <servlet-name>spring</servlet-name>  
  26.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  27.     <init-param>  
  28.       <param-name>contextConfigLocation</param-name>  
  29.       <param-value>/WEB-INF/spring/*.xml</param-value>  
  30.     </init-param>  
  31.     <load-on-startup>1</load-on-startup>  
  32.   </servlet>  
  33.     
  34.   <servlet-mapping>  
  35.     <servlet-name>spring</servlet-name>  
  36.     <url-pattern>/</url-pattern>  
  37.   </servlet-mapping>  
  38.   
  39.   <welcome-file-list>  
  40.     <welcome-file>  
  41.       index.jsp  
  42.     </welcome-file>  
  43.   </welcome-file-list>  
  44.   
  45. </web-app>  

Then the Spring context configuration (for webapp that is located in WEB-INF/spring/ directory as specified in web.xml parameters to the servlet):

  1. <!--?xml version="1.0" encoding="UTF-8"?-->  
  2. <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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  3. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
  4. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
  5. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
  6. http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
  7.   
  8.     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  9.         <property name="locations">  
  10.             <list>  
  11.                 <!-- You can have multiple files here -->  
  12.                 <value>classpath:spring-context.properties</value>  
  13.             </list>  
  14.         </property>  
  15.     </bean>  
  16.   
  17.   
  18.     <import resource="classpath*:spring/cache.xml">  
  19.     <import resource="classpath*:spring/persistence.xml">  
  20.   
  21.     <!-- Service to manipulate the cart -->  
  22.     <bean id="cartService" parent="txProxyTemplate">  
  23.         <property name="target">  
  24.             <bean class="dp.example.shoppingcart.service.impl.WebCartServiceCached">  
  25.                 <constructor-arg index="0" ref="cartDao">  
  26.             </constructor-arg></bean>  
  27.         </property>  
  28.     </bean>  
  29.       
  30.     <!-- Scans the classpath of this application for @Components to deploy as beans -->  
  31.     <context:component-scan base-package="dp.example.shoppingcart.web">  
  32.   
  33.     <!-- Configures the @Controller programming model -->  
  34.     <mvc:annotation-driven>  
  35.   
  36.     <!-- Forwards requests to the "/" resource to the "welcome" view -->  
  37.     <mvc:view-controller path="/" view-name="cart">  
  38.   
  39.     <!-- Configures Handler Interceptors -->      
  40.     <mvc:interceptors>  
  41.         <!-- Changes the locale when a 'locale' request parameter is sent; e.g. /?locale=de -->  
  42.         <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">  
  43.     </bean></mvc:interceptors>  
  44.   
  45.     <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->  
  46.     <mvc:resources mapping="/resources/**" location="/resources/">  
  47.   
  48.     <!-- Saves a locale change using a cookie -->  
  49.     <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">  
  50.   
  51.     <!-- Application Message Bundle -->  
  52.     <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
  53.         <property name="basename" value="/WEB-INF/messages/messages">  
  54.         <property name="cacheSeconds" value="0">  
  55.     </property></property></bean>  
  56.   
  57.     <!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->  
  58.     <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
  59.         <property name="prefix" value="/WEB-INF/views/">  
  60.         <property name="suffix" value=".jsp">  
  61.     </property></property></bean>  
  62.   
  63.   
  64. </bean></mvc:resources></mvc:view-controller></mvc:annotation-driven></context:component-scan></import></import></beans>  

The main context file contains two imports: spring/cache.xml that contains ehcache configuration and spring/persistence.xml that contains hibernate configuration

  1. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util" xsi:schemalocation="http://www.springframework.org/schema/beans  
  2.        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  3.        http://www.springframework.org/schema/cache  
  4.        http://www.springframework.org/schema/cache/spring-cache.xsd  
  5.        http://www.springframework.org/schema/util  
  6.        http://www.springframework.org/schema/util/spring-util-3.2.xsd">  
  7.   
  8.     <cache:annotation-driven>  
  9.   
  10.     <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cachemanager-ref="ehcache">  
  11.   
  12.     <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configlocation="classpath:ehcache.xml" p:shared="true">  
  13.   
  14. </bean></bean></cache:annotation-driven></beans>  

Note the p:shared="true" attribute. This ensures that we are using singleton manager for the Ehcache, which is important if you would like to use the same cache manager for your webapp and your hibernate 2nd level cache. "p:configLocation="classpath:ehcache.xml" tells Spring that we what to use the src/main/resources/ehcache.xml for our cache configurations

  1. <ehcache updatecheck="false">  
  2.   
  3.     <defaultcache maxelementsinmemory="10000" overflowtodisk="false" eternal="false" timetoidleseconds="1800" timetoliveseconds="3600">  
  4.   
  5.     <!-- ############################################# cluster cache #######################################-->  
  6.   
  7.     <cache name="cart" maxelementsinmemory="100" overflowtodisk="false" eternal="false" timetoliveseconds="86400" timetoidleseconds="7200">  
  8.   
  9. </cache></defaultcache></ehcache>  

Persistence condifuration is quite simple. We setup dataSource first. Note that we use Spring variable substitution, which is defined by propertyConfigurer bean in context.xml that takes values from spring-context.properties. Then we define the hibernate factory and txProxyTemplate, which is used by transactional beans. It is good practice to manage transaction boundaries on the service layer. I.e. each method of service beans represents a unit of work and hence is a good candidate for a single transaction. For that purpose our Data access object is specifically not marked as transactional to prevent using it directly and hence have transaction per single db operation, which is not optimal. Instead we will make our service (WebCartServiceCached) transactional which will be using DAO and hence the transaction will be propagated to DAO.

  1. <!--?xml version="1.0" encoding="UTF-8"?-->  
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:ctx="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans  
  3.                            http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  4.                            http://www.springframework.org/schema/util  
  5.                            http://www.springframework.org/schema/util/spring-util-3.1.xsd  
  6.                            http://www.springframework.org/schema/context  
  7.                            http://www.springframework.org/schema/context/spring-context-3.1.xsd">  
  8.   
  9.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  10.         <property name="driverClassName" value="${db.driverClassName}">  
  11.         <property name="url" value="${db.url}">  
  12.         <property name="username" value="${db.username}">  
  13.         <property name="password" value="${db.password}">  
  14.     </property></property></property></property></bean>  
  15.   
  16.     <util:properties id="hibernateProperties" location="classpath:hibernate/hibernate-hsqldb.properties">  
  17.   
  18.     <!-- Hibernate ******************************************************************* -->  
  19.   
  20.     <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">  
  21.         <property name="dataSource" ref="dataSource">  
  22.         <property name="hibernateProperties" ref="hibernateProperties">  
  23.         <property name="mappingResources">  
  24.             <list>  
  25.                 <value>hibernate/mapping.hbm.xml</value>  
  26.             </list>  
  27.         </property>  
  28.     </property></property></bean>  
  29.   
  30.     <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">  
  31.         <property name="sessionFactory" ref="sessionFactory">  
  32.     </property></bean>  
  33.   
  34.     <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
  35.         <description>  
  36.             NOTE: see org.springframework.transaction.TransactionDefinition  
  37.   
  38.             DAO: all beans must be wrapped into this proxy to allow seamless  
  39.             transaction management integration via AOP.  
  40.         </description>  
  41.         <property name="transactionManager" ref="transactionManager">  
  42.         <property name="transactionAttributes">  
  43.             <props>  
  44.                 <!--<prop key="add*">PROPAGATION_REQUIRED,-Throwable</prop>-->  
  45.                 <!--<prop key="remove*">PROPAGATION_REQUIRED,-Throwable</prop>-->  
  46.                 <prop key="*">PROPAGATION_REQUIRED,-Throwable</prop>  
  47.             </props>  
  48.         </property>  
  49.     </property></bean>  
  50.   
  51.     <bean id="cartDao" class="dp.example.shoppingcart.dao.impl.CartDaoImpl">  
  52.         <constructor-arg index="0" ref="sessionFactory">  
  53.     </constructor-arg></bean>  
  54.   
  55. </util:properties></beans>  

DAO object is very simple and uses Hibernates current session which is provided by txProxyTemplate from WebCartService to perform DB operations. Note that if we to use DAO bean directly we would have a Hibernate exception and no session is available standalone.

  1. package dp.example.shoppingcart.dao.impl;  
  2.   
  3. import dp.example.shoppingcart.dao.CartDao;  
  4. import dp.example.shoppingcart.domain.impl.CartEntity;  
  5. import dp.example.shoppingcart.dto.Cart;  
  6. import dp.example.shoppingcart.dto.Item;  
  7. import dp.example.shoppingcart.service.CartService;  
  8. import org.hibernate.SessionFactory;  
  9.   
  10. import java.util.List;  
  11.   
  12. /** 
  13.  * Web implementation of cart service 
  14.  *  
  15.  * @author DPavlov 
  16.  */  
  17. public class CartDaoImpl implements CartDao  
  18. {  
  19.   
  20.     private final SessionFactory sessionFactory;  
  21.   
  22.     public CartDaoImpl(final SessionFactory sessionFactory) {  
  23.         this.sessionFactory = sessionFactory;  
  24.     }  
  25.   
  26.     public Cart createCart() {  
  27.         final Cart cart = new CartEntity();  
  28.         save(cart);  
  29.         return cart;  
  30.     }  
  31.   
  32.     public Cart findCart(final long pk) {  
  33.         return (Cart) sessionFactory.getCurrentSession().get(CartEntity.class, pk);  
  34.     }  
  35.   
  36.     public void save(final Cart cart) {  
  37.         sessionFactory.getCurrentSession().save(cart);  
  38.     }  
  39. }  

Our service uses DAO to provide business functions represented by WebCartServiceCached methods. For example #addToCart(final long pk, String item) will either find or create cart with specific primary key, add and item and persist the cart. All these db operations will happen in a single transaction. And if we have an exception all these changes are rolled back. So we have a good "all or nothing" strategy that will keep our db in a consistent state.

Caching is very simple. Facilitated by two annotations: @Cacheable(value = "cart") and @CacheEvict(value = "cart", allEntries = false, key = "#pk"). There are lots of configurations for this but this is the basic setup. @Cacheable tells ehcache to use cache named "cart" (this is why we have this entry in ehcache.xml) to keep results of this method. @CacheEvict allows to remove cache entries. In this case we remove a single entry by key 'pk', but it is possible to evict all items. As I already said it is very configurable.

  1. package dp.example.shoppingcart.service.impl;  
  2.   
  3. import dp.example.shoppingcart.dao.CartDao;  
  4. import dp.example.shoppingcart.dto.Cart;  
  5. import dp.example.shoppingcart.dto.Item;  
  6. import dp.example.shoppingcart.service.CartService;  
  7. import org.springframework.cache.annotation.CacheEvict;  
  8. import org.springframework.cache.annotation.Cacheable;  
  9.   
  10. import java.util.List;  
  11.   
  12. /** 
  13.  * Web implementation of cart service 
  14.  *  
  15.  * @author DPavlov 
  16.  */  
  17. public class WebCartServiceCached implements CartService  
  18. {  
  19.   
  20.     private final CartDao cartDao;  
  21.   
  22.     public WebCartServiceCached(final CartDao cartDao) {  
  23.         this.cartDao = cartDao;  
  24.     }  
  25.   
  26.     @CacheEvict(value = "cart", allEntries = false, key = "#pk")  
  27.     public void addToCart(final long pk, String item) {  
  28.         final Cart cart = findOrCreateCart(pk);  
  29.         cart.addItem(item);  
  30.         cartDao.save(cart);  
  31.     }  
  32.   
  33.     @Cacheable(value = "cart")  
  34.     public List<item> getItemsInCart(final long pk) {  
  35.         final Cart cart = findOrCreateCart(pk);  
  36.         return cart.getItems();  
  37.     }  
  38.   
  39.     @CacheEvict(value = "cart", allEntries = false, key = "#pk")  
  40.     public void removeFromCart(final long pk, String item) {  
  41.         final Cart cart = findOrCreateCart(pk);  
  42.         cart.removeItem(item);  
  43.         cartDao.save(cart);  
  44.     }  
  45.   
  46.   
  47.     private Cart findOrCreateCart(final long pk) {  
  48.         Cart cart = cartDao.findCart(pk);  
  49.         if (cart == null) {  
  50.             cart = cartDao.createCart();  
  51.         }  
  52.         return cart;  
  53.     }  
  54.   
  55.   
  56. }  
  57. </item>  

Basic controller uses @RequestMapping and @PathVariable to give a nice looking and SEO friendly url pattern.

  1. package dp.example.shoppingcart.web;  
  2.   
  3. import dp.example.shoppingcart.dto.Item;  
  4. import dp.example.shoppingcart.service.CartService;  
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.stereotype.Controller;  
  7. import org.springframework.web.bind.annotation.PathVariable;  
  8. import org.springframework.web.bind.annotation.RequestMapping;  
  9. import org.springframework.web.bind.annotation.RequestMethod;  
  10. import org.springframework.web.servlet.ModelAndView;  
  11.   
  12. import java.util.List;  
  13.   
  14. /** 
  15.  * Basic controller 
  16.  *  
  17.  * @author DPavlov 
  18.  */  
  19. @Controller  
  20. public class CartController {  
  21.       
  22.     private CartService cartService;  
  23.       
  24.     private int counter = 1;  
  25.   
  26.     @Autowired  
  27.     public CartController(CartService cartService) {  
  28.         this.cartService = cartService;  
  29.     }  
  30.       
  31.     @RequestMapping(value = "/cart/{pk}", method = RequestMethod.GET)  
  32.     public ModelAndView getCart(@PathVariable final long pk) {  
  33.         final List<item> list = cartService.getItemsInCart(pk);  
  34.         ModelAndView mav = new ModelAndView("cart");  
  35.         mav.addObject("pk", pk);  
  36.         mav.addObject("items", list);  
  37.         mav.addObject("add""Item" + counter++);  
  38.         return mav;  
  39.           
  40.     }  
  41.       
  42.     @RequestMapping(value = "/cart/{pk}/addtocart/{item}", method = RequestMethod.GET)  
  43.     public String addToCart(@PathVariable final long pk, @PathVariable final String item) {  
  44.           
  45.         cartService.addToCart(pk, item);  
  46.         return "redirect:/cart/" + pk;  // Do redirect to prevent double post  
  47.           
  48.     }  
  49.       
  50.     @RequestMapping(value = "/cart/{pk}/removefromcart/{item}", method = RequestMethod.GET)  
  51.     public String removeFromCart(@PathVariable final long pk, @PathVariable final String item) {  
  52.           
  53.         cartService.removeFromCart(pk, item);  
  54.         return "redirect:/cart/" + pk;  // Do redirect to prevent double post  
  55.           
  56.     }  
  57.   
  58. }  
  59. </item>  

Basic view (if you look at the spring context.xml file you will see that we are using spring resolver that look for views in WEB-INF/views/ and uses view name as file name with extension .jsp):

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>  
  2. <%@ page isELIgnored="false" %>  
  3. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  
  4. <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>  
  5.   
  6. <title>Items</title>  
  7.   
  8.   <h3>Items:</h3>  
  9.   <c:foreach var="item" items="${items}">  
  10.     <div>${item.articleNo} @ ${item.price}, qty: ${item.quantity}  <a href="${pageContext.request.contextPath}/cart/${pk}/removefromcart/${item.articleNo}">[-] remove</a> <a href="${pageContext.request.contextPath}/cart/${pk}/addtocart/${item.articleNo}">[+] add</a></div>  
  11.   </c:foreach>  
  12.   <a href="${pageContext.request.contextPath}/cart/${pk}/addtocart/${add}">add "${add}"</a>  

${pageContext.request.contextPath} in jsp allows to reference the root of your webapp. This is very usefull if your webapp context name changes from environment to environment as this kind of url building approach will work everywhere consistently. Now if you navigate to localhost:8080/cart/1 you will see contents for cart with PK 1.

Here is screenshot of the outcome:

This sums up the basic Spring webapp project. Now we can have a look at how we can do integration tests for what we have written.

Firstly we need an alternative Spring context where we can remove all the MVC part so we can use it for core testing (i.e. services, dao). Note: that I am jumping to integration tests straight away since I assume that you know (and already done) unit tests. Integration tests is the final frontier! Before doing such tests appropriate unit test needs to be done! Please do not substitute these kind of tests for unit tests as they are considerably slower to run and much harder to test thoroughly.

  1. package dp.example.shoppingcart.service.impl;  
  2.   
  3. import dp.example.shoppingcart.dto.Item;  
  4. import dp.example.shoppingcart.service.CartService;  
  5. import org.junit.Test;  
  6. import org.junit.runner.RunWith;  
  7. import org.springframework.beans.factory.annotation.Autowired;  
  8. import org.springframework.test.context.ContextConfiguration;  
  9. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
  10. import org.springframework.transaction.annotation.Transactional;  
  11.   
  12. import java.math.BigDecimal;  
  13. import java.util.HashMap;  
  14. import java.util.List;  
  15. import java.util.Map;  
  16.   
  17. import static org.junit.Assert.*;  
  18.   
  19. /** 
  20.  * User: denispavlov 
  21.  * Date: 29/11/2013 
  22.  * Time: 13:17 
  23.  */  
  24. @RunWith(SpringJUnit4ClassRunner.class)  
  25. @ContextConfiguration(locations = { "/spring/testContext.xml" })  
  26. @Transactional  
  27. public class WebCartServiceCachedTest {  
  28.   
  29.     @Autowired  
  30.     private CartService cartService;  
  31.   
  32.     /** 
  33.      * This is NOT unit test but rather an integration suite that proves that 
  34.      * our business logic works as expected using actual db and all the wiring. 
  35.      */  
  36.     @Test  
  37.     public void testCartCRUDOperationsIntegrationTest() throws Exception {  
  38.   
  39.         List<item> items;  
  40.   
  41.         // Check empty initially  
  42.         items = cartService.getItemsInCart(1L);  
  43.   
  44.         assertNotNull(items);  
  45.         assertTrue(items.isEmpty());  
  46.   
  47.         // Add one item and check contents of cart  
  48.         cartService.addToCart(1L, "ABC");  
  49.         items = cartService.getItemsInCart(1L);  
  50.   
  51.         assertNotNull(items);  
  52.         assertFalse(items.isEmpty());  
  53.         assertEquals(1, items.size());  
  54.         assertEquals("ABC", items.get(0).getArticleNo());  
  55.         assertEquals(new BigDecimal(1).compareTo(items.get(0).getQuantity()), 0);  
  56.   
  57.         // Add same item and check contents of cart  
  58.         cartService.addToCart(1L, "ABC");  
  59.         items = cartService.getItemsInCart(1L);  
  60.   
  61.         assertNotNull(items);  
  62.         assertFalse(items.isEmpty());  
  63.         assertEquals(1, items.size());  
  64.         assertEquals("ABC", items.get(0).getArticleNo());  
  65.         assertEquals(new BigDecimal(2).compareTo(items.get(0).getQuantity()), 0);  
  66.   
  67.         // Add another item and check contents of cart  
  68.         cartService.addToCart(1L, "DEF");  
  69.         items = cartService.getItemsInCart(1L);  
  70.   
  71.         assertNotNull(items);  
  72.         assertFalse(items.isEmpty());  
  73.         assertEquals(2, items.size());  
  74.         // we re-map the items in list to have deterministic assertions  
  75.         final Map<string, item=""> byArticle = new HashMap<string, item="">();  
  76.         for (final Item item : items) {  
  77.             byArticle.put(item.getArticleNo(), item);  
  78.         }  
  79.         // deterministic assertions by article no  
  80.         assertTrue(byArticle.containsKey("ABC"));  
  81.         assertEquals(new BigDecimal(2).compareTo(byArticle.get("ABC").getQuantity()), 0);  
  82.         assertTrue(byArticle.containsKey("DEF"));  
  83.         assertEquals(new BigDecimal(1).compareTo(byArticle.get("DEF").getQuantity()), 0);  
  84.   
  85.         // Remove one item  
  86.         cartService.removeFromCart(1L, "DEF");  
  87.         items = cartService.getItemsInCart(1L);  
  88.   
  89.         assertNotNull(items);  
  90.         assertFalse(items.isEmpty());  
  91.         assertEquals(1, items.size());  
  92.         assertEquals("ABC", items.get(0).getArticleNo());  
  93.         assertEquals(new BigDecimal(2).compareTo(items.get(0).getQuantity()), 0);  
  94.   
  95.         // Remove last item  
  96.         cartService.removeFromCart(1L, "ABC");  
  97.         items = cartService.getItemsInCart(1L);  
  98.   
  99.         assertNotNull(items);  
  100.         assertTrue(items.isEmpty());  
  101.   
  102.     }  
  103.   
  104.     /** 
  105.      * Since we already established that the CRUD works assertions in this test are specific 
  106.      * to making sure that we get the cached list back, so we do not need to check individual elements. 
  107.      * 
  108.      * @throws Exception 
  109.      */  
  110.     @Test  
  111.     public void testCachingIsWorking() throws Exception {  
  112.   
  113.         // Check empty initially  
  114.         List<item> items1st = cartService.getItemsInCart(2L);  
  115.         assertEquals(0, items1st.size());  
  116.   
  117.         // add cart item  
  118.         cartService.addToCart(2L, "ABC");  
  119.         List<item> items2nd = cartService.getItemsInCart(2L);  
  120.         assertEquals(1, items2nd.size());  
  121.   
  122.         // now we can ask for the cart contents again and assert that is it same instance of list  
  123.         List<item> items3rd = cartService.getItemsInCart(2L);  
  124.         assertSame(items3rd, items2nd);  
  125.   
  126.         // we expect that adding items will evict cache  
  127.         cartService.addToCart(2L, "ABC");  
  128.         List<item> items4th = cartService.getItemsInCart(2L);  
  129.         assertEquals(1, items4th.size());  
  130.         // but these list should be different now  
  131.         assertNotSame(items2nd, items4th);  
  132.   
  133.         // now check the cart contents again which should be cached object from 4th call  
  134.         List<item> items5th = cartService.getItemsInCart(2L);  
  135.         assertSame(items4th, items5th);  
  136.   
  137.         // make sure that after adding to a different cart we still have cached version of list for 2L  
  138.         cartService.addToCart(1L, "ABC");  
  139.         List<item> items6th = cartService.getItemsInCart(2L);  
  140.         assertSame(items4th, items6th);  
  141.   
  142.   
  143.     }  
  144. }  
  145. </item></item></item></item></item></item></string,></string,></item>  

The important elements are: include JUnit dependency in pom.xml, use @RunWith and @ContextConfiguration to tell JUnit what context configuration file you want to use. If you are testing DAO objects in isolation you can add @Transactional annotation to your Test suite at class level to provide the transaction support.

You also may have noticed that we use alternative context file /spring/testContext.xml. This context imports our original cache.xml and persistence.xml but provides override for spring-context.properties to supply alternative data source configurations and also does not contain the MVC configuration which are not needed for this test.

  1. <!--?xml version="1.0" encoding="UTF-8"?-->  
  2. <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" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  3. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
  4.   
  5.   
  6. http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
  7.   
  8.   
  9.     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  10.         <property name="locations">  
  11.             <list>  
  12.                 <!-- spring-testContext will override any properties in spring-context as it comes after -->  
  13.                 <value>classpath:spring-context.properties</value>  
  14.                 <value>classpath:spring-testContext.properties</value>  
  15.             </list>  
  16.         </property>  
  17.     </bean>  
  18.   
  19.     <import resource="classpath*:spring/cache.xml">  
  20.     <import resource="classpath*:spring/persistence.xml">  
  21.   
  22.     <!-- Service to manipulate the cart -->  
  23.     <bean id="cartService" parent="txProxyTemplate">  
  24.         <property name="target">  
  25.             <bean class="dp.example.shoppingcart.service.impl.WebCartServiceCached">  
  26.                 <constructor-arg index="0" ref="cartDao">  
  27.             </constructor-arg></bean>  
  28.         </property>  
  29.     </bean>  
  30.   
  31. </import></import></beans>  

Hope you enjoyed this tutorial and let me know if any information is inaccurate or incomplete. Here is link to the full source for this:

denis-pavlov-spring3mvc-mvn2-example-updated.zip

PLEASE BARE IN MIND THIS IS AN EXAMPLE TO SHOW HOW SPRING MAVEN PROJECT IS SETUP - THIS IS NOT PRODUCTION CODE AND NEVER WAS INTENDED TO IMITATE ONE.

This page was last updated on: 29/11/2013 15:23
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值