Sunday, July 5, 2009

Shopping Cart Web Application - Part 7 - Hibernate


Introduction
Hibernate is an object-relational mapping (ORM) library for the Java language, providing a framework for mapping an object-oriented domain model to a traditional relational database.
If you are new to Hibernate I would suggest reading the documentation on the Hibernate website as well as this tutorial.

What you need before we get started


What we will cover

  • Hibernate Overview
  • Java Persistence API
  • Shopping cart web application changes


Level
  • Beginner

Overview
Hibernate's primary feature is mapping from Java classes to database tables (and from Java data types to SQL data types). Hibernate also provides data query and retrieval facilities. Hibernate generates the SQL calls and relieves the developer from manual result set handling and object conversion, keeping the application portable to all supported SQL databases, with database portability delivered at very little performance overhead.

Java Persistence API
The Java Persistence API (JPA) provides an object/relational mapping facility to Java developers for managing relational data in Java applications. Java Persistence consists of three areas:
  • The Java Persistence API
  • The query language
  • Object/relational mapping metadata

JPA was introduced in JEE 5 and forms part of the JSR 220 EJB3.0 specification. The Java Persistence API draws upon the best ideas from persistence technologies such as Hibernate, TopLink, and JDO. Customers now no longer face the choice between incompatible non-standard persistence models for object/relational mapping.

Both sample applications use JPA annotations in the Entity classes, the difference between the two is in the data access objects. The Hibernate sample application makes use of Hibernate's session factory to read and write to the database whereas the JPA sample application makes use of JPA's EntityManager to read and write to the database.  

For further reading and information on JPA I recommend these links:

Shopping Cart Web Application


Database Schema
Below is a schema of the database I want my application to use:



The SQL statements for the creation of these tables for this database will look like:
create table basket (id bigint not null unique, primary key (id)) ENGINE=InnoDB;
create table basket_item (id bigint not null unique, price double precision not null, quantity integer not null, items_id bigint not null, basket_id bigint not null, primary key (id)) ENGINE=InnoDB;
create table item (id bigint not null unique, description varchar(255), name varchar(255) not null, price double precision not null, primary key (id)) ENGINE=InnoDB;
alter table basket_item add index FK3F6FA5ECF59CCC6 (basket_id), add constraint FK3F6FA5ECF59CCC6 foreign key (basket_id) references basket (id);
alter table basket_item add index FK3F6FA5EC98F3A8D9 (items_id), add constraint FK3F6FA5EC98F3A8D9 foreign key (items_id) references item (id);

The above basket table is pretty useless but I wanted an example which had a many-to-one association with items. The basket table could also include more columns such as username.

HSQLDB
HSQLDB (Hyperthreaded Structured Query Language Database) is a relational database management system written in Java. The reason I have chosen this database over MySQL or any other database is because I wanted an in-memory database so that it would be easier for readers who are unfamiliar with configuring databases to get my sample application up and running without much hassle. Using HSQLDB’s in-memory database all I had to do was include the HSQLDB library in my applications classpath. Hibernate will create the database for me and prepopulate our database with necessary data.

Hibernate configuration file vs Persistence configuration file
The Hibernate sample application requires a Hibernate configuration file whereas the JPA sample application requires a persistence configuration file. Both are very similar:

1. Hibernate configuration file
Hibernate uses a file (hibernate.cfg.xml) to configure hibernate properties such as connection details to the database, location of the entity classes used to create tables in the database and a whole range of other settings. This is a copy of the hibernate.cfg.xml we used in the Shopping Cart application:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="hibernate.connection.url">jdbc:hsqldb:shoppingcart</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="connection.password"></property>
        <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">create</property>
        <property name="current_session_context_class">thread</property>
        
        <mapping class="com.mydomain.shoppingcart.bo.Item" />
        <mapping class="com.mydomain.shoppingcart.bo.Basket" />
        <mapping class="com.mydomain.shoppingcart.bo.BasketItem" />
    </session-factory>
</hibernate-configuration>

2. Persistence configuration file
Much like the Hibernate configuration file the persistence configuration file is used to configure the JPA context.
<persistence 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"
 version="1.0">
 <persistence-unit name="shopping-cart" transaction-type="RESOURCE_LOCAL">
  <properties>
   <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
   <property name="hibernate.connection.url" value="jdbc:hsqldb:shoppingcart" />
   <property name="hibernate.connection.username" value="sa" />
   <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
   <property name="hibernate.archive.autodetection" value="class" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.format_sql" value="true" />
  </properties>
 </persistence-unit>
