2010-06-23

Eclipse JPA with Hibernate & HSQLDB. Part 2

Did you see Part 1 of the series where I explained how to create an Eclipse JPA project?

In this part of the series I will show some basic functionalities of Eclipse JPA as we create a very simple project.


Creating an entity class


It is easy to create an entity class. Right click on a package, New -> Entity.
Creating an entity class

Note: In our scenario we are not using a legacy database, so we will let Hibernate create all tables in the database for our entities.

Now you can specify the entity name:
Fill entity name

You can add properties:
Adding a property

Access type
  • Field - annotate the field itself (e.g. with @Id)
  • Property - annotate the getter

You can also provide an alternative name for the table (other than the one derived from the class name):
Table name - DOGS

It's better to use the property one.

Let's see the generated code.

package me.m1key.dogs.dao;

import java.io.Serializable;
import java.lang.Long;
import java.lang.String;
import javax.persistence.*;

/**
 * Entity implementation class for Entity: Dog
 *
 */
@Entity
@Table(name = "DOGS")
public class Dog implements Serializable {

    private Long id;
    private String name;
    private static final long serialVersionUID = 1L;

    public Dog() {
        super();
    }

    @Id
    public Long getId() {
        return this.id;
    }

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

    public String getName() {
        return this.name;
    }
    //... Omitted for brevity.
}

