2010-06-24

Spring, JPA 2.0 & Maven - Hello World

In this article I will show you how to create a
  • Spring 3.0.3.RELEASE application
  • that is a desktop application (no application server/servlet container required) but might be deployed to a server too
  • and that connects to a database (HSQLDB)
  • using JPA 2.0
  • without using provider specific code (such as Hibernate) in Java files
  • and that allows for declarative transaction management (next article).

You can download the source code and you are free to use it in your projects. I'm using Eclipse 3.5.2 for this.


JPA persistence file - persistence.xml


This file should be located in a folder called META-INF in the root of your classpath. It is a standard JPA configuration file where you put data source specific configuration.

Here's the content:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="Dogs">
        <class>me.m1key.springtx.entities.Dog</class>

        <properties>
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />

            <property name="hibernate.connection.driver_class"
                    value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url"
                    value="jdbc:hsqldb:hsql://localhost/dogsdb" />

            <property name="hibernate.connection.username"
                    value="sa" />

            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements"
                    value="50" />
            <property name="hibernate.c3p0.idle_test_period"
                    value="3000" />

            <property name="hibernate.dialect"
                    value="org.hibernate.dialect.HSQLDialect" />

            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>

</persistence>

Things to note:
  • JPA is only an API that requires a concrete implementation - we are using Hibernate here; you can use anything you want though as long as it is JPA 2.0 compliant. There are going to be no references to Hibernate in Java code
  • Persistence unit name is Dogs
  • hibernate.hbm2ddl.auto is set to create so that tables are only dropped when the application starts (and not where it shuts down) - you can investigate the content after shutdown (see how)

If you are looking for more information on HSQLDB, I wrote an article on HSQLDB and Eclipse where you can find some basic information (and this project has it set up exactly the same way).


Maven pom.xml


Compile the project using Java 6

...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
...

Add a repository

This is for the latest Hibernate distribution.

...
    <repositories>
        <repository>
            <id>r.j.o-groups-public</id>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
        </repository>
    </repositories>
...

Correct dependencies

...
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate</artifactId>
            <version>3.5.3-Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>3.5.3-Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-annotations</artifactId>
            <version>3.5.3-Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-commons-annotations</artifactId>
            <version>3.3.0.ga</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>3.5.3-Final</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jpa</artifactId>
            <version>2.0.8</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.5.8</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.5.8</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>
...


Spring beans file - applicationContext.xml


This file is located directly in the classpath, I put it in src/main/resources (a Maven convention).

Entity Manager Factory

This is the JPA way of doing things in Spring. You do not declare a data source in your Spring beans file (it is declared in persistence.xml), but an entity manager factory instead. You give it the persistence unit name ("Dogs").

...
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="Dogs" />
    </bean>
...

JPA template

We are going to use JPA template from our DAO classes. It makes DAO operations a bit easier (and sometimes it doesn't). Note that Hibernate Template also exists and has methods that are easier to use than those of JPA template, but I promised no Hibernate in the code.

...
    <bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
...

A DAO class

DogsDao is our only DAO class and it needs the JPA Template.

...
    <bean id="dogsDao" class="me.m1key.springtx.dao.DogsDaoImpl">
        <property name="jpaTemplate" ref="jpaTemplate" />
    </bean>
...

An alternative approach would be to inject the entity manager factory directly, thus eliminating the need to use JPA templates at all.

A business logic class

Here's a business logic class and it receives the DAO class, setter-injected.

...
    <bean id="dogsBean" class="me.m1key.springtx.beans.DogsBean"
        scope="singleton">
        <property name="dogsDao" ref="dogsDao" />
    </bean>
...

Transaction management

The next article will tell you more about transaction management. For now, just use this (it is required by JPA).

...
    <tx:annotation-driven transaction-manager="myTransactionManager" />

    <bean id="myTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
...


Java code


The DAO class

package me.m1key.springtx.dao;

import java.util.List;

import me.m1key.springtx.entities.Dog;

import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.transaction.annotation.Transactional;

public class DogsDaoImpl extends JpaDaoSupport implements DogsDao {

    @SuppressWarnings("unchecked")
    public List<Dog> retrieveAllDogs() {
        return getJpaTemplate().find("from Dog");
    }

    public Long retrieveNumberOfDogs() {
        return (Long) getJpaTemplate().getEntityManagerFactory()
                .createEntityManager()
                .createQuery("select count(d) from Dog d").getSingleResult();
    }

    @Transactional
    public void persistDog(Dog dog) {
        getJpaTemplate().persist(dog);
    }

}
It extends JpaDaoSupport which gives us access to the getJpaTemplate() method. You don't have to extend this class - if you don't, just provide a getter and setter for the jpaTemplate property.

The first method retrieves all dogs and, sadly, needs a SuppressWarning annotation (collection cast).

The second method demonstrates how you can get access to the entity manager, if you need it. That kind of query (that returns a number) is called reporting query.

The third method persists an object. The @Transactional annotation is actually a requirement - without it no insertion occurs.

The DAO class

package me.m1key.springtx.dao;

import java.util.List;

import me.m1key.springtx.entities.Dog;

public interface DogsDao {

    List<Dog> retrieveAllDogs();

    Long retrieveNumberOfDogs();

    void persistDog(Dog dog);

}

The business logic class

A very basic sample that should give you an idea.

package me.m1key.springtx.beans;

import me.m1key.springtx.dao.DogsDao;
import me.m1key.springtx.entities.Dog;

public class DogsBean {

    private DogsDao dogsDao;

    /**
     * Returns true if there is at least one dog in the database.
     *
     * @return true if there is a dog in the database
     */
    public boolean containsDogs() {
        return dogsDao.retrieveNumberOfDogs() > 0;
    }

    /**
     * Persists the dog to the database.
     *
     * @param dog
     *            dog to persist
     */
    public void persistDog(Dog dog) {
        dogsDao.persistDog(dog);
    }

    protected DogsDao getDogsDao() {
        return dogsDao;
    }

    public void setDogsDao(DogsDao dogsDao) {
        this.dogsDao = dogsDao;
    }

}

The entity class

This is out Dog entity. No Hibernate, as promised.

package me.m1key.springtx.entities;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "DOGS")
public class Dog {