</persistence>


Annotations
Many APIs require a fair amount of boilerplate code (any code that is or can be reused in new contexts or applications without being changed much from the original). For example, in order to write a JAX-RPC web service, you must provide a paired interface and implementation. This boilerplate could be generated automatically by a tool if the program were “decorated” with annotations indicating which methods were remotely accessible.

Annotations are like meta-tags that you can add to your code and apply them to package declarations, type declarations, constructors, methods, fields, parameters, and variables. As a result, you will have helpful ways to indicate whether your methods are dependent on other methods, whether they are incomplete, whether your classes have references to other classes, and so on.

Annotation-based development relieves Java developers from the pain of cumbersome configuration. Annotation-based development lets us avoid writing boilerplate code under many circumstances by enabling tools to generate it from annotations in the source code. This leads to a declarative programming style where the programmer says what should be done and tools emit the code to do it.

Mapping with EJB3/JPA Annotations
Every bound persistent POJO class is an entity bean and is declared using the @Entity annotation (at the class level). Below is an extract of the code of what our Item class now looks like having annotations added to it:

Item.java
@Entity
@Table(name="item")
public class Item implements Serializable  {
 private static final long serialVersionUID = 1135428828106917172L;
 private String description;
 private Long id;
 private String name;
 private Double price;
 private List<BasketItem> basketItems = new ArrayList<BasketItem>(0);
 
 public Item() {
 }

 public Item(Long id, String name, double price) {
  this.id = id;
  this.name = name;
  this.price = price;
 }

 public Item(Long id, String description, String name, double price) {
  this.id = id;
  this.description = description;
  this.name = name;
  this.price = price;
 }
 
 @Column(name="description")
 public String getDescription() {
  return description;
 }

 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 public Long getId() {
  return id;
 }

 @Column(name="name", nullable=false)
 public String getName() {
  return name;
 }

 @Column(name="price", nullable=false, precision=22, scale=0)
 public Double getPrice() {
  return price;
 }

 public void setDescription(String description) {
  this.description = description;
 }

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

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

 public void setPrice(Double price) {
  this.price = price;
 }
 
 @OneToMany(mappedBy="item", cascade=CascadeType.ALL)
 public List<BasketItem> getBasketItems() {
  return this.basketItems;
 }

 public void setBasketItems(List<BasketItem> basketItems) {
  this.basketItems = basketItems;
 }
}


  • @Entity declares the class as an entity bean (i.e. a persistent POJO class). @Id declares the identifier property of this entity bean. The other mapping declarations are implicit.
  • @Table is set at the class level; it allows you to define the table, catalog, and schema names for your entity bean mapping. If no @Table is defined the default values are used: the unqualified class name of the entity.


Hibernate Annotation Extensions
Hibernate 3.1 offers a variety of additional annotations that you can mix/match with your EJB 3 (JPA) entities. They have been designed as a natural extension of JPA annotations. I am using a Hibernate annotation in the Basket class to handle the removal of orphans as the current version of JPA doesn't have an annotation that handles this.

Basket.java
@OneToMany(mappedBy="basket", cascade=CascadeType.ALL)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
public List<BasketItem> getBasketItems() {
 return basketItems;
}

To empower the EJB3 capabilities, hibernate provides specific annotations that match hibernate features. The org.hibernate.annotations package contains all these annotations extensions. Please refer to the hibernate documentation for more information.

Serializable
One of the other important things to note was that our entity classes implement the interface Serializable. Strictly speaking, this is not a requirement. However, in practice you will normally want your Hibernate objects to be serializable so that they can be (potentially) migrated around a multiprocessor cluster or saved and restored across a web server reboot etc.

Session Factory vs EntityManager
The Session factory is used by the Hibernate sample application to transact with the database whereas the EntityManager is used by the JPA sample application.

1. SessionFactory
The session factory creates Sessions. Usually an application has a single SessionFactory. Threads servicing client requests obtain Sessions from the factory. The Session interface is the main runtime interface between a Java application and Hibernate. The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes.

2. EntityManager
An EntityManager instance is associated with a persistence context. The EntityManager is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.


Hibernate utility class vs JPA utility class
I created a utility class that will be used by any class within the Shopping Cart application that requires a hibernate session / EntityManager.

