Update: I've written
a new blog entry with a link to code on GitHub that combines the latest versions of Scala, Spring, Hibernate and Maven. The original blog entry below is still a great walk-through for learning how to put together all the technologies from scratch.
The TechnologiesSo, 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 TaskThe 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 ProjectIf 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 MavenI 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 scalaThen 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 IDEI'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 {
def main(args: Array[String]) {
println( "Hello World!" );
}
}
and then ran it (in IDEA use: CTRL-SHIFT-F10)
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 working
Having proved that the
App
class was working - I DELETED IT!
Step 3: Create a Spring Web ApplicationI 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 MavenThen 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 @RequestMappingI 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 documentationDispatcherServlet javadocIntroduction to Spring MVC Web FrameworkJSTL 1.2 and maven-jetty-pluginThen 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.5Annotation-based controller configurationSpring Web MVC Views and resolving themThen 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 ObjectsServletRequest javadocAnd, 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 PluginOne 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 PluginThen on the command line I simply ran:
mvn clean package jetty:run
and 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 SpringI added a dependency for the latest version of Hibernate and Hibernate Annotations to the POM:
<dependency>
<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>
I also added a couple of dependencies that Hibernate would need:
<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>
Then I created a very simple Hibernate
@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 AnnotationsJavaBean Properties in ScalaNext, 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 AnnotationAnnotation-based controller configurationHibernate Session javadocUnderstanding the Spring Framework's declarative transaction implementationScala for accessing a Class at runtimeSpring ModelAndView javadocThen 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;"%>
<%@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>
and customer.jsp for viewing a
Customer
entity that's been retrieved from the database:
<%@page contentType="text/html;"%>
<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>
References:Spring Form Tag LibraryI 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>
<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>
Then I wrote the Spring config for Hibernate, and a BasicDataSource (using HSQLDB) in a file called spring-context.xml…
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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>
.. and wired it into the web.xml using Spring's ContextLoaderListener:
<context-param>
<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>
Lastly, I added an
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 implementationSpring Hibernate TutorialUnit-Testing Hibernate With HSQLDBIntroduction to Spring MVC Web FrameworkOpenSessionInViewInterceptorThen 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, ProblemsOkay, 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 IntelliJWhen 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 repositoryAfter 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 authority
I 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 annotationWhen 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 argumentsProblem 4: IntelliJ IDEA not compiling Scala codeWhile 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>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
<scope>provided</scope>
</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:
@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 LoggingWhen 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/LogFactory
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)
It appears that spring-web requires commons-logging, but doesn't declare it as a dependancy. I solved it by adding this to the POM:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
Problem 7: DispatcherServlet's Context Configuration FileHaving 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]
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)
By default, the
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 javadocProblem 8: Scala Syntax for Accessing a Class object at RuntimeWhen 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 runtimeProblem 9: Hibernate Depends on the javax.transaction / JTAWhen I first added Hibernate to the POM, I tried to compile (without adding any Hibernate code) and Maven gave me the following error:
Missing:
----------
1) javax.transaction:jta:jar:1.0.1B
Try downloading the file manually from:
http://java.sun.com/products/jta
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:
<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 HttpServletRequestWhen 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-apiProblem 11: Incompatible version of SLF4JAfter 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:
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:
<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 forumProblem 12: NoSuchMethodError in Hibernate AnnotationsI 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 objectWhen 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.html
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
I 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
CustomerController
:
@ModelAttribute("command")
def createCustomerForFormBinding = new Customer
Problem 14: Can't Access a Scala 'var' from JSPAfter 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 ScalaProblem 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 here
I 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.