Hibernate

By Sandeep Desai (http://www.thedesai.net)

 

Books:

Steps to run Auction demo against Oracle 10.2 Database on Windows XP

1)      Download and install Oracle 10.2 database

2)      Unlock scott user account, set password to tiger

3)      Grant dba to scott as Hibernate sample will create table and constraints

4)      Install JDK 1.4 and the JDK bin directory to your path

5)      Download Hibernate 2.1 and unzip to c:\hibernate

6)      Download Apache Ant and unzip to c:\ant

7)      Edit c:\hibernate\hibernate-2.1\etc\hibernate.properties, uncomment out the Oracle section, set user to scott, password to tiger, set JDBC URL

8)      Copy <ORACLE_HOME>/jdbc/lib/ojdbc14.jar to c:\hibernate\hibernate-2.1\lib

9)      Edit c:\hibernate\hibernate-2.1\build.bat to point to ojdbc14.jar

10)  Run c:\hibernate\hibernate-2.1\build eg this will create the table and then run the application

11)  Java source code is in the eg directory

 

Sample Application with Code (requires Hibernate 3 and Spring 1.2.6)

ORM Mismatch

ORM Products

 

EJB Problems

What should ORM have

Types of ORM

ORM not suitable for

Hibernate

Hibernate Architecture and API

 

Sample Hibernate Bootstrap code

 

Configuration cfg = new Configuration();

cfg.configure();

cfg.addresource(“foo/order.hbm.xml”);

cfg.setProperties(System.getProperties());

SessionFactory sessionFactory = cfg.buildSessionFactory();

sessionFactory.addClass(org.foo.Customer.class);

sessionFactory.addClass(org.foo.Supplier.class);

 

Sample hibernate.properties to work with Oracle 10.2 

(see <HIBERNATE>etc/hibernate.properties for all options)

hibernate.show_sql true

 

hibernate.dialect net.sf.hibernate.dialect.Oracle9Dialect

hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver

hibernate.connection.username scott

hibernate.connection.password tiger

hibernate.connection.url jdbc:oracle:thin:@localhost:1521:orcl

#hibernate.default_schema scott

 

See etc/log4j.properties for Logging

JMX support will automatically create JNDI SessionFactory

Tomcat has read only JNDI so cannot register SessionFactory, use Sun’s fscontext.jar

Domain Model

 

Many to many Associations example

public class Category {

  private Set items = new HashSet();

  public Set getItems() { return items; }

  public void setItems(Set items) { this.items = items; }

}

 

public class Item {

  Set categories = new HashSet();

  public void addCategory(Category category) {

    category.getItems().add(this)
    categories.add(category);

  }

}

 

 

 

Hibernate Mapping XML file (Can also use XDoclet for setting attributes in Java code)

<?xml version="1.0"?>

     <!-- DTD same for 2.0 and 2.1 -->

<!DOCTYPE hibernate-mapping PUBLIC

     "-//Hibernate/Hibernate Mapping DTD 2.0//EN"

     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

          <!-- default-schema=”scott”

              package-name=”org.foo”

           -->

<hibernate-mapping package="org.hibernate.auction">

     <import class="AuctionInfo"/>

     <class name="AuctionItem" table=”auction”> <!-- table optional -->

          <id name="id"> <-- primary key -->

              <generator class="native"/>

          </id>

               <!-- optional column and type -->

          <property name="description" column=”description” type=”string”/>

          <property name="ends"/>

          <property name="condition"/>

          <!--directly access instance variable no get and set -->

     <property name=”phone” access=”field”/>

     <property name=”fax” insert=”false” update=”false”/> <!—immutable -->

     <property name=”profit” formula=”sales-costs”/>

          <property name=”avgSales” formula=”(select avg(sales) from sales where year=2004)”/>

        <!-- can reuse Name class -->

           <component name="father_name" class=”Name”>

               <property name="firstName" column=”father_firstname”/>

               <property name="lastName" column=”father_lastname/>

           </component>

           <component name="mother_name” class=”Name”>

               <property name="firstName" column=”mother_firstname”/>

               <property name="lastName" column=”mother_lastname/>

           </component>

          <many-to-one name="seller" not-null="true"/>

          <many-to-one name="successfulBid" outer-join="false"/>

          <bag name="bids" lazy="true" inverse="true" cascade="all">

              <key column="item"/>

              <one-to-many class="Bid"/>

          </bag>

     </class> 

</hibernate-mapping>

 

 

class properties

bean properties

 

Implement NamingStrategy interface to automatically put prefix or suffix on table and column names

 

Manipulate Metadata at runtime

PersistentClass mapping = cfg.getClassMapping(Customer.class)

Column column = new Column();

column.setName(“phone”); ....

mapping.getTable().addColumn(column);...

SimpleValue ....

mapping.addProperty(..)

 

Hibernate Object Identity

 

Fine grained object models

 

Mapping class inheritance

Three approaches to class hierarchy as per Scott Ambler paper

 

Table per class hierarchy mapping example

 

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

     "-//Hibernate/Hibernate Mapping DTD 2.0//EN"

     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="org.hibernate.auction">

     <class name="Bid" discriminator-value="N">

          <id name="id">

              <generator class="native"/>

          </id>

          <discriminator type="char" column="isBuyNow"/>

          <property name="amount" not-null="true"/>

          <property name="datetime" not-null="true"/>

          <many-to-one name="bidder" not-null="true"/>

          <many-to-one name="item" not-null="true"/>

          <subclass name="BuyNow"

              discriminator-value="Y"/>

     </class> 

</hibernate-mapping>

 

Associations

 

Persistent Objects Lifecycle

 

 

Object states

 

Hibernate has transaction scoped identity, so Hibernate will have one o

 

Session session1 = sessionFactory.openSession();

Transaction tx1 = session1.beginTransaction();

Object a = session1.load(Person.class, new Long(1234));

Object b = session1.load(Person.class, new Long(1234));

boolean eq1 = (a == b); // true

tx1.commit();

session1.close();

Session session2 = sessionFactory.openSession();

Transaction tx2 = session1.beginTransaction();

Object c = session1.load(Person.class, new Long(1234));

boolean eq1 = (c == a); // false

 

Hibernate supports selective reassociation of detached instances i.e. application can reattach a subgraph of a graph of detach objects with the second session. Hibernate can detect new transient instances and will create new rows for them

 

Implementing equals

 

Persistence Manager

 

Making object persistent

 

Person p = new Person();

p.setName(“foo”);

p.setPhone(123456);

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

session.save(p); // Insert SQL not done here

tx.commit();  // Insert SQL done here

session.close();

 

Detached object handling, call update() or lock()

if select-before-update is set in class mapping hibernate will do a select and then check if object is dirty

p.setName(“foo2”);

Session session2 = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

session2.update(p); // reassociate detached object with session will cause SQL UPDATE

p.setPhone(789123);

tx.commit();  //

session2.close()

 

 

p.setName(“foo3”); // change lost

session2.lock(p, LockMode.NONE); // only changes after lock will be updated

 

Retrieving, updating and deleting persistent object

Person pt; // transient

Transaction tx = session.beginTransaction();

Person p = (Person) session.get(Person.class, new Long(1234)); //Retrieve

Person p2 = (Person) session.get(Person.class, new Long(4321)); //Retrieve

p.setPhone(789123); // update

session.delete(p2); // delete persistent

session.delete(pt); // delete transient

tx.commit();

session.close();

 

 

Cascading persistence with Hibernate

Calling save or delete on each object of the object graph is not efficient, Transitive Persistence provide better way to control lifecycle

Implements Persistence by reachability

cascade options in Entity Associations

 

Transient and detached instances

Unsaved transient instance if

 

Note: <id name=”id” unsaved-value=”0”> if we use java primitive long as a primitive identifier, if we use Long object then we don’t need this

 

Retrieving objects

 

Tuning object retrieval

 

Transactions

Transactions should pass ACID test

·        Atomic:  Works or fails, no in between state

·        Consistent: Data should stay consistent

·        Isolated: Transactions are protected from each other

·        Durable: Changes are permanent on commit, even if server goes down

 

Hibernate turns off JDBC auto commit mode

Don’t use Hibernate JDBC or JTA Transaction API use Transaction interface

 

Transaction API usage example

Session session = sessionFactory.openSession();

Transaction tx = null;

try {

   tx = session.beginTransaction();

   updatePayroll();

   tx.commit();

} catch (Exception e) {

   if (tx ! = null) try  { tx.rollback(); } catch(HibernateException he) {}

} finally { try { session.close(); } catch(HibernateException he) {}

}

 

Flushing a session

Hibernate implements transparent write behind changes to the domain model are not immediately propagated to the database e.g. calls to set property or save() will not fire SQL query

session.setFlushMode() (rare to use this)

Transaction Isolation Level

Databases implement this using locking or multiversion concurrency control (implemented by Oracle and Postgre SQL, is more scalable)

 

ANSI SQL Isolation levels are based on what is permissible in terms of the phenomenon below

JTA Isolation levels (global setting using hibernate.connection.isolation, possible values 1,2,4,8)

 

Choosing an isolation level (163) // TODO

 

Locking

 

Pessimistic Locking

Use lock to prevent other transaction from reading data, lock can be for read or duration of transaction. In read-commited mode, the database never acquires pessimistic locks unless explicitly requested by the application. Can be use to prevent deadlocks. Oracle and Postgre SQL support SELECT ... FOR UPDATE to support explicit pessimistic locks. Locking is expensive and cause problems while scaling applications. Caching more useful than locking. Let  DBA decide which transactions require pessimistic locking

Lock Modes (can be used to bypass cache or execute simple version check)

 

Lock example

Person p = ...;

Transaction tx = session.beginTransaction();

Person p2 = (Person) session.get(Person.class, 34, LockMode.UPGRADE);

session.lock(p, LockMode.READ);

tx.commit();

 

Application transactions (long transaction, users transaction, business transaction)

Long UI based work e.g. shopping cart, user may spend a long time on this transaction

  1. Data is retrieved and displayed on the screen in a first database transaction
  2. User has views and modifies data, outside of any database transaction
  3. Modifications are made persistent in a second database transaction

 

e.g. In auction website two users try to add comments, we can handle this in three ways

 

Managed Versioning (optimistic locking)

Add version property to class using int counter (better) or timestamp (less safe)

 

Granularity of Session

Caching

 

Caching provides minimal gains in most cases

Persistence layers provide multiple levels of caching. For example a cache miss at the transacation scope maybe followed up by a process scope. A database lookup is the last resort.

Reference data e.g. list of states that rarely changes is a good candidate for cache

 

Cache Levels

·        Session is the first-level cache and is always on

o       returns same object instance for multiple calls to fetch object for a row

o       call session.vict() to remove the object and its collections from the first-level cache

o       call session.clear() to remove all objects from memory useful when getting OutOfMemoryException

·        Second level cache is pluggable, is useful for read only data only,

o       EHCache: simple process scope cache in single JVM (does not support transactional)

o       OpenSymphony OSCache: caching for single JVM (does not support transactional)

o       SwarmCache: cluster cache based on JGroups (does not support read-write and transactional)

o       JBossCache: fully transactional replicated clustered cache based on the JGroups mutlicast library (supports read-only and transactional)

 

Four built in concurrency strategies representing decreasing level of strictness. Slower to faster.

·        transactional: available in managed environment only, guarantees full transactional isolation up to repeatable read

·        read-write: maintains read committed isolation, using a timestamping mechanism. Available in non-clustered environments

·        nonstrict-read-write: No guarantee of consistency between cache and database. Set short timeout

·        read-only: suitable for data that rarely changes (reference data)

 

Hibernate Cache settings

·        hibernate.cache.provider_class=net.sf.ehcache.hibernate.Provider

·        hibernate.cache.region_prefix

·        <class ...> <cache usage=”read-write”/> </class>

·        sessionFactory.evict(Person.class, new Long(1234)) to clear cache

·        sessionFactory.evict(“org.foo.Person”) to clear all elements of a cache

 

Hibernate supports following pluggable Cache Provider

 

Hibernate Advanced Mapping

Entity and Value Types: Value Types (e.g. Address) may have associations but navigation is one way only. Value type does not have persistent identity; so persistent lifecycle does not apply. The lifecycle is determined by owning entity. Hibernate can hide the link table for many to many associations. It is recommend that

 

Hibernate built-in mapping types

Mapping type

Java type

Standard JDBC SQL built-in type

integer

int or java.lang.Integer

INTEGER

long

long or java.lang.Long

BIGINT

short

short or java.long.Short

SMALLINT

float

float or java.lang.Float

FLOAT

double

double or java.lang.Double

DOUBLE

big_decimal

java.math.BigDecimal

NUMERIC

character

java.lang.String

CHAR(1)

string

java.lang.String

VARCHAR

byte

byte or java.lang.Byte

TINYINT

boolean

boolean or java.lang.Boolean

BIT

yes_no

boolean or java.lang.Boolean

CHAR(1) (‘Y’ or ‘N’)

true_false

boolean or java.lang.Boolean

CHAR(1) (‘T’ or ‘F’)

 

 

 

date

java.util.Date or java.sql.Date

DATE

time

java.uti.Date or java.sql.Time

TIME

timestamp

java.util.Date or java.sql.Timestamp

TIMESTAMP

calendar

java.util.Calendar

TIMESTAMP

calendar_date

java.util.Calendar

DATE

 

 

 

binary

byte[]

VARBINARY (or BLOB)

text

java.lang.String

CLOB

serializable

any java class implementing java.io.Serializable

VARBINARY or (BLOB)

clob

java.sql.Clob

CLOB

blob

java.sql.Blob

BLOB

 

 

 

class

java.lang.Class

VARCHAR

locale

java.util.Locale

VARCHAR

tiemzone

java.util.TimeZone

VARCHAR

currency

java.util.Currency

VARCHAR

 

 

 

JDBC provides abstraction of database vendor SQL data types

Don’t mix date types, choose one type for application and stick to it.

Blob types cannot be detached objects

 

Custom Mapping objects

Implement CompositeUserType to use custom types in Hibernate queries

// Domain object, immutable implementation

public class Phone implements Serializable {

public Phone(..) {}

public ... getAreaCode() {}

public boolean equals() {}

public int hashCode() {}

}

 

public class PhoneUserType implements net.sf.hibernate.UserType {

public int sqlTypes() { return new int[] { Types.NUMERIC }; }

public Class returnedClass() { return Phone.class; }

public equals(Object x, Object y) {..} // used for dirty checking by Hibernate

public Object deepCopy (Object value) { return value; } // do copy for mutable type

public boolean isMutable() { return false; } // helps Hibernate do some optimizations

public Object nullSafeGet (

     ResultSet resutSet,

     String[] name,

     Object owner)

     throws HibernateException, SQLException {

     if (resultSet.wasNull) return null;

     return new Phone(names[0]);

}

 

public void nullSafeSet (

     PreparedStatement statement,

     Object value,

     int index)

     throws HibernateException, SQLException {

     if (value == nul) statement.setNull(index, Types.NUMERIC);

     else {

        Phone phone = (Phone) value;

         ....

        statement.setBigDecimal(index, phone.getValue());

     }

}

 

// Implement CompositeUserType to use custom types in Hibernate queries

public class PhoneCompositeUserType implements net.sf.hibernate.CompositeUserType {

public int sqlTypes() { return Types.NUMERIC; }

public Class returnedClass() { return Phone.class; }

public equals(Object x, Object y) {..} // used for dirty checking by Hibernate

public Object deepCopy (Object value) { return value; } // do copy for mutable type

public boolean isMutable() { return false; } // helps Hibernate do some optimizations}        

 

public Object nullSafeGet(

     ResultSet resutSet,

     String[] name,

     SessionImplementor session,

     Object owner)

     throws HibernateException, SQLException { ... }

 

     public Object nullSafeSet(

     PreparedStatement statement,

     Object value,

     int index,

     SessionImplementor session)

     throws HibernateException, SQLException { ..}

 

public String getPropertynames() { return new String[] { “areaCode”, “localCode” };

public Type[] getPropertyTyeps() { Hibernate.NUMERIC, Hibernate.NUMERIC  }

public Object getPropertyValue(Object component, int property)

     throws HibernateException {

    Phone phone = (Phone) component;

    if (property == 0) return phone.getAreaCode();

    if (property == 1) return phone.getLocalCode();

}

public Object setPropertyValue(Object component, int property, object value)

     throws HibernateException {

     throw new UnsupportedOperationException(“Immutable”);

}

 

public Object assemble(Serializable cached, SessionImplementero session, Object owner)

     throws HibernateException { return cached; }

public Serializable disassemble(Object value, SessionImplementor session)

     throws HibernateException { return (Serializable) value }       

 

}

 

<!-- mapping example -->

<property name=”homePhone” type=”org.foo.PhoneCompositeUserType”>

     <column name=”AREA_CODE”>

     <column name=”LOCAL_NUMBER”>

</property>

 

Enumerated Types

 

public class Rating implements Serializable {

     private String name;

     public static final Rating EXCELLENT = new Rating("Excellent");

     public static final Rating OK = new Rating("OK");

     public static final Rating LOW = new Rating("Low");

     private static final Map INSTANCES = new HashMap();

 

     static {

          INSTANCES.put(EXCELLENT.toString(), EXCELLENT);

          INSTANCES.put(OK.toString(), OK);

          INSTANCES.put(LOW.toString(), LOW);

     }

 

     // Prevent instantiation and subclassing with a private constructor.

     private Rating(String name) { this.name = name; }

 

     public String toString() { return name; }

     Object readResolve() { return getInstance(name);     }

     public static Rating getInstance(String name) { return (Rating) INSTANCES.get(name);    }

}

 

public class RatingUserType implements UserType {

 

     private static final int[] SQL_TYPES = {Types.VARCHAR};

 

     public int[] sqlTypes() { return SQL_TYPES; }

     public Class returnedClass() { return Rating.class; }

     public boolean equals(Object x, Object y) { return x == y; }

     public Object deepCopy(Object value) { return value; }

     public boolean isMutable() { return false; }

 

     public Object nullSafeGet(ResultSet resultSet,

                                    String[] names,

                                    Object owner)

              throws HibernateException, SQLException {

 

       String name = resultSet.getString(names[0]);

       return resultSet.wasNull() ? null : Rating.getInstance(name);

     }

 

     public void nullSafeSet(PreparedStatement statement,

                                  Object value,

                                  int index)

              throws HibernateException, SQLException {

 

          if (value == null) {

              statement.setNull(index, Types.VARCHAR);

          } else {

              statement.setString(index, value.toString());

          }

     }

}

 

// ******************* Usage ********************//

// incorrect usage

Query q = session.createQuery(“from Comment c where c.rating = Rating.LOW”);

// correct usage

Query q = session.createQuery(“from Comment c where c.rating = :rating”);

q.setParameter(“rating”, Rating.LOW, Hibernate.custom(RatingUserType.class));

 

 

Mapping collections of value types

 

Sets, bags, lists and maps

Sets: If we have two tables EMPLOYEE (EMPID, EMPNAME, DEPTID) and DEPARTMENT (DEPTID, DEPTNAME)

// Sets

class Department {

     private Set employees = new HashSet();

     public void getEmployees() { return employees; }

public void setEmployees(Set employees) { this. employees = employees; }

}

 

<set name=”employees” lazy=”true” table=”EMPLOYEE”>

     <key column=”deptid”/>

     <element type=”String” column=”EMPNAME” not-null=”true”/>

</set>

Bag: A bag is an unordered collection that allows duplicates, if we have two tables ITEM (ITEM_ID, NAME) and ITEM_IMAGE (ITEM_IMAGE_ID, ITEM_ID, FILENAME)

 

<idbag name=”images” lazy=”true” table=”ITEM_IMAGE”>

<collection-id  type=”long” column=”ITEM_IMAGE_ID”>

  <generator class=”sequence”/>

</collection-id>

<key column=”ITEM_ID”/>

<element type =”string” column=”FILENAME” not-null=”true”/>

</idbag>

 

Using a list, requires a index column in the table ITEM (ITEM_ID, NAME) and ITEM_IMAGE (ITEM_ID,POSITION (int),FILENAME)

<list name=”images” lazy=”true” table=”ITEM_USAGE”>

  <key column=”ITEM_ID”/>

  <index column=”POSITION”/>

  <element type=”string” column=”FILENAME” not-null=true/>

</list>

 

Using a map, ITEM (ITEM_ID, NAME) and ITEM_IMAGE (ITEM_ID,IMAGE_NAME,FILENAME)

<map name=”images” lazy=”true” table=”ITEM_IMAGE”>

     <key column=”ITEM_ID”/>

<index column=”IMAGE_NAME” type=”string”/>

<element type=”string” column=”FILENAME” not-null=”true”/>

</map>


Sorted and ordered collections

A sorted collection is sorted in memory using Java Comparator

An ordered is sorted by the database

<!-- sort attribute will cause Hibernated to use SortedMap -->

<!-- for custom sorting sort=”org.foo.comparator.MyCustomCamparator” -->

<map name=”images” lazy=”true” sort=”natural”>

    <key column=”ITEM_ID”/>

    <index column=”IMAGE_NAME” type=”string”/>

    <element type=”string” column=”FILENAME” not-null=”true”/>

</map>

 

Sorted set

<set name=”images” lazy=”true” table=”ITEM_IMAGE” sort=”natural”>

     <key column=”ITEM_ID”/>

<element type=”string” column=”FILENAME” not-null=”true”/>

</set>

Bags can’t be sorted

 

Ordered collections (both sets and bags accept a order-by)

 

<!-- order-by is SQL ORDER BY fragment -->

<map name=”images” lazy=”true” table=”ITEM_IMAGE” order-by=”LOWER(IMAGE_NAME) ASC”>

    <key column=”ITEM_ID”/>

    <index column=”IMAGE_NAME” type=”string”/>

    <element type=”string” column=”FILENAME” not-null=”true”/>

</map>

 

Collections of Components

The component class is a POJO and it does not have an identifier, bidirectional navigation is not possible (use parent/child relationship if bidirectional navigation required)

Sets: If we have two tables EMPLOYEE (EMPID, EMPNAME, DEPTID) and DEPARTMENT (DEPTID, DEPTNAME, DEPT_CITY, DEPT_STATE)

 

<!-- need not-null if we don’t have property primary key -->

<set name=”emps” lazy=”true” table=”EMPLOYEE” sort=”natural”>

     <key column=”DEPTID”/>

<composite-element class=”Department>

     <property name=”name” COLUMN=”DEPTNAME” not-null=”true”/>

     <property name=”city” COLUMN=”DEPT_CITY” not-null=”true”/>

     <property name=”state” COLUMN=”DEPT_STATE” not-null=”true”/>

</composite-element>

</set>

 

Mapping entity associations

Map one to one associations as many to one association using foreign key set unique=”true” in mapping

Table USER (USER_ID <<PK>>, BILLING_ADDRESS_ID <<FK>>, FIRSTNAME..)

Table ADDRESS (ADDRESS_ID <<PK>>, STREET, CITY, STATE)

<many-to-one name=”billingAddress” class=”Address” column=”BILLING_ADDRESS_ID” cascade=”all” unique=”true”/>

 

To make the one to one relationship navigable add following for Address Mapping

<one-to-one name=”user” class=”User” property-ref=”billingAddress”/>

 

Avoid defining more than one to one mapping e.g. Customer to Home Address and Customer to Billing Address

 

One to one Mapping using primary key Association

In the tables below the USER_ID and ADDRESS_ID share same key

Table USER (USER_ID <<PK>>, , FIRSTNAME..)

Table ADDRESS (ADDRESS_ID <<PK>> <<FK>>, STREET, CITY, STATE)

<class name=”User” ...>

   <one-to-one name=”address” class=”Address” cascade=”save-update”/>

</class>

 

<class name=”Address” ...>

  <id name=”id” column=”ADDRESS_ID”>

     <generator class=”foreign”>

    <param name=”property”>user</param>

</generator>

  </id>

  <one-to-one name=”address” class=”Address” constrained=”true”/>

</class>

 

 

Many to Many Associations

 

CATEGORY_ITEM (CATEGORY_ID <<PK>> <<FK>>, ITEM_ID <<PK>> <<FK>>)

// Parent child Hiearchy

CATEGORY (CATEGORY_ID <<PK>> PARENT_CATEGORY_ID <<FK>>, NAME, CREATED)

ITEM (ITEM_ID <<pk>> NAME, DESCRIPTION, INITIAL_PRICE)

 

Unidirectional many-to-many

 

<set name=”items” table=”CATEGORY_ITEM” lazy=”true” cascade=”save-update”>

     <key column=”CATEGORY_ID”>

     <many-to-many class=”Item” column=”ITEM_ID”/>

</set>

 

Category cat = (Category) session.get(Category.class, categoryId);

Item item = (Item) session.get(Item.class, itemId);

cat.getItems().add(item);

 

Polymorphic Associations

 

Domain Model

User  -----> BillingDetails (owner,number,created)

CreditCard extends BillingDetails (type, expMonth, expYear)

BankAccount extend BillingDetails (bankName)

 

 

 

BillingDetails Mapping has <joined-subclass> for CreditCard and BankAccount

 

public abstract class BillingDetails {}

 

<many-to-one name=”billingDetails” class=”BillingDetails” column=”BILLING_DETAILS_ID” cascade=”save-update”/>

 

CreditCard cc = new CreditCard();

cc.setNumber(ccNumber);

cc.setType(ccType);

cc.setExpiryDate(ccExpiryDate);

 

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

User user = (User) session.get(User.class, uid);

user.setBillingDetails(cc);

tx.commit();

session.close();

 

 

Session session2 = sessionFactory.openSession();

Transaction tx2 = session.beginTransaction();

User user = (User) session.get(User.class, uid);

user.getBillingDetails().pay(paymentAmount);

tx2.commit();

session2.close();

 

Note if association has lazy set to true then typecast and instanceof check will behave strangely because of proxy, in this case do session.load().

User user = (User) session.get(User.class, uid);

BillingDetails bd = user.getBillingDetails();

// will fail even if table has CreditCard Association because of proxy created due to lazy=true

if (bd instanceof CreditCard) System.out.println(“false”); // will print false

BillingDetails bd2 = (BillingDetails) session.load(CreditCard.class, bd.getId());

if (bd2 instanceof CreditCard) System.out.println(“true”); // will print true

 

// Recommended way to get subclassed object polymorphically using Eager Fetching

Criteria criteria = session.createCriteria(User.class);

criteria.add(Expression.eq(“id”,uid);

criteria.setFetchMode(“billingDetails”, FetchMode.EAGER);

user = criteria.uniqueResult();

CreditCard cc = (CreditCard) user.getBillingDetails();

ed = cc.getExpiryDate();

Polymorphic Collections

 

<class name=”BillingDetails”>

<many-to-one name=”user” class=”User” column=”USER_ID”/>

<class>

 

<class name=”User” ..>

<set name=”billingDetails” lazy=”true” cascade=”save-update” inverse=”true”>

     <key column=”USER_ID”/>

     <one-to-many class=”BillingDetails”/>

</set>

</class>

 

CreditCard cc = new CreditCard();

cc.setNumber(ccNumber);

cc.setType(ccType);

cc.setExpiryDate(ccExpiryDate);

 

User user = (User) session.get(User.class, uid);

user.addBillingDetails(cc);

tx.commit();

 

public void addBillingDetails(Billingdetails bd) {

     getBillingDetails().add(cc);

     cc.setUser(this);

}

 

User user = (User) session.get(User.class, uid);

for (Iterator iter = user.getBillingDetails.iterator(); iter.hasNext();) {

     BillingDetails bd = (BillingDetails) iter.next();

     bd.pay(ccPaymentAmount);

}

 

Polymorphic associations and table-per-concrete-class

 

Tables

USER (USER_ID, BILLING_DETAILS_ID <<Any>> BILLING_DETAIL_TYPE <<Discriminator>>, FIRSTNAME)

CREDIT_CARD (CREDIT_CARD_ID, OWNER)

BANK_ACCOUNT (BANK_ACCOUNT_ID <<PK>>, OWNER)

 

<any name=”billingDetails” meta-type=”string” id-type=”long” cascade=”save-update”>

              <!-- value can be anything e.g. CC and BA -->

     <meta-value value=“CREDIT_CARD” class=”CreditCard”/>

     <meta-value value=”BANK_ACCOUNT” class=”BankAccount”/>

       <!-- order is important first type then id -->

     <column name=”BILLING_DETAILS_TYPE”/>

     <column name=”BILLING_DETAILS_ID”/>

</any>

 

 

 

 

Queries (Retrieving objects efficiently)

Queries

Executing Queries

Both createQuery and createSQLQuery return Query object

 

Pagination

Query query = session.createQuery(“from Person p where p.state = ‘MA’”)

query.setFirstResult(40);

query.setMaxResults(20);

List results = query.list();

 

Resultset that returns only one row

Query query = session.createQuery(“from Person p where p.title = ‘CEO’”)

query.setMaxResults(1);

// will throw exception if more than one row

Person p = (Person) query.uniqueResult();

 

iterate() only fetches primary key, object fetched from cache

Query query = session.createQuery(“from Person p where p.state = ‘MA’”)

query.setFirstResult(40;

query.setMaxResults(20);

List results = query.iterate();

 

Binding Parameters (are null safe)

Query query = session.createQuery(“from Person p where p.state = :stateName”);

query.setString(“stateName”, “MA”);

query.setString(“stateName”, null); // can pass null

query.setParameter(“stateName”, Hibernate.STRING); // same as above line

List result = query.list();

 

Binding position Parameters (Avoid this style of code, as it makes code fragile)

Query query = session.createQuery(“from Person p where p.state = ? and p.title = ?”);

query.setString(0, “MA”);

query.setString(1, “Programmer”);

List result = query.list();

 

Binding arbitrary arguments

Query query = session.createQuery(“from Person p where p.manager = :manager”);

query.setEntity(“manager”, manager); // can pass null also

List result = query.list();

 

Named queries (put query in XML file)

<!-- in person.hbm.xml file -->

<query name=”findPersonByState”><![CDATA[

   from Person p where p.state = :stateName

]]></query>

 

Query query = session.getNamedQuery(“findPersonByState”);

query.setString(“stateName”, “MA”);

List result = query.list();

 

Name SQL queries

<sql-query-name=”findItemsByDescription”><![CDATA[

   select {i.*} from ITEM {i} where DESCRIPTION like :description

]]>

  <return-alias=”i” class=”Item”/>

</sql-query>

 

Note: To test HQL use Hibern8IDE (Java Swing Application available as part of the Hibernate Extension Package) or Hibernator Eclipse Plugin

 

Basic Queries for Objects

 

All the following queries result in a select name,age,... from Person (list of fields depends on mapping file)

// Below two are select * from table

Query query = session.createQuery(“from Person”);

Criteria criteria = session.createCriteria(Person.class);

 

// Simple where clause

Query query = session.createQuery(“from Person p where p.state = ‘MA’”);

Criteria criteria = session.createCriteria(Person.class);

criteria.add(new Expression.eq(“state”, “MA”);

List result = criteria.list();

 

HQL and Criteria same basic and logical operators as SQL ( = <>, in etc)

 

Polymorphic queries

 

CreditCard extends BillingDetails

BankAccount extends BillingDetails

// supported by Query and Criteria

createQuery(“from BillingDetails”); // from base class

createQuery(“from CreditCard”); // from concrete class

createQuery(“from java.lang.Object”); // all persistent objects

createQuery(“from java.io.Serializable”); // all interface also

createCriteria(java.lang.Object.class().list()

 

Joining associations

 

Types of join

 

Hibernate join options  (HQL join similar to SQL join but not the same)

Four types of joins

 

Fetching associations

eager fetching 

 

 

Query query = session.createQuery(“from Item item left join fetch item.bids where item.description like '%gc%'”);

 

Criteria criteria = session.createCriteria(Item.class);

criteria.setFetchMode(“bids”, FetchMode.EAGER);

criteria.add(Expression.like(“description”, “gc”, MatchMode.ANYWHERE);

 

// above two API calls produces the following query

select I.DESCRIPTION, I.CREATED, I.SUCCESSFULL_BID, B.BID_ID, B.AMOUNT, B.ITEM_ID, B.CREATED

from ITEM I

left outer join BID B on I.ITEM_ID = B.ITEM_ID

where I.DESCRIPTION like ‘%gc%’

 

many-to-one or one-to-one associations can also be prefetched

 

Query query = create.Query(“from Bid bid

left join fetch bid.item

left join fetch bid.bidder

where bid.amount > 100”);

 

Criteria criteria = session.createCriteria(Bid.class);

criteria.setFetchMode(“item”, FetchMode.EAGER);

criteria.setFetchMode(“bidder”, FetchMode.EAGER);

criteria.add( Expression.gt(“amount”, new BigDecimal(100)));

 

 

// above two API calls produces the following query

select I.DESCRIPTION, I.CREATED, I.SUCCESSFULL_BID, B.BID_ID, B.AMOUNT, B.ITEM_ID, B.CREATED,

   U.USERNAME, U.PASSWOR,D U.FIRSTNAME, U.LASTNAME

from BID B

left outer join ITEM I on I.ITEM_ID = B.ITEM_ID

left outer join USER U on U.USER_ID = B.BIDDER_ID

where I.DESCRIPTION like ‘%gc%’

 

Join aliases (required for where clause)

Query join will return object pair

Query query = session.createQuery(“from Item item join Item.bids bid where item.description like ‘%gc%’ and bid.amount > 100”);

 

for (Iterator pairs = query.list.iterator(); pairs.hasNext();) {

  Object[] pair = (Object[]) pairs.next();

  Item item = (Item) pair[0];

  Bid bid = (Item) pair[1];

}

 

// will generate Inner join that returns item only

Query query = session.createQuery(“select item from Item item join Item.bids bid where item.description like ‘%gc%’ and bid.amount > 100”);

for (Iterator items = query.list.iterator(); pairs.hasNext();) {

  Item item = (Item) items.next();

}

 

// nest Criteria to do the above

Criteria itemCriteria = session.createCriteria(Item.class);

itemCriteria.add(Expression.like(“description”, “gc”, MatchMode.ANYWHERE));

Criterira bidCriteria = itemCriteria.createCriteria(“bids);

bidCriteria.add(Expression.gt(“amount”, new BigDecimal(“100”)));

List items = itemCriteria.list();

 

List itemBidMaps = itemCriteria.returnMaps().list();

for (Iterator itemBidMaps.iterator(); itemMaps.hasNext();) {

  Map map = (Map) itemBidMaps.next();

  Item item = (Item) itemBidMaps.get(“this”);

  Bid bid = (Bid) map.get(“bid”);

}

 

Implicit joins  (shortcut avoid)

 

Theta-style joins (Cartesian product with where clause)

No Foreign key relationship

from User user, LogRecord log where user.username = log.username;

 

Note: Hibernate does not support outer-join on two tables that don’t have a mapped association

 

Comparing identifiers

from Person p where e.id = 1234

 

User user = ..

Query query = session.createQuery(“from Comment c where c.fromuser = :user”);

q.setEntity(“user”, user);

 

 

 

Writing report queries

HQL syntax

[select ...] from ... [ where ...] [ group by ... [having ...]] [ order by ...]

 

 

JDBC style queries (no POJO, no mapping)

Query query = session.createQuery(“select p.id, p.name, p.phone from Person p where p.state = ‘MA’”);

List list = query.list();

for (Iterator list.iterator(); list.hasNext();) {

   Object[] row = (Object[]) it.next();

   Long id = (Long) row[0];

   String name = (String) row[1];

   String phone = (String row[2];

}

 

// Object oriented version of above query using Data transfer class

// Hibernate uses reflection

public class PersonRow {

  public PersonRow(long id, String name, String phone) { ...}

}

 

Query query = session.createQuery(“select new PersonRow(p.id, p.name, p.phone) from Person p where p.state = ‘MA’”);

List list = query.list();

for (Iterator list.iterator(); list.hasNext();) {

   PersonRow row = (PersonRow) it.next();

}

 

// Aggregation can use count, sum, min, max, avg

Integer count = (Integer) session.createQuery(“select count(*) from Person”).uniqueResult();

 

// Grouping

createQuery(“select user.lastname, count(user) from User user group by user.lastname having user.lastname like ‘A%’”);

 

Advanced query

 

Dynamic queries (QBE)

 

// get users with unsold items

public List findUsers(User u, Item i) throws HibernateException {

 

Example ExampleUser = Example.create(u).ignoreCase().enableLike(matchMode.ANYWHERE);

Example ExampleItem = Example.create(i).ignoreCase().enableLike(matchMode.ANYWHERE);

Criteria criteria = getSession().createCriteria(User.class);

criteria.add(exampleUser);

criteria.createCriteria(“items”).add(exampleItem);

return criteria.list();

}

 

Collection filters (have an implicit from and where)

cannot be applied to transient objects

filter not executed in memory

List results = session.createFilter(item.getBids(), “order by this.amount asc”).list();

 

// can paginate

List results = session.createFilter(item.getBids(), “”).setFirstResult(50).setMaxResults(100).list();

 

 

Subqueries

·               ANSI SQL quantifiers (any, all, some (any) , in (= any))
·               HQL provides shortcuts elements(), indices(), maxelement(), minelement(), maxindex(), minindex(), size()

 

createQuery(“from Item item where 100 < any (select b.amount from item.bids b)”);

 

// HQL shortcut for correlated sub queries

List list = session.createQuery(“from Category c  where :item in elements(c.items)”)

              .setEntity(“item”, item)

         .list();

 

 

Native SQL queries

 

session.createSQLQuery(“select u.USER_ID as {uzer.id}, u.LASTNAME as {uzer.lastname} from USERS u”, “uzer”, User.class);

 

 

Optimizing object retrieval

Solving n+1 selects problem

 

Caching queries

Writing Hibernate Applications

can work with

 

 

Simple Utility that can be used in a servlet engine

public class HibernateUtil {

  // could store in ServletContext or application registry

  private static final SessionFactory sessionFactory;

 

  static {

    try {

      Configuration cfg = new Configuration();

 SessionFactory = cfg.configure().buildSessionFactory();

    } catch (Throwable t) {

      t.printStackTrace();

    }

  }

 

public static Session getSession() throws HibernateException {

     return sessionFactory.openSession();

}

}

 

 

Use Sun Blueprints J2EE DAO pattern

Don’t need to use Sun Blueprints DTO pattern

 

Long Running session

 

 

Table triggers

 

Interceptors (Can be use for auditing)

 

public class MyInterceptor implements Interceptor {

  public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException {}

  public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException  {}

  public void postFlush(Iterator iterator) throws CallbackException {}

  ....

}

 

MyInterceptor mi = new MyInterceptor();

 

Session session = sessionFactory.openSession(mi);

 

 

Hibernate Toolset

 

 

Tools

 

Development Process

 

Automatic Schema Generation

Requires POJO classes to be in path

java net.sf.hibernate.tool.hbm2ddl.SchemaExport options mapping-files

Options

 

can specify column properties such as type, unique-key foreign key which is used fro SchemaExport

 

<property name=”name” type=”string” >

    <column name=”NAME” not-null=”true” length=”255” index=”foo_indx”/>

</property>

 
Call from code
Configuration cfg = new Configuration();
cfg.configure();
SchemaExport schemaExport = new SchemaExport(cfg)
schemaExport.create(false, true);

 

Note: SchemaUpdate to update schema may not work property

 

Automatic POJO generation

java –net.sf.hibernate.tool.CodeGenerator options mapping_files

 

Set code generation attributes in mapping files

 

 

Middlegen (can generated Hibernate, EJB Entity Bean, Struts action etc)

 

Open source tool that has plugin for generate Hibernate Mapping XML from Database Schema

 

XDoclet (set attributes in POJO code instead of XML file)

 

/**

 * @hibernate.class

 * table=”USERS”

 **/

public class User implements Serializable { ...

 

}

 

 

 

 

 

 

 

Hibernate tips and best practices

1)      Have a hibernate class for table entities.

2)      Avoid defining more than one to one mapping e.g. Customer to Home Address and Customer to Billing Address

3)      Avoid using Polymorphic associations and table-per-concrete-class

4)      Avoid using Session.find() use Query.list() instead

5)      Avoid SQL Injection attack by avoiding code like session.createQuery(“from Person p where p.name like ‘” + s + “’”) use bind variables instead

6)      Avoid Binding positional parameter in your queries as it makes code more fragile

7)      Put queries in the Mapping file so that can optimized without changing code

8)      Use HQL for complex queries instead of Criteria API

9)      HQL is not case sensitive, write queries in lower case

10)  Use Criteria API to build a query from a UI

11)   Avoid using implicit joins in HQL

12)   Subqueries may not be portable across databases

13)  Avoid doing eager fetching using outer-join=true to solve n+1 selects problem

14)  Watch for LazyInitializationException thrown by detached objects

 

Links

·        Introduction to Hibernate

·        ORM Mismatch (The Vietnam of computing)

·        Hibernate Basics