1. Hibernate utility
The class is called HibernateUtil.java and can be found in the com.mydomain.shoppingcart.util package.

2. JPA utility
The class is called JPAUtil.java and can be found in the com.mydomain.shoppingcart.util package.


Data Access Object (DAO's)
A Data Access Object (DAO) is an object that provides an abstract interface to some type of database or persistence mechanism, providing some specific operations without exposing details of the database. The DAO pattern allows data access mechanisms to change independently of the code that uses the data. Compare the DAO's in the Hibernate sample application to the JPA sample application. You will see where the Hibernate session is being used and where JPA's EntityManager is being used. I created two DAO's, one for each of our Entities:
  • ItemDaoImpl – implements ItemDao interface
  • BasketDaoImpl – implements BasketDao interface

Lastly I tied the rest of the pieces together so that our application was using our DAO's now and not just getting back stubbed data.

12 comments:

Unknown said...

hi there

i did the Hibernate session sample application, so i decided to migrate it to MySQL,but im struggling whit the build file.Can u please help me with the build file.

tx

Unknown said...

sure -- what is the problem?

Anonymous said...

Hello Ross,

I was searching for a shopping cart and spring tutorial and came accross this tutorial. It's one of the best. Very simple and great for beginners. I am trying to get the ShoppingCartHibernateJPAPart7 working. I created a database first of all in netbeans with derby database. I generated a persistence unit inside netbeans.




oracle.toplink.essentials.PersistenceProvider
jdbc_/shopping-cart2
false







I don't have the database you've recommented. I also tried using MS SQL server. But still getting the following error-

[#|2010-01-03T18:52:56.606+0000|WARNING|sun-appserver2.1|javax.enterprise.system.stream.err|_ThreadID=22;_ThreadName=httpSSLWorkerThread-8080-0;_RequestID=53df71ea-b311-4a19-8b0d-16ddca64b5c5;|
java.lang.ClassCastException: com.sun.enterprise.naming.SerialContext cannot be cast to javax.sql.DataSource
at oracle.toplink.essentials.jndi.JNDIConnector.connect(JNDIConnector.java:129)
at oracle.toplink.essentials.sessions.DatasourceLogin.connectToDatasource(DatasourceLogin.java:184)
at oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.loginAndDetectDatasource(DatabaseSessionImpl.java:582)
at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:280)
at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:230)
at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerFactoryImpl.getServerSession(EntityManagerFactoryImpl.java:93)
at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:138)
at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:132)
at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:91)
at com.mydomain.shoppingcart.dao.impl.ItemDaoImpl.list(ItemDaoImpl.java:23)
at com.mydomain.shoppingcart.service.impl.ShoppingManager.findItems(ShoppingManager.java:40)
at com.mydomain.shoppingcart.view.ShoppingViewHelper.findItems(ShoppingViewHelper.java:37)
at com.mydomain.shoppingcart.view.Items.findItems(Items.java:37)
at com.mydomain.shoppingcart.view.Items.getItems(Items.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at


Any, ideas why?

Thanking you in advance.

eve

Unknown said...

Hello Eve,

I am glad you are finding the tutorial helpful.

I don't have an answer for your problem unfortunately. I haven't tried to deploy the application to Glassfish or use the TopLink implementation.

Your error occurs on a JNDI lookup for the datasource. Looks like on finding the resource tries to cast it to a datasource and falls over. Have you tried looking up the datasource resource programmatically? If so do you get the same error?

If I have some time today I will see if I can get something working.

Anonymous said...

Good morning Ross,

Thank you for your reply. I was able to deploy the other applications -

ShoppingCartTestCasesPart3
ShoppingCartWebDevPart5
ShoppingCartFaceletsPart6

to glassfish v3

I would really like to get this working so if you do have time to look into it, I would really apprecaite it. I wil also try and do some research. The application deploys. But I get the error when I try to run the application. Looking at the server log, the tables are not created dueing deployment. Usually, it does if there's @Entity annotation.

Thanks

eve

Unknown said...

Hello Eve,

I have been able to test on GlassFish. I didn't get the error you were getting though but I did pick up some 'BUGS' (arghh - LOL) that you would have eventually come across. I will update all the projects with the fixes later on today but if you would like me to send you the changes in the meantime please send me your email address.

This is what my persistence.xml file looks like:

<persistence-unit name="shopping-cart"
transaction-type="JTA">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/__shoppingcart</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.ddl-generation" value="none" />
<property name="toplink.ddl-generation.output-mode" value="database" />
<property name="toplink.target-database" value="Derby" />
<property name="toplink.platform.class.name"
value="oracle.toplink.essentials.platform.database.DerbyPlatform" />
<property name="toplink.logging.level" value="FINE" />
</properties>
</persistence-unit>

I created the database manually because I was getting errors when TopLink was trying to drop and create tables (Just wasn't doing it). Here's a script to create the database:

create table basket (id bigint primary key, userid varchar(255) not null);
create table basket_item (id bigint primary key, price double precision not null, quantity integer not null, items_id bigint not null, basket_id bigint not null);
create table item (id bigint primary key, description varchar(255), name varchar(255) not null, price double precision not null);
alter table basket_item add constraint FK3F6FA5ECF59CCC6 foreign key (basket_id) references basket (id);
alter table basket_item add constraint FK3F6FA5EC98F3A8D9 foreign key (items_id) references item (id);

insert into item (id,description,name,price) values (2,'Jelly icecream waffle cream','Jelly Beans',18.99);
insert into item (id,description,name,price) values (3,'Jam Doughnut','Strawberry jam and Christmas pudding',23.00);
insert into item (id,description,name,price) values (4,'Mallow Madness','Marshmellow wrap',8.50);
insert into item (id,description,name,price) values (5,'Chocolate Cheese','Crunchy chocolate creamy cheese',17.50);
insert into item (id,description,name,price) values (6,'Custard Crazy','Custard sauce with jelly and cream',13.55);

Anonymous said...

Ross,

You are a star. My email address is hotmail. I was not sure which of the identity to use.
gorgeous65@msn.com

I look forward to your mail.

eve

Anonymous said...

Ross,

In my libs, I had more than one slf4j-api type of jar. I removed one and it seems to be working. After deployment, I am able to view the tables has been created with MS SQL server 2000. I only get the following error when I try running the application. I look forward to your mail.

thanks

eve

Anonymous said...

Ross,

Sorry, the following error-

[#|2010-01-04T11:22:01.898+0000|INFO|sun-appserver2.1|org.apache.catalina.loader.WebappClassLoader|_ThreadID=23;_ThreadName=httpSSLWorkerThread-8080-0;|PWC1635: Illegal access: this web application instance has been stopped already (the eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact)|#]

[#|2010-01-04T11:22:01.898+0000|SEVERE|sun-appserver2.1|javax.enterprise.system.container.web|_ThreadID=23;_ThreadName=httpSSLWorkerThread-8080-0;_RequestID=733d5b37-296c-4421-8bce-26fc019e7ff3;|StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.ThreadDeath
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1411)
at java.beans.Introspector.instantiate(Introspector.java:1438)
at java.beans.Introspector.findExplicitBeanInfo(Introspector.java:410)
at java.beans.Introspector.(Introspector.java:359)
at java.beans.Introspector.getBeanInfo(Introspector.java:159)
at javax.el.BeanELResolver$BeanProperties.(BeanELResolver.java:153)
at javax.el.BeanELResolver.getBeanProperty(BeanELResolver.java:574)
at javax.el.BeanELResolver.getValue(BeanELResolver.java:281)
at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:175)
at com.sun.faces.el.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:64)
at com.sun.el.parser.AstValue.getValue(AstValue.java:138)
at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:206)
at com.sun.facelets.el.TagValueExpression.getValue(TagValueExpression.java:71)
at javax.faces.component.UIOutput.getValue(UIOutput.java:173)
at com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getValue(HtmlBasicInputRenderer.java:189)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.getCurrentValue(HtmlBasicRenderer.java:320)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd(HtmlBasicRenderer.java:200)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:836)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive(HtmlBasicRenderer.java:279)
at com.sun.faces.renderkit.html_basic.TableRenderer.encodeChildren(TableRenderer.java:307)
at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:812)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:886)
at javax.faces.render.Renderer.encodeChildren(Renderer.java:137)

Thanks

eve

Anonymous said...

Ross,

Guess what? I restarted the server and everything works.

Thank you.

eve

Unknown said...

that's great. You did all work so you should be thanking yourself.

I am sending you an email with a version of the app that includes fixes to the bugs I mentioned.

Anonymous said...

hey ross

i would like to ask about the scjwcd would you recommend someone to study and write
study guide (exam 310-082 & 310-083)
in the year 2010
not the exam(310-083 and 084)
from cyber @tsc
thankx