The Technologies
So, this post is simply a recipe for combining four of today's most popular technologies: Spring, Hibernate, Maven and Scala.
In case you're new to some of these technologies, here's a REALLY quick overview:
Spring is a framework for creating enterprise applications that uses Inversion of Control, Dependency Injection, Aspect-Oriented Programming (including annotation-based transaction boundaries) and lots of flexible, highly-extensible components for building pretty much anything. Its Model-View-Controller (MVC) web framework has been a popular replacement for Struts.
Hibernate is an Object-Relational Mapping library, used to store data from Java objects into relational databases (and retrieve it again).
Maven is a Java-based and -focussed build tool that uses a common project lifecycle and a declarative Project Object Model (POM) to make it simple to build applications by defining only what needs to happen, rather than how it happens (which remains in the implementation details of its plugins).
Scala is a Functional/Object-Oriented hybrid language that executes on the Java Virtual Machine (JVM) and is becoming increasingly popular, especially with Java developers, mostly due to its concise syntax, static typing and seamless integration with the Java SE API.
The Task
The task is to write a simple webapp that can persist and retrieve an entity. The goal is to use the latest of everything, including Scala 2.8, Hibernate 3.2.7 with annotations and Spring 3 and it's annotation-based controllers for creating REST-ful webapps.
The first part of this tutorial is basically just a diary of the steps I took to create a working application using Maven, Scala, Spring and Hibernate. I won't do a lot of explaining because different readers will obviously have different knowledge about different technologies, but I've documented every single web page that gave me a useful piece of information in putting the whole thing together.
The second part is a documentation of all the little things that went wrong while creating the "how to". Including these in the main introduction would just be a distraction but I'm sure the fixes I had to discover and correct will be useful to a few people scouring the internet for solutions one day.
Enough chatter, let's write some code…
No, wait!
Let's GENERATE some code! :)
Step 1: Use Maven to Create a Project
If you don't already have Maven installed, you'll need to download and install it and put its bin/ directory on your path.
References:
Download Maven
I already had it installed (although I don't remember installing it - is it standard on a Mac?) so I went to the directory where I wanted the project to live and created a new Maven project using the Scala archetype with this command:
mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create \
-DarchetypeGroupId=org.scala-tools.archetypes \
-DarchetypeArtifactId=scala-archetype-simple \
-DarchetypeVersion=1.1 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=au.com.belmonttechnology -DartifactId=scala-spring-hibernate
References:
Scala Blogs: maven for scala
Then I went into the directory and built the project:
mvn clean package
After downloading lots of plugin code, it successfully compiled the App and AppTest classes and then the AppTest.testKO test case failed. No need to panic - the implementation of this generated Test shows that it's meant to fail. It proves that Maven is running the test.
Too easy. Onwards and upwards...
Step 2: Get Scala and Maven Working in Your IDE
I'm using IntelliJ IDEA 9 Community Edition (it's free!). I was dead-simple to create a new IDEA project using the Maven POM that was just generated. Here are the steps:
1. New Project
2. Import project from external model
3. Maven
4. Choose directory
5a. Only tick 'Import Maven projects automatically' and 'Use Maven output directories'
5b. Use 'process-test-resources'
6. Finish!
You'll also need to make sure you've got a Scala plugin for your IDE installed and working. In IDEA, you can get one just by going into the Plugins config pan, searching for Scala and downloading the one that pops up.
To make sure the IDE was set up properly, I opened the
App
class, and changed it to look like this:object App {and then ran it (in IDEA use: CTRL-SHIFT-F10)
def main(args: Array[String]) {
println( "Hello World!" );
}
}
I like to live on the edge, and I noticed that the Scala version that the Maven archetype had delivered me was 2.6.1. Pfh!
So, I opened up http://scala-tools.org/repo-releases/org/scala-lang/scala-library/ in Firefox and found the latest and greatest (and hopefully somewhat stable) Scala release version, which was 2.8.0.Beta1-RC7 at the time of writing. I copied this version into the top of pom.xml, where the archetype had written:
<scala.version>2.6.1</scala.version>I ran the
App
class again to make sure it's still workingHaving proved that the
App
class was working - I DELETED IT!Step 3: Create a Spring Web Application
I added the following two entries into the
<repositories>
section of the POM:<repository>
<id>com.springsource.repository.bundles.release</id>
<name>EBR Spring Release Repository</name>
<url>http://repository.springsource.com/maven/bundles/release</url>
</repository>
<repository>
<id>com.springsource.repository.bundles.external</id>
<name>EBR External Release Repository</name>
<url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
References:
Obtaining Spring 3 Artifacts with Maven
Then I added the following line to the
<properties>
section:<org.springframework.version>3.0.0.RELEASE</org.springframework.version>
and added the following dependencies to the
<dependencies>
section:<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.transaction</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.web.servlet</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
I wanted to get Spring working before I tried to integrate Hibernate, so I created a new package,
au.com.belmonttechnology.spring.web
, and created a new Scala class in there called HelloWorldController
, and wrote this code:@Controller
class HelloWorldController {
@RequestMapping(Array("/hello.html"))
def showHello = "helloPage"
}
References:
Mapping requests with @RequestMapping
I ran
mvn clean package
again to make sure this little guy compiled.Then I created a src/main/webapp/WEB-INF directory and created a web.xml file with a basic
DispatcherServlet
setup:<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="scala-spring-hibernate"
version="2.5">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/web-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
References:
DispatcherServlet reference documentation
DispatcherServlet javadoc
Introduction to Spring MVC Web Framework
JSTL 1.2 and maven-jetty-plugin
Then I created the web-context.xml file that is used to initialise the Spring context for the
DispatcherServlet
. I set up a component-scan for the package that contains the HelloWorldController and inserted the same UrlBasedViewResolver configuration that everyone else in the world is using:<?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:component-scan
base-package="au.com.belmonttechnology.spring.web"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
References:
Annotation-Based Autowiring in Spring 2.5
Annotation-based controller configuration
Spring Web MVC Views and resolving them
Then I created the JSP that the
HelloWorldController
was returning as it's view: src/main/webapp/WEB-INF/jsp/helloPage.jsp<%@page contentType="text/html;"%>
<html>
<body>
<h1>Hello from ${pageContext.request.serverName}</h1>
</body>
</html>
References:
JSTL Implicit Objects
ServletRequest javadoc
And, finally, I changed the POM's packaging to be 'war':
<packaging>war</packaging>
At this point, I was able to
mvn clean install
the project and everything builds, but there's no way of running the webapp...Step 4: Start the Webapp with Maven using the Jetty Plugin
One of the easiest ways I've found of getting a webapp to run for testing is to use the Jetty Maven Plugin. All I had to do was add the Jetty plugin to the POM:
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.0.2.v20100331</version>
</plugin>
References:
Jetty Maven Plugin
Then on the command line I simply ran:
mvn clean package jetty:runand it all started up brilliantly.
I accessed http://localhost:8080/hello.html and saw a beautiful thing:
Hello from localhost
So, with my Spring 3 / REST / Scala app running on Jetty, I moved onto Hibernate…
Step 5: Get Hibernate Running in Spring
I added a dependency for the latest version of Hibernate and Hibernate Annotations to the POM:
<dependency>I also added a couple of dependencies that Hibernate would need:
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.4.0.GA</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>Then I created a very simple Hibernate
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.5.8</version>
</dependency>
@Entity
class in Scala:package au.com.belmonttechnology.data
import javax.persistence.{GeneratedValue, Id, Entity}
import reflect.BeanProperty
@Entity
class Customer() {
@Id @GeneratedValue
var id: Long = 0
@BeanProperty
var name: String = null;
}
References:
Hibernate Annotations
JavaBean Properties in Scala
Next, I created a controller that would be able to serve a "Create New Customer" form, receive the POST submission from the form and redirect to a JSP that would display the newly-stored entity:
package au.com.belmonttechnology.spring.web
import org.springframework.stereotype.Controller
import au.com.belmonttechnology.data.Customer
import org.springframework.beans.factory.annotation.Autowired
import org.hibernate.SessionFactory
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.{PathVariable, ModelAttribute, RequestMapping}
import org.springframework.web.bind.annotation.RequestMethod._
import org.springframework.web.servlet.ModelAndView
@Controller
class CustomerController {
implicit def sessionFactory2Session(sf: SessionFactory) =
sf.getCurrentSession();
@Autowired
var sessionFactory: SessionFactory = null
@ModelAttribute("command")
def createCustomerForFormBinding = new Customer
@RequestMapping(value = Array("/customers/new"), method = Array(GET))
def showNewCustomerForm() = "newCustomer"
@Transactional
@RequestMapping(value = Array("/customers/new"), method = Array(POST))
def createNewCustomer(@ModelAttribute("command") customer: Customer) =
"redirect:/customers/" + sessionFactory.save(customer) + ".html"
@Transactional(readOnly = true)
@RequestMapping(
value = Array("/customers/{customerId}"), method = Array(GET))
def viewCustomer(@PathVariable customerId: Long) =
new ModelAndView("customer", "customer",
sessionFactory.get(classOf[Customer], customerId))
}
References:
Spring @Autowired Annotation
Annotation-based controller configuration
Hibernate Session javadoc
Understanding the Spring Framework's declarative transaction implementation
Scala for accessing a Class at runtime
Spring ModelAndView javadoc
Then I created JSPs to match the views that I'd specified in the controller - newCustomer.jsp for submitting a POST to create a new Customer in the database:
<%@page contentType="text/html;"%>and customer.jsp for viewing a
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<body>
<h1>New Customer</h1>
<form:form>
<form:label path="name">Name</form:label>
<form:input path="name"/>
<input type="submit" label="Create Customer"/>
</form:form>
</body>
</html>
Customer
entity that's been retrieved from the database:<%@page contentType="text/html;"%>References:
<html>
<body>
<h1>${customer.name}</h1>
<p>Might I suggest enhancing the system to support more than just a customer's name?</p>
</body>
</html>
Spring Form Tag Library
I decided I need a quick and dirty in-memory database to test with, so I added the dependency for HSQLDB to the POM, as well as the Apache Commons Database Connection Pool:
<dependency>Then I wrote the Spring config for Hibernate, and a BasicDataSource (using HSQLDB) in a file called spring-context.xml…
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>.. and wired it into the web.xml using Spring's ContextLoaderListener:
<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"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.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:mem:scala-spring-hibernate"/>
<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="annotatedClasses">
<list>
<value>au.com.belmonttechnology.data.Customer</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.HSQLDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
</beans>
<context-param>Lastly, I added an
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-context.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
OpenSessionInViewInterceptor
into the Spring configuration in web-context.xml and specified this as an interceptor on the DefaultAnnotationHandlerMapping
:...
<bean id="openSessionInViewInterceptor"
class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list><ref bean="openSessionInViewInterceptor"/></list>
</property>
</bean>
...
References:
Understanding the Spring Framework's declarative transaction implementation
Spring Hibernate Tutorial
Unit-Testing Hibernate With HSQLDB
Introduction to Spring MVC Web Framework
OpenSessionInViewInterceptor
Then I fired it all up again with mvn clean package jetty:run, browsed to http://localhost:8080/customers/new.html and voila! I happily spent the rest of my evening delighting in entering new customers into my in-memory database. (Not.)
If you'd like to get your hands on this fantastic new application, you can download the source code from Belmont Technology (MIT licence).
Here's some books you might find useful if you plan to go further with Spring, Hibernate, Scala or Maven:
From Amazon
From The Book Depository
Spring in Action - Craig Walls & Ryan Breidenbach (Manning)
Hibernate in Action - Gavin King & Christian Bauer (Manning)
Spring Recipes - Gary Mak (Apress)
Programming in Scala - Martin Odersky, Lex Spoon & Bill Venners (Artima)
Maven - A Developer's Notebook - Vincent Massol & Timothy M. O'Brien (O'Reilly)
Problems, Problems, Problems
Okay, in all honesty, it was nowhere near as straightforward to create this simple webapp as the journal above makes out. There were a number of compilation errors, thrice as many runtime exceptions, and the number of Google searches required was approaching a googol. But for your benefit - either by help or by humour - I've documented below the error, cause and solution to all of the hitches I encountered along the way.
Problem 1: NoClassDefFound when running Scala App in IntelliJ
When I first tried to run the App class in IntelliJ, I got a
NoClassDefFoundError
as output.At this point, I opened the Maven tab and tried to run the 'test' phase, which told me that I didn't have a Maven Home Directory set, so I went into the project settings and fixed that.
After this, I was able to run the 'test' phase successfully, and then the App class ran successfully, too.
Problem 2: Invalid URI / Invalid Authority trying to access the Maven repository
After first adding Spring to the POM, I got some errors in my Maven pane in IDEA. I tried running the compile goal and got the following Maven error in the console:
Invalid uri 'http:// repository.springsource.com/maven/bundles/release/org/springframework/org.springframework.context/3.0.0.RELEASE/org.springframework.context-3.0.0.RELEASE.pom': Invalid authorityI copied and pasted this URL into Firefox, upon which it did a Google search, which is an unusual thing to do with a URL. Looking closer, I realised there was a space after http:// in the repository URL that I'd pasted into the POM. I deleted the space and was able to run the compile goal successfully (although, at this point, there was nothing to compile!)
Problem 3: Type Mismatch in Scala annotation
When I first created the
HelloWorldController
and tried to run the mvn package
phase, I got the following compilation error:[ERROR] /Users/graham/Code/scala-spring-hibernate/src/main/scala/au/com/belmonttechnology/spring/web/HelloWorldConroller.scala:16: error: type mismatch;
[INFO] found : java.lang.String("/hello.html")
[INFO] required: Array[java.lang.String]
[INFO] @RequestMapping("/hello.html")
It was easy enough to figure out how to fix this - in Scala, annotation parameters that are an array require
Array(…)
around them. In Java, you're allowed to leave the curly braces out when there's only one value for an array in an annotation, but Scala (at the moment) always requires an actual array.References:
Arrays in Scala annotation arguments
Problem 4: IntelliJ IDEA not compiling Scala code
While I was fixing the problem with the annotation, I noticed a much more worrying problem, which was that IDEA wasn't compiling my Scala code. Pressing CTRL-F9 (Command-F9, actually) did nothing, even if I changed the code and saved the file and tried again.
Coincidentally, I also noticed at this point that controller was actually spelled 'Conroller'. So I renamed it, and just after I did a little box flashed up saying a Scala file had been detected and did I want to install the Scala facet. I wasn't quick enough to click it the first time, so I had to rename the controller again and hit the 'Create Scala facet' link.
Then I tried to compile again and I was shown a dialog box with the message "Cannot compile Scala files. Please attach a scala-compiler.jar to any project module." Seeing as I'm using Maven, I decided to add the Scala compiler dependency to the POM instead of directly into IntelliJ (which I assumed might remove it on the next POM update anyway):
<dependency>I don't want to package the Scala compiler into my WAR, so I've used the 'provided' scope because this makes the artefacts available at compile time, but NOT at runtime. Now I compiled in IDEA, and finally I get the same error in IDEA that Maven was giving me earlier, and I fix the annotation on the controller to use an Array:
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
<scope>provided</scope>
</dependency>
@RequestMapping(Array("/hello.html"))
Problem 5: AppTest.testKO fails (even though I deleted it)
After getting IDEA compiling Scala properly, I executed the Maven
package
goal again and that silly AppTest.testKO
caused my build to fail, but I'd deleted the test class! I realised I hadn't run the 'clean' task for a while, so I did that and then it was all building successfully again.Problem 6: Spring Web requires Commons Logging
When I first tried to start Jetty, I got this error:
2010-01-16 13:59:12.392:WARN::FAILED ContextHandlerCollection@58a1a199: java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactoryIt appears that spring-web requires commons-logging, but doesn't declare it as a dependancy. I solved it by adding this to the POM:
2010-01-16 13:59:12.392:WARN::FAILED HandlerCollection@5b787144: java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
2010-01-16 13:59:12.392:WARN::Error starting handlers
java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:179)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
Problem 7: DispatcherServlet's Context Configuration File
Having fixed the commons-logging problem, I started Jetty again and got this error:
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]By default, the
Jan 16, 2010 2:01:07 PM org.springframework.web.servlet.FrameworkServlet initServletBean
SEVERE: Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:341)
DispatcherServlet
will look for a config file with the name of the servlet entry ('dispatcher', in my web.xml) with "-servlet.xml" tacked on the end. I got the DispatcherServlet to read my configuration file by adding this to the <servlet>
in web.xml:<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/web-context.xml</param-value>
</init-param>
References:
Spring FrameworkServlet javadoc
Problem 8: Scala Syntax for Accessing a Class object at Runtime
When I first wrote the
CustomerController.viewCustomer
method, I tried to use the Java syntax to access the Customer's corresponding (runtime) class object:sessionFactory.get(Customer.class, customerId)which didn't compile. After realising I'd never had to do this before in Scala, I went searching and found that the syntax for getting the class object representing a type at runtime in Scala is:
classOf[Customer]
References:
Scala for accessing a Class at runtime
Problem 9: Hibernate Depends on the javax.transaction / JTA
When I first added Hibernate to the POM, I tried to compile (without adding any Hibernate code) and Maven gave me the following error:
Missing:Since I was pretty sure that wouldn't be using any JTA features, I simply added an exclusion for it to the <dependency> in the POM:
----------
1) javax.transaction:jta:jar:1.0.1B
Try downloading the file manually from:
http://java.sun.com/products/jta
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.7.ga</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
I should mention that IntelliJ's POM integration here was AWESOME, allowing me to auto-complete the group and artefact IDs of the exclusion by showing me a list of all Hibernate's transitive dependencies! If any IDEA developers are reading this - you guys ROCK!
Problem 10: Spring can't find HttpServletRequest
When I tried to compile the
CustomerController
for the first time, I received this compilation error:Error: error while loading View, Missing dependency 'class javax.servlet.http.HttpServletRequest', required by /Users/graham/.m2/repository/org/springframework/org.springframework.web.servlet/3.0.0.RELEASE/org.springframework.web.servlet-3.0.0.RELEASE.jar(org/springframework/web/servlet/View.class)The problem this time was that spring-web, while relying heavily on the Servlet API, declares the dependency with the 'provided' scope, which is the right thing for the Spring guys to do but a small gotcha for people trying to use it. The solution is just to add a sevlet-api dependency into your own POM (which should ALSO use the 'provided' scope):
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
References:
Maven Repository: javax.servlet::servlet-api
Problem 11: Incompatible version of SLF4J
After adding the javax.servlet dependency, I started up Jetty again and Spring spewed out the following error along with a massive stack trace:
2010-01-16 21:47:54.804:WARN::Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in ServletContext resource [/WEB-INF/spring-context.xml]: Invocation of init method failed; nested exception is java.lang.IllegalAccessError: tried to access field org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory:I found a forum saying that the slf4j-api distribution had a different implementation of StaticLoggerBinder to the one in the slf4j implementation JARs, and that to get it to work you need to include both in your classpath (with the API having priority). The solution was to add the SLF4J API to the POM:
java.lang.IllegalAccessError: tried to access field org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.5.8</version>
</dependency>
References:
StaticLoggerBinder.SINGLETON IllegalAccessError on Hibernate forum
Problem 12: NoSuchMethodError in Hibernate Annotations
I was originally using Hibernate version 3.1.3, as there was some repository indexing page I had browsed suggested that was the latest. When I started up Jetty, I got this error message:
2010-01-16 21:52:06.913:WARN::Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in ServletContext resource [/WEB-INF/spring-context.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.hibernate.cfg.SecondPass.doSecondPass(Ljava/util/Map;)V:
java.lang.NoSuchMethodError: org.hibernate.cfg.SecondPass.doSecondPass(Ljava/util/Map;)V
I found a page where someone had the same problem and a respondent suggested they try a newer version of Hibernate, so I used IntelliJ's Maven integration to download the whole index of repo1.maven.org and then auto-complete the version for the Hibernate dependency, which showed me that the newest version of hibernate-annotaions was actually 3.4.0.ga. Switching to the latest version made the error go away.
Problem 13: Spring Form Binding Requires a Command object
When I first tried to view the newCustomer JSP, Jetty returned an error page showing this:
2010-01-16 21:54:19.068:WARN::/customers/new.htmlI had, as I often do, forgotten to provide a blank form object for the new form, which was easily solved by adding this method to the
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
CustomerController
:@ModelAttribute("command")
def createCustomerForFormBinding = new Customer
Problem 14: Can't Access a Scala 'var' from JSP
After providing the blank form object for the JSP to bind from, I got this error:
Invalid property 'name' of bean class [au.com.belmonttechnology.data.Customer]: Bean property 'name' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
I was actually expecting this one to happen! It occurred because a var in a Scala object, while publicly accessible, does not conform to the JavaBeans specification, which is what JSPs and JSTL use to inspect beans and retrieve data from them. Scala provides the
@BeanProperty
annotation that can be used to tell the compiler to generate getter and setter methods for a variable in order to make it accessible by JavaBeans-reliant tools.@BeanProperty
var name: String = null;
References:
JavaBean Properties in Scala
Problem 15:
When I first submitted a request to
CustomerController.createNewCustomer
, I got the following error:No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one hereI remembered that I had decided to experiment and see whether my
OpenSessionInViewInterceptor
would be automagically picked up by the default annotation handler mappings (some Spring beans do this for some of their dependenices). But, they weren't. Solution: define the mappings bean explicitly and inject the interceptor as a property:<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list><ref bean="openSessionInViewInterceptor"/></list>
</property>
</bean>
Problems solved.
Hope you enjoyed that.
Graham.
Great article. Just the kind of application that I was planning on doing myself. Gives me a great start. Thanks.
ReplyDeleteExcellent article.I appreciate your patience to write such a detailed post.I am going to try this
ReplyDeleteThanks for this, it was really helpful.
ReplyDeleteThe Maven repo now refers to Jetty 8.0.0 by default which doesn't work with the instructions/zip file you provided.
Adding 7.0.2.v20100331 makes it work. Ie,
org.mortbay.jetty
jetty-maven-plugin
7.0.2.v20100331
Also - you might want to edit the post to include the openSessionInViewInterceptor piece in the "clean" instructions. Right now it's only in the problems section.
ReplyDelete@Eric P: Thanks heaps for letting me know about those omissions; I've fixed them in the post.
ReplyDeleteCheers,
Graham.
Problem 16: Need antidote for XML poisoning. :)
ReplyDeleteExcellent. Very good article. I dont want to use Lift. Is it worth the effort to write a web application like this using Scala? What are the benefits of this over using Java?
ReplyDeleteThanks
Hi Aravin,
ReplyDeleteAt the very least, Scala removes a lot of the boilerplate normally associated with writing traditional webapps in Java. For instance, beans used for persistence or as Command objects or @ModelAttribute/s do not require getters or setters, but simply for the fields to be annotated with @BeanProperty. You also open up the ability to do some functional programming where you see fit, which can greatly reduce the noise in any complex business logic that your application contains.
In short, anything you can do in Java you can do in Scala - almost always with far less code - and Scala also provides developer-time-efficient paradigms that aren't available in Java.
I am using Grails Jetty Solr and I had this problem since yesterday when deployed war file into Jetty.
ReplyDeletejava.lang.IllegalAccessError: tried to access field org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
Reason was that war file was created with two jar files in it. slf4j-api-1.5.5.jar and slf4j-api-1.5.8.jar
Solution was to remove slf4j-api-1.5.5.jar from inside war file.
Thanks for the sample. It saves quite lots of time for me on how to integrating those popular frameworks.
ReplyDeleteHi Hieu. You're welcome. If your using the downloadable sample, you'll probably want to upgrade the POM for the latest versions of most of the dependencies.
ReplyDeleteI am trying to use your example but I am getting the following error..
ReplyDeleteFailed to execute goal org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create (default-cli) on project standalone-pom: Error creating from archetype: Archetype does not exist. Requested download does not exist. Could not find artifact org.scala-tools.archetypes:scala-archetype-simple:jar:1.1 in id0 (http://scala-tools.org/repo-releases)
Hi Jonathon.
DeleteI'm not sure why it isn't working for you, but I can see that there are newer versions of both the Maven Archetype Plugin and the Scala Simple Archetype available of Maven Central:
http://search.maven.org/#search|gav|1|g%3A%22org.apache.maven.plugins%22%20AND%20a%3A%22maven-archetype-plugin%22
http://search.maven.org/#search|gav|1|g%3A%22org.scala-tools.archetypes%22%20AND%20a%3A%22scala-archetype-simple%22
Perhaps updating one or both of those will resolve the issue.
Good luck!
Graham.