    private Long id;
    private String name;
    private static final long serialVersionUID = 1L;
    private List<String> nicknames = new ArrayList<String>();

    public Dog() {
        super();
    }

    @Id
    @GeneratedValue
    @Column(name = "DOG_ID")
    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "DOG_NAME")
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ElementCollection(fetch = FetchType.EAGER)
    public List<String> getNicknames() {
        return nicknames;
    }

    public void setNicknames(List<String> nicknames) {
        this.nicknames = nicknames;
    }

    @Override
    public String toString() {
        return "[" + id + ", " + name + "]";
    }

}

Let's launch it!

Finally, here's how you can see whether it works.

// ...
    public static void main(String[] args) {
        ApplicationContext appContext = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext.xml" });
        DogsBean dogsBean = appContext.getBean("dogsBean", DogsBean.class);

        Dog doggie = new Dog();
        doggie.setName("Sega");

        System.out.println("Any dogs: " + dogsBean.containsDogs());
        dogsBean.persistDog(doggie);
        System.out.println("Any dogs: " + dogsBean.containsDogs());
    }
// ...
It should first print false, then true - and lots of useful (or not) logs.


I hope this gives you an idea how to build such an application.

Issues and Solutions


Missing artifact org.hibernate:hibernate:jar:3.5.3-Final:compile

It happened to me that Maven was unable to fetch this lib - hibernate-3.5.3-Final.jar. If it happens to you, just download the correct (3.5.3-Final) Hibernate distribution ZIP/TAR file and put the missing file (it might be called hibernate3.jar) into your local repository manually (or your company corporate repository).

Caused by: java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist

Might happen when you run your application from Eclipse before you packaged it with Maven. That's a classpath issue. Just do Maven -> Clean and Maven -> Package, and then launch the application again.


I got my knowledge from here: Java Persistence with Hibernate

It helps me run this website when you buy the book via this link - thanks!

Download source code for this article

Sega

14 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. about yout first issue:
    correct solution is to fix dependency declaration.

    to first dependency add type pom:

    [dependency]
    [groupId]org.hibernate[/groupId]
    [artifactId]hibernate[/artifactId]
    [version]3.5.3-Final[/version]
    [type]pom[/type]
    [/dependency]

    ReplyDelete
  3. I understand that it's [type]pom[/type] that makes the difference? What does it do exactly?

    ReplyDelete
  4. that artifact in repository is just pom file listing modules (actual jars) and its packaging type set to pom. You can check this in repository.

    ReplyDelete
  5. So it's actually supposed to be like that because it just references other jars? And it's those other jars where my dependencies come from?

    ReplyDelete
  6. Hi, is it possible for you to guide me to develop web application using Spring 3,JPA and Maven. I am trying use this article but unable to do job for web application using maven. Can you explain me step by step may be simple login module as example.

    ReplyDelete
  7. @Naresh, what problems are you facing? You can download the working source code.

    ReplyDelete
  8. I followed your example about not using JpaTemplate. As it turns out, I created my own example and now I have a very nagging "Session was already closed" problem. Any insights from you would be greatly appreciated.

    ReplyDelete
  9. Ilango, it probably is a Hibernate problem - you are reading a lazy initialized collection after the session is closed, I suppose.

    ReplyDelete
  10. When i run the Launcher.java i got the below error

    Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/annotations/common/reflection/MetadataProvider

    Then i did below changes-


    org.hibernate
    hibernate-commons-annotations
    3.2.0.Final


    Now i managed to run the example

    ReplyDelete
  11. i get the below error while i supose to run the code .. help me pls

    Error: Could not find or load main class me.m1key.springtx.Launcher

    thanks in advance

    ReplyDelete
  12. كيف تحقق النجاح بالنسبة لخدمات كشف تسربات المياه بجدة حتي نكون علي اقتناع تام بأننا شركة كشف تسربات بجدة تقدم خدمات لا مثيل لها من خلال فنين يمتلكون القدرة علي اكتشاف جميع انواع التسريبات من خلال تدريبهم العالي علي الاجهزة الحديثة التي تساعد علي ذلك بدون هدم او تكسير كل ذلك سوف تجدونه عندما تتواصلوا مع شركة كشف تسربات المياه بجدة والتي تقدم كل ما لديها لمساعدتكم

    ReplyDelete