You have a no-argument constructor (that's a JPA requirement). You have an ID annotated with @Id, the class is annotated with @Entity and you have the @Table annotation so that an alternative table name can be specified.

Note that in the peristence.xml file content was created automatically. It should look more or less like this.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
    
    <persistence-unit name="Dogs">
        <class>me.m1key.dogs.entities.Dog</class>
    </persistence-unit>
    
</persistence>


Eclipse JPA validation


At this moment, if connected to your HSQLDB server, you might get the following error:
Table "DOGS" cannot be resolved
Table cannot be resolved

That's right, the table does not exist because we are not using a legacy database. We should let Hibernate create this table for us.


What is missing?


We specified the ID column, but we did not specify how to get its value. Use the @GeneratedValue annotation.
import javax.persistence.GeneratedValue;

    // ...

    @Id
    @GeneratedValue
    public Long getId() {
        return this.id;
    }

    // ...

Compared to the project from the first part of the tutorial, you also need to add a dependency to slf4j-simple-1.5.8.jar.

One more thing we must add is database specific stuff in persistence.xml. Eclipse knows how to connect to the database, but your project doesn't.

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

        <properties>
            <property name="hibernate.archive.autodetecion" value="class, hbm" />

            <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>
...

The hibernate.hbm2ddl.auto paramater is set to create because it is more convenient for us in this situation. It means that the database is dropped and recreated on application start-up, but is not dropped after it shuts down, so that you can investigate the content of it after the application has run.


DAO


Let's create simple DAO classes, namely a generic DAO and DogDAO.

package me.m1key.dogs.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
 * Generic DAO for other DAOs to extend.
 *
 * @author Michal
 *
 */
public abstract class GenericDAO {

    private static EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("Dogs");

    public EntityManager createEntityManager() {
        return emf.createEntityManager();
    }

    public static void closeEntityManager() {
        emf.close();
    }

}
This is a simple generic DAO. All it does is it allows for using its createEntityManager method from extending classes. This is temporary design, not a recommended way of doing it, mind you.

Now, look at the createEntityManagerFactory method - it specifies the persistence unit name. It is the very same name which is specified in our persistence.xml file.

Let's create the DogDAO then.

package me.m1key.dogs.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import me.m1key.dogs.entities.Dog;

/**
 * Dog DAO.
 *
 * @author MichalH
 *
 */
public class DogDAO extends GenericDAO {

    public void persistDog(Dog dog) {
        EntityManager em = createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        em.persist(dog);

        tx.commit();
        em.close();
    }

    @SuppressWarnings("unchecked")
    public List getAllDogs() {
        EntityManager em = createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Query allDogsQuery = em.createQuery("select d from Dog d");

        List allDogs = allDogsQuery.getResultList();

        tx.commit();
        em.close();

        return allDogs;
    }

}

Two simple methods, one for saving dogs in the database, and one for retrieving all of them.


Let it run


Below, sample code that uses those DAO classes.

package me.m1key.dogs;

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

import me.m1key.dogs.dao.DogDAO;
import me.m1key.dogs.dao.GenericDAO;
import me.m1key.dogs.entities.Dog;

/**
 * Launches the simple JPA demonstration application.
 *
 * @author MichalH
 *
 */
public class Launcher {

    /**
     * @param args
     */
    public static void main(String[] args) {
        DogDAO dogDao = new DogDAO();

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

        Dog someOtherDog = new Dog();
        // http://www.fundognames.com/male-dog-names-p-9.html
        someOtherDog.setName("Assan");

        try {
            dogDao.persistDog(michalsDog);
        } catch (Exception e) {
            System.err.println("Exception while saving dog: " + michalsDog);
            System.err.println(e);
            e.printStackTrace();
        }

        try {
            dogDao.persistDog(someOtherDog);
        } catch (Exception e) {
            System.err.println("Exception while saving dog: " + someOtherDog);
            System.err.println(e);
            e.printStackTrace();
        }

        List<Dog> allDogs = dogDao.getAllDogs();
        for (Dog doggie : allDogs) {
            System.out.println(doggie);
        }

        GenericDAO.closeEntityManager();
    }

}

It's very very basic at this point. But what happens if you run it?

[1, Sega]
[2, Assan]
Alright! Something got saved in the database and was successfully retrieved. Note that each persisting operation and retrieval occur in separate transactions. If you don't like that, there are several solutions available:
  • Create business classes instead of DAO classes that handle persistence AND business logic
  • Use sf.getCurrentSession().beginTransaction() where sf is session factory - but that makes you use Hibernate directly, not just under the hood
  • Use JTA UserTransaction class
  • Use EJBs transaction propagation. (requires an application server)
  • Use Spring Transaction Management

And, if you refresh/clean the project now, you should see that the previous error (Table "DOGS" cannot be resolved) is now gone. You may have to do more Eclipse magic, like closing the database connection and opening it again etc.


Creating an entity from an existing database table


You can create entities from existing database tables, but it doesn't work so well, as I will show.

Imagine you already have an existing table in the database. Or, better, let's create one.

CREATE TABLE toys
(
TOY_ID BIGINT NOT NULL PRIMARY KEY,
TOY_NAME VARCHAR(128) NOT NULL,
DOG_ID BIGINT NOT NULL
)
That's simple SQL code (compatible with HSQLDB) creates a table called toys with a single column primary key and a toy name. There's also a column DOG_ID which we are going to make a foreign key.

ALTER TABLE toys
ADD FOREIGN KEY (DOG_ID)
REFERENCES DOGS (ID)
ON DELETE CASCADE

That's the foreign key. ON DELETE CASCADE means that if a dog is deleted - all its toys are deleted too. If a toy is deleted - the dog stays. Dogs don't share toys in our application.

Simply refreshing your database connection in Eclipse may not work (didn't work for me), so you may have to close it and open it again.

Now, right-click the entities package and select "New -> Entities From Tables":

New -> Entities From Tables

Here, select our new table:

Select tables to generate entities from

In the next window of the wizard it is possible to specify the associations. Please look at the following screenshot:

Select tables to generate entities from

It is smart enough to suggest the right columns:

Join columns

Now it's time to specify the kind of association. In our case, each dog has many toys.

Join columns

If you now click Finish, there's a surprise waiting for you. The list of associations remains empty.

No associations

I don't understand why. The class it generates doesn't even compile, and has no track of the relationship. Even when I created the relationship in JPA myself:

    @ManyToOne
    public Dog getDog() {
        return this.dog;
    }

... and let it create the table in the database, and then from this Hibernate-made table I repeated the procedure - I got the same disappointing result. What am I missing here?


Hand-written associations


I gave up generating entities using Eclipse JPA, I wrote them myself. Look at the previous code sample. This gives a one way association from a toy to a dog. A dog entity is unaware of its toys. You would need a separate DAO (ToysDAO) to even save toys.

We can change this by making the association bidirectional. In the Dog class:

// ...
    private List<Toy> toys = new ArrayList<Toy>();
    // ...
    @OneToMany(mappedBy = "dog", cascade = CascadeType.ALL)
    public List<Toy> getToys() {
        return toys;
    }

    public void setToys(List<Toy> toys) {
        this.toys = toys;
    }

CascadeType.ALL (javax.persistence.CascadeType) does the trick.

If you do not provide the mappedBy attribute, JPA will expect you to have another class just to "join" two tables together and you will get a validation error from Eclipse JPA:

Join table "DOGS_TOYS" cannot be resolved.

Cool.


Mapping a collection of Strings


Sometimes you want to map a collection of plain Objects and not entities. In our case, a dog can have some nicknames.

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

An additional table is created:
DOG_NICKNAMES

You need JPA 2.0 for this; prior to JPA 2.0 you'd have to use Hibernate explicitly:

org.hibernate.annotations.CollectionOfElements

But not anymore, now you can use @ElementCollection like shown above.

Download source code for this article

5 comments:

  1. hi,
    I am a newbie to jpa,hsqldb and hibernate .So I followed your tutorial step by step.I am able to start db and connect from eclipse.But when I created Entity Dog,I got an error
    Catalog "SA" cannot be resolved for table "Dog"

    I copied your properties to my src/META-INF/persistence.xml changing 'sa' to 'SA'. .Still the error is shown.
    Pls advise,
    jim
    ReplyDelete
  2. Thank you, your blog was great help and was able to create a sample application using eclipse link JPA and HSSQL in a short time.
    - Sai Vargheese.
    ReplyDelete
  3. Hi Michal,
    I have two tables with common columns (non primary key one).I created hibernate entity classes. Can I use JPA one to many annotation annotations to join these two entities. Can I use @JoinCoulmn with non FK. one more query is
    If I create a database view on these two tables and map this view to entity class. what are the advantages and disadvantages of this design.
    ReplyDelete
  4. Hi Srinivas,

    I am not sure, I don't want to mislead you - you should give it a try and write some test cases.

    Best of luck. :)
    ReplyDelete