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

18 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
  5. What does mean "We should let Hibernate create this table for us"?? I've wrote application like in this article. I have a connection and trying to create a new entry for my entity with EntityManager.persist() method. But I get error:
    "Internal Exception: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: SEQUENCE
    Error Code: -5501
    Call: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
    bind => [2 parameters bound]"

    I connect to my db and see that Hibernate did not create the table for my entity! I gues I should do something to "let Hibernate create this table for us".. I'm abit disappointed that this article lacks a general instrustion how to create the table for Entity.. I think that this article may be improved. I will wait for answer..

    ReplyDelete
    Replies
    1. Hi Bronto, Hibernate can create your SQL tables based on your entities. It will look at JPA (or Hibernate) annotations and create appropriate SQL code. This does it:

      [property name="hibernate.hbm2ddl.auto" value="create" /]

      (This is XML code that I 'escaped' using square brackets)

      So if you use the code as described in the article, you don't have to do anything, it already does it for you.

      As for the other error, I think the user you're connecting with lacks proper privileges. What database are you using?

      Delete
    2. I'm glad to get answer! Thanks!
      I use hsqldb. I made review of my project and compared with steps in article. I did not create connection to my bd in "Data source explorer". Now I added connection. but after adding connection I see strange error message in my Entity class. Here is error message: "Catalog "SA" cannot be resolved for table "contacts"". Contacts - is a name of table (@Table(name="contacts") in Entity class). And sa I guess is a default user in hsqldb. But may be it is another "sa".. Still need help.

      Delete
    3. I found this online, maybe it will help: http://www.eclipse.org/forums/index.php/t/269786/

      Delete
    4. Finally I made it work. There was incorrect platform "Eclipselink" when I created my project first time. There was no Hibernate support in my Eclipse. That is why I could not select "Hibernate" in platforms combo-box in jpa project configuration window. I've installed Hibernate plugin to my Eclipse. And created new project. Then I fixed "Catalog "x" cannot be resolved" error. Then I fixed "Table "x" cannot be resolved" error. Then I fixed some other problems. And finally my project start to work!!!

      So much problems with this example... Some of this problems were caused by inattention. Some of them appeared becouse of... I don't know why. May be becouse of karma?:) I think there are another programmers with such karma as my. And this article does not contain every possible error description.. But may it should not?

      Delete
    5. Hi, I'm glad you got it working. I guess exceptions and research are part of learning process. The goal of this blog post is to get you started and provide a basic to-do, rather than explain every single thing that might go wrong. Thanks to your comments and experience the article is now more complete, thanks.

      Delete
  6. Michal,

    Thanks for your article!!

    While following it with my own code, I just found a way to get JPA to generate the relations between tables. All you have to have, is the tables already created in the DB. Then right-click the entities package and select "New -> Entities From Tables". On the following screen select all the tables that have relationships among them and click next. Voila! On the "Table Associations" screen you'll find that the relations now appear mapped, and if you generate the code, all the annotations needed will be there, automatically generated.

    Cheers,
    José da Gama

    ReplyDelete
  7. How can I choose to create memory table or cache table? Can it be done in annotation or through configuration?

    ReplyDelete
  8. JPA for eclipse only seems to support up to hsqldb 1.8. When are we going to see support for the newer versions of HSQLDB?

    ReplyDelete
    Replies
    1. Hi, I haven't been looking into this and I simply don't know. But I don't recommend this GUI approach at all. :)
      Thanks for visiting.

      Delete
  9. Hi,

    Nice tutorial, although using Hibernate 4.3.7 I had to add hibernate-entitymanager-4.3.7.Final.jar as well. I noticed that I get a warning when running the code, saying "Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; use [org.hibernate.jpa.HibernatePersistenceProvider] instead.". I checked your code and there seem to be some differences between the necessary jars, you don't have an "hibernate-entitymanager" jar in your project. What am I missing?

    ReplyDelete