By Sandeep Desai (http://www.thedesai.net)
Books:
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)
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);
(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
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
PersistentClass mapping = cfg.getClassMapping(Customer.class)
Column column = new Column();
column.setName(“phone”); ....
mapping.getTable().addColumn(column);...
SimpleValue ....
mapping.addProperty(..)
Three approaches to class hierarchy as per Scott Ambler paper
<?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>
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
Query, Criteria
)Transaction
)
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
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();
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
Interceptor
and return
Boolean.TRUE
from
is
UnSaved()
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
Criteria
API
(tree of Criterion
instances, created using Expression
factory)Criteria criteria =
session.createCriteria(Person.class);
criteria.add(Expression.like(“name”,
“foo”));
List result = criteria.list();
Person examplePerson = new Person();
examplePerson.setState(“Florida”);
Criteria criteria =
session.createCriteria(Person.class)
criteria.add(Example.create(examplePerson));
List result = criteria.list();
class=... lazy=”true”
batch-size=”7”>
for <one-to-one>
and <many-to-one>
set
<class ...
proxy=”ItemInterface”>
<many-to-one
name=”item” outer-join=”true”>
outer-join=”auto”
fetch
associated object lazily if associated object has proxying enabled
outer-join=”true”
fetch
association eagerly using outer join even if proxying enabled
outer-join=”false”
never
fetches the association using outer join even if proxying enabled
<one-to-one name=.. class=..
constrained=”true”>
always
retrieve associated object
collections
always have proxies to detect modification (<set>)
can
specify either
lazy
or
outer-join
attribute not both
no
lazy or outer-join, collection fetched from second level cache or by SQL
SELECT
outer-join=”true”
fetch
eagerlylazy=”true”
fetch lazily
(can set batch-size
)hibernate.max_fetch_depth
global
setting on how many table joins to make for single SQL query
Initializing
lazy associations
Session session...
Category cat = (Category)
session.get(Category.class, id);
Hibernate.initialize(cat.getItems()
tx.commit();
session.close();
Iterator iter =
cat.getItems().iterator();
Tuning object retrieval
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) {}
}
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
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)
load()
and get()
) get
from database only if object isn’t in either cachesession.lock()
)Bypass
both levels of the cache and perform a version check to verify that the
object in memory is the same as in the databaseUPGRADE
but do a SELECT .. FOR
UPGRADE NOWAIT
on Oracle. This diables waiting for the
concurrent lock releases, throwing a locking exception immediately if the
lock can’t be obtained
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();
Long UI based work e.g. shopping cart, user may spend a long time on this transaction
e.g. In auction website two users try to add comments, we can handle this in three ways
Add version property to class using int
counter (better)
or timestamp (less safe)
<version name=”version” column=”VERSION”>
<!—setVersion(int version) -->
incremented
by Hibernate when object is dirty
Query
generated by hibernate on update is
update
person_table set phone=456, version=5 where person_id = 111 and
version=4
if
another transaction has updated then version number will change and
Hibernated will get no rows updated and will throw
StaleObjectStateException
<timestamp name=”lastUpdate”
column=”LAST_UPDATED> <!—setLastUpdated(Date lastUpdated) ->
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
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
Mapping type |
Java type |
Standard JDBC
SQL built-in type |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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>
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));
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>
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>
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
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>
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);
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();
<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);
}
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
Session.find()
and Query.list()
equally fastBoth createQuery and createSQLQuery return Query object
Query query = session.createQuery(“from
Person p where p.state = ‘MA’”)
query.setFirstResult(40);
query.setMaxResults(20);
List results = query.list()
;
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
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()
Types of join
Hibernate join options (HQL join similar to SQL join but not the same)
Four types of joins
eager fetching
distinctResults = new
HashSet(resultList)
setFetchMode(“bids”,
FetchMode.LAZY)
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);
HQL syntax
[select ...] from ... [ where ...] [ group by ...
[having ...]] [ order by ...]
select
clause
performs projectionselect
distinct
upper
etc
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%’”);
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();
}
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();
· ANSI SQL quantifiers (any, all, some (any) , in (= any))
· HQL provides shortcutselements(), 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);
Solving n+1 selects problem
batch-size
in mapping fileouter-join=”true”
maybe
bad idea because it reads too much data
Use Criteria and
setFetchMode
to FetchMode.EAGER
fetch all needed daa in the
initial query
Navigate object graph
Caching queries
Hibernate will expires cache results after an insert,
update or delete
hibernate.cache.use_query_cache=true
(queries ignore this setting by default)
query.setCacheable(true)
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
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);
Development Process
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);
java
–net.sf.hibernate.tool.CodeGenerator options mapping_files
Set code generation attributes in mapping files
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 {
...
}
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
· ORM Mismatch (The Vietnam of computing)