Hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">2</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's current session context -->
<property name="current_session_context_class">org.hibernate.context.ManagedSessionContext</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
<mapping resource="org/hibernate/tutorial/domain/Person.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Person.xml :
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set>
</class>
</hibernate-mapping>
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}
}
public class Person {
private Long id;
private String firstname;
private Set events = new HashSet();
public Person() {}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
// Defensive, convenience methods
protected Set getEvents() {
return events;
}
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
}
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory from hibernate.cfg.xml
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
public class EventManager {
public static void main(String[] args) {
EventManager mgr = new EventManager();
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println("Event: " + theEvent.getTitle() +
" Time: " + theEvent.getDate());
}
}
else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);
}
else if (args[0].equals("addemailtoperson")) {
Long personId = mgr.createAndStorePerson("Foozy", "Beary");
mgr.addEmailToPerson(personId, "foo@bar");
mgr.addEmailToPerson(personId, "bar@foo");
System.out.println("Added two email addresses (value typed objects) to person entity : " + personId);
}
HibernateUtil.getSessionFactory().close();
}
private Long createAndStoreEvent(String title, Date theDate) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
return theEvent.getId();
}
private Long createAndStorePerson(String firstname, String lastname) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person thePerson = new Person();
thePerson.setFirstname(firstname);
thePerson.setLastname(lastname);
session.save(thePerson);
session.getTransaction().commit();
return thePerson.getId();
}
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent = (Event) session.load(Event.class, eventId);
// If we want to handle it bidirectional and detached, we also need to load this
// collection with an eager outer-join fetch, this time with Criteria and not HQL:
/*
Event anEvent = (Event) session
.createCriteria(Event.class).setFetchMode("participants", FetchMode.JOIN)
.add( Expression.eq("id", eventId) )
.uniqueResult(); // Eager fetch the colleciton so we can use it detached
*/
session.getTransaction().commit();
// End of first unit of work
aPerson.addToEvent( anEvent );
// or bidirectional safety method, setting both sides: aPerson.addToEvent(anEvent);
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = ( Person ) session.load(Person.class, personId);
// The getEmailAddresses() might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}
}
public class EventManagerServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
try {
// Begin unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
// Handle actions
if ( "store".equals(request.getParameter("action")) ) {
String eventTitle = request.getParameter("eventTitle");
String eventDate = request.getParameter("eventDate");
if ( "".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b><i>Please enter event title and date.</i></b>");
} else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Added event.</i></b>");
}
}
// Print page
printEventForm(out);
listEvents(out, dateFormatter);
// Write HTML footer
out.println("</body></html>");
out.flush();
out.close();
// End unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
} catch (Exception ex) {
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().rollback();
throw new ServletException(ex);
}
}
private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name='eventTitle' length='50'/><br/>");
out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action' value='store'/>");
out.println("</form>");
}
private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border='1'>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
for (Iterator it = result.iterator(); it.hasNext();) {
Event event = (Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() + "</td>");
out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}
}
/**
* Demonstrates good practice of making sure the SessionFactory is initialized
* on application startup, rather than on first request. Here we register
* as a listener to the servlet context lifecycle for building/closing of the
* SessionFactory.
*
* @author Steve Ebersole
*/
public class SessionFactoryInitializer implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
HibernateUtil.getSessionFactory();
}
public void contextDestroyed(ServletContextEvent event) {
HibernateUtil.getSessionFactory().close();
}
}
public class SessionInterceptor implements Filter {
private static final Logger log = LoggerFactory.getLogger( SessionInterceptor.class );
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
log.trace( "===> opening session for request [" + request.hashCode() + "]" );
// Start the session to be used for this request
Session session = HibernateUtil.getSessionFactory().openSession();
try {
// make the session available to the session factory's "current context"
ManagedSessionContext.bind( session );
// pass control along to the rest of the processing chain
chain.doFilter( request, response );
}
finally {
log.trace( "===> cleaning-up session for request [" + request.hashCode() + "]" );
// remove session from "current context"
ManagedSessionContext.unbind( HibernateUtil.getSessionFactory() );
try {
session.close();
}
catch( Throwable t ) {
log.warn( "was unable to properly close session for request [" + request.hashCode() + "]" );
}
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
web.xml :
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<listener>
<listener-class>org.hibernate.tutorial.web.SessionFactoryInitializer</listener-class>
</listener>
<servlet>
<servlet-name>Event Manager</servlet-name>
<servlet-class>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
<filter>
<filter-name>Session Interceptor</filter-name>
<filter-class>org.hibernate.tutorial.web.SessionInterceptor</filter-class>
</filter>
<filter-mapping>
<filter-name>Session Interceptor</filter-name>
<servlet-name>Event Manager</servlet-name>
</filter-mapping>
</web-app>
Hibernate Performance Considerations Summary:
- Use allways default mapping settings : lazy = true
- Get the default Proxies by session.load(), entityManager.getReference() methods
- Proxies contain only the idendifiers (primary keys)
- if the entitey Object is needed instead of proxy, get it by session.find(), entityManager.get()
- To prevent sequential DB hit, first try to solve by HQL request by using command ...
- If you have sessionLost exceptions in sequential Hibrnate calls, try to use lazy=false at run-time setting by ....
- use batch-size="i" to prevent n+1 selects problem for collections.
- Avoid persistent object hierarchy because of its performance tuning difficulties, if you dont have to use polymorphic persistent objects?
- Avoid to use global fetching strategies.
Optimisation step by step:
- Enable Hibernate SQL Log
- read understand, evaluate SQL queries and their performance
- will the single outer join be faster than two selects?
- are all the indexes used properly?
- what is the cache hit ratio inside the database?
- discuss performance evaluation with your DBA
- execute each use case and note how many and what SQL statements are executed.
- verify obrject retrieval in each use case by walking the object links, retrieval by identifier, HQL, and Criteria queries.
- define your fetch plan considering switching to lazy="true" for m-1, 1-1, and collection mappings
- if SQL statements use complex, slow join statements verify fetch="join" for m-1 & 1-1 relations and hibernate.max_fetch_depth settings
- if too many SQL statements are executed set fetch="join" for certain m-1 & 1-1 relations
- think to change fetch strategy dynamically (at run-time) for each HQL and Crieteria queries.
- Caching
Query specific optimization:
- Dynamic optimization is not as easy as static optimization
Static optimization rules (such as fetch settings defined on mapping) are not applied during the query, so it returns many proxy for complex relations
- As the program calls proxy methods Hibernate hits the DB, so on the log trace one can see many independent SQL calls
- Dynamic optimization capabilities (such as fetch settings) depends on your session, entity manager or hibernate template selections. Using JPA is another factor.
- Think of using “outer join fetch” key word in the query, but it is not advised for 1-many relationships
- For very complex relations think of loading keys in first query and loading each entity by using DAO get method, it seems silly, but in general it works because of static fetch settings.
Count and compare the number of SQL requests on log trace for each try.
If the SQL statements use too complex and slow join operations:
- Optimize SQL execution plan with DBA
- remove fetch="join" on collection mappings
- optimize all the many-to-one, one-to-one associations considering if we need fetch="join" or a secondary select statement.
- try to tune hibernate.max_fetch_depth (recommended as between 1-5)
If too many SQL statements are executed:
- set fetch="join" on many-to-one and one-to-one
- if you can try to set fetch="join" for the others and check the performance
- Keep in mind that more than one eagerly fetched nested collections creates cartesian products
- try to prefetch collections by batch or subselects.
- use batch-size between 3 and 15
Dynamically fetch=EAGER or lazy=false technics for static mapping lazy=true (default) for queries
- using "outer left/right join" fetch key words.
- using hints
Caching:
- the cache is a local copy of data
- resides between the application and the database
- used to avoid DB hit whenever the application performs look-up by identifier
- used to avoid DB hit whenever the persistence layer resolves an association or collection lazily
- may be used in the results of queries but the performance gain is not significant in many case.
- there are 3 scope : transaction, process, cluster
- persistence layers may provide multiple layers of caching, a cache miss in transaction scope may be followed by a look-up in process scope
- scopes (types) of cache used by persistence layer affects the scope of object identity
Scopes (types) of cache:
- Transaction (level1) - attached to (and valid in) one current unit of work, every unit of work has its own cache, data in this cache is not accessed concurrently.
- Process (level2) - shared between many units of work, accessed by concurrently running threads of a process with implications on transaction isolation.
- Cluster (level2) - shared between many units of work, accessed by many processes in the same or different machines.
cache and object identity:
- Transaction scope : two look-ups for objects of the same database identifier return the same instance -
- Process scope : two look-ups for objects of the same database identifier in two concurrently running units of work return the same instance
- Cluster scope : the same instance is not guaranteed.
Transaction scope is preferred in general, if there is a shortage of memory processor scope is preferred even if there is a high probability of deadlocks and reduced scalability due to lock contention.
Deadlock possibility is very low in transaction scope, for multiuser concurrency - web and enterprise applications no need to compare object ids across concurrent unit of works, each user is isolated.
If concurrency model is already defined (as in Oracle, Postgres), cache concurrency is undesirable.
level1 cache is mandatory, level2 is not.
level2 cache has relations with nonexclusive (clustered, shared legacy) access of DB and advised to use for noncritical data, not advised for financial data.
level2 usage is complex and it must be studied/tried carefully before using.
Hibernate Object States
- Transient (new)
- Persistent (get,load,find, list and save, saveOrUpdate, persist etc. )
- Detached (evict, close, clear)
- Removed (delete, remove)
After being detached, going to persistent state again (by reattachment, merging) has some impacts, especially for multi threaded applications.
persistentContext state is also important in these transitions and hibernate object state functions.
Persistence Context:
- may be considered as a cache of managed entity instances.
- One session has one persistence context
- One entity manager has one persistence context
it is used for:
- Automatic dirty checking and transactional write behind.
- first level cache.
- to guarantee a scope of java object identity.
- to span whole conversation by extensions.
Automatic Dirty Checking:
- when a unit of work ends Hibernate may synchronize with DB automatically, especially before execution of a query
- Hibernate has a strategy for modified object checking: automatic dirty checking.
- modified but unsynchronized yet object is called dirty.
- dirty state is not visible to the application.
- to update only modified columns use dynamic-update="true", by default Hibernate updates all columns
- to insert only modified columns : dynamic-insert="true", good for a table with many columns
- to implement customized dirty check use Interceptor with findDirty() method.
Repeatable Read:
- to load an object with PK or to execute query, Hibernate checks the persistent context first, if founds loads it without DB hit.
Object Identity:
- One data access may be considered one unit of work
- One unit of work starts when a request requires data access
- One unit of work ends when the response is ready (session per request strategy)
- One single client request may create many unit of work.
- One conversation is a long-running unit of work (Hibernate), or a user interaction (Application)
- One cannot usually maintain a transaction across a user interaction
Session-per-request with detached objects strategy in conversation
- Object is load, persistent state
- Object changes its state to detached
- Object is held in detached state during user think-time
- Object is modified and it is made persistent again by merge or reattach.
Session-per-conversation
- Object stays persistent state during conversation
- Object never changes its state to detached.
Strategies to implement a conversation:
- with detached objects
- by extending a persistence context
object identity : (a == b)
database identity : ( a.getId().equals( b.getId() ) )
Scope of object identity:
- no identity scope - no guarantee, there may be two instance belong to one row
- persistence context scope idendity - only one object represents a particular row in the scope of a single persistence context
- process scoped identity - only one object represents the particular row in the whole jvm process - preferred for multi threaded applications/
Session session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
// Load Item with identifier value "1234"
Object a = session1.get(Item.class, new Long(1234) );
Object b = session1.get(Item.class, new Long(1234) );
( a==b ); // True, persistent a and b are identical
tx1.commit();
session1.close();
// References a and b are now to an object in detached state
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Object c = session2.get(Item.class, new Long(1234) );
( a==c ); // False, detached a and persistent c are not identical
tx2.commit();
session2.close();
If we implement:
session2.close();
Set allObjects = new HashSet();
allObjects.add(a);
allObjects.add(b);
allObjects.add(c);
allObjects contain 2 elements a, c because a and b come from the same session, so a=b.
In implementation one needs to implement his own equals() and hashCode() methods.
Querying with Hibernate
There are mainly 3 ways:
- HQL, JPL - QL
- Criteria
- By Example (with criteria) - QBE
Query q = session.createQuery("from User as u where u.firstname = :fname");
q.setString("fname", "John");
List result = q.list();
Easy to maintain
Criteria:
Criteria criteria = session.createCriteria(User.class);
criteria.add( Restrictions.like("firstname", "John") );
List result = criteria.list();
Java code oriented
Criteria criteria = session.createCriteria(User.class);
User exampleUser = new User();
exampleUser.setFirstname("John");
criteria.add( Example.create(exampleUser) );
criteria.add( Restrictions.isNotNull("homeAddress.city") );
List result = criteria.list();
java code oriented - for queries difficult to implement with HQL like above.
Queries
Hibernate use lazy fetching strategy for all entities and collections by default.
JPA
Not to hit DB is not the purpose of the lazy loading at all.
Hibernate proxies are instances of run-time generated subclasses of entity classes
Proxy does not belong to JPA, belongs to Hibernate3.
if you get an entity object all associated entities and collections are not initialized.
To reach entity class instead of proxy use:
HibernateProxyHelper.getClassWithoutInitializingProxy(entityObject);
Hits Database and executes Select statement.
UserDAO.get(id);
entityManager.find(User.class, id);
Does not hit Database, uses proxy, does not execute HQL Select (but sometimes does)
session.load(User.class, id);
entityManager.getReference(User.class, id);
Example: use session.load() to update foreign key of an entity, so no need to make select
proxy initialisation:
Item item = (Item) session.load(Item.class, new Long(123));
item.getId();
item.getDescription(); // Initialize the proxy
without proxy initialisation, instanciates two proxies with PKs:
Item item = (Item) session.load(Item.class, new Long(123));
User user = (User) session.load(User.class, new Long(1234));
Bid newBid = new Bid("99.99");
newBid.setItem(item);
newBid.setBidder(user);
session.save(newBid);
proxies contains the identifier values only.
collections use collection-wrappers for lazy loading.
one-to-one assosiacion based on the shared pk can be proxied only if constrained=true
implementation of lazy:
- by proxy, Hibernate generates, instantiates proxy subclasses at runtime for persistent objects, they contain only identifiers.
- by interception, Hibernate instantiates the persistent object, it sets only identifier.
Proxies are recommended, only difficulty is for polymorphic objects comparisons,
we cannot use 'instanceof' operator directly with proxies
A collection is initialized if one call size(), isEmpty(), contains() or start to iterate.
A collection may be defined lazy="extra" (not for JPA) to prevent initialization with these methods - but DB is hit.
@OneToMany
@org.hibernate.annotations.LazyCollection(
org.hibernate.annotations.LazyCollectionOption.EXTRA
)
private Set<Bid> bids = new HashSet<Bid>();
this option does not exist in JPA.
@OneToMany(fetch = FetchType.EAGER)
private Set<Bid> bids = new HashSet<Bid>();
Disabling Lazy:
session.load does not return proxy (eg Item instead of ItemProxy).
entityManager.getReference does not return proxy reference (eg Item instead of ItemProxy).
session.get & entityManager.find returns entity with no proxy for its associations (e.g. Item & User instead of Item & UserProxy).
Example: lazy false:
<class name="User" table="USERS" lazy="false">
...
</class>
or
@Entity
@Table(name = "USERS")
@org.hibernate.annotations.Proxy(lazy = false)
public class User { ... }
then load, getReference hit the DB.
User user = (User) session.load(User.class, new Long(123));
User user = em.getReference(User.class, new Long(123));
Lazy initialization by interception (lazy="no-proxy") means:
<class name="Item" table="ITEM">
...
<many-to-one name="seller"
class="User"
column="SELLER_ID"
update="false"
not-null="true"
lazy="no-proxy" />
...
</class>
means, for seller loads user by interception:
Item item = (Item) session.get(Item.class, new Long(123));
User seller = item.getSeller();
Default fetch strategy is lazy, so if we load Item we execute a SQL stmt.
To load its associations and collections we execute other SQL stmts.
Item item = (Item) session.get(Item.class, new Long(123));
results in the SQL select:
select item.* from ITEM item where item.ITEM_ID = ?
List allItems = session.createQuery("from Item").list();
processSeller( (Item)allItems.get(0) );
processSeller( (Item)allItems.get(1) );
processSeller( (Item)allItems.get(2) );
results in one SQL for allItems, and one for each item
All associated users are proxies:
select items...
select u.* from USERS u where u.USER_ID = ?
select u.* from USERS u where u.USER_ID = ?
this is called n+1 select problem
Prefetching data in batch blind-guess optimizations:
batch size fetching strategy:
<class name="User" table="USERS" batch-size="10">
...
</class>
or
@Entity
@Table(name = "USERS")
@org.hibernate.annotations.BatchSize(size = 10)
public class User { ... }
results in:
select items...
select u.* from USERS u where u.USER_ID in (?, ?, ?)
for collections:
<class name="Item" table="ITEM">
...
<set name="bids" inverse="true" batch-size="10">
<key column="ITEM_ID" />
<one-to-many class="Bid" />
</set>
</class>
results in:
select items...
select b.* from BID b where b.ITEM_ID in (?, ?, ?)
Prefetching collections with sub select:
<class name="Item" table="ITEM">
...
<set name="bids" inverse="true" fetch="subselect">
<key column="ITEM_ID" />
<one-to-many class="Bid" />
</set>
</class>
or
@OneToMany
@org.hibernate.annotations.Fetch(
org.hibernate.annotations.FetchMode.SUBSELECT
)
private Set<Bid> bids = new HashSet<Bid>();
results in:
select i.* from ITEM i
select b.* from BID b
where b.ITEM_ID in (select i.ITEM_ID from ITEM i)
Fetching with joins:
<class name="Item" table="ITEM">
...
<many-to-one name="seller"
class="User"
column="SELLER_ID"
update="false"
fetch="join" />
</class>
results in:
select i.*, u.*
from ITEM i
left outer join USERS u on i.SELLER_ID = u.USER_ID
where i.ITEM_ID = ?
for JPA use EAGER:
@Entity
public class Item {
// ...
@ManyToOne(fetch = FetchType.EAGER)
private User seller;
@OneToMany(fetch = FetchType.EAGER)
private Set<Bid> bids = new HashSet<Bid>();
// ...
}
Fetching for inheritance hierarchy:
select
b1.BILLING_DETAILS_ID,
b1.OWNER,
b1.USER_ID,
b2.NUMBER,
b2.EXP_MONTH,
b2.EXP_YEAR,
b3.ACCOUNT,
b3.BANKNAME,
b3.SWIFT,
case
when b2.CREDIT_CARD_ID is not null then 1
when b3.BANK_ACCOUNT_ID is not null then 2
when b1.BILLING_DETAILS_ID is not null then 0
end as clazz
from
BILLING_DETAILS b1
left outer join
CREDIT_CARD b2
on b1.BILLING_DETAILS_ID = b2.CREDIT_CARD_ID
left outer join
BANK_ACCOUNT b3
on b1.BILLING_DETAILS_ID = b3.BANK_ACCOUNT_ID
Here above the case statement fills clazz column with 0-2
Optimisation Guidelines
Outer join redundancy (Cartesian product problem):
<class name="Item">
...
<set name="bids" inverse="true" fetch="join">
<key column="ITEM_ID" />
<one-to-many class="Bid" />
</set>
<set name="images" fetch="join">
<key column="ITEM_ID" />
<composite-element class="Image" > ...
...
</set>
</class>
results in:
select item.*, bid.*, image.*
from ITEM item
left outer join BID bid on item.ITEM_ID = bid.ITEM_ID
left outer join ITEM_IMAGE image on item.ITEM_ID = image.ITEM_ID
If for one ITEM row, there are 2 related BID row and
if for the 2 BID row, there are 4 related ITEM_IMAGE
that means in result set:
- For each ITEM_IMAGE row, there are 2 BID rows which contains the same info - redundant fields.
- For each ITEM_IMAGE row, there are 4 ITEM rows which contains the same info - redundant fields.
- But all the rows are different
As the join numbers and related row numbers increase, the redundant fields increase in Cartesian manner.
One must be careful with memory consumption.
To load all the Collection items use dynamic fetch with HQL or Criteria.
if not satisfied try static method : Hibernate.initialise( persObj)
SQL Injection Security Issue
Dont write parameters to HQL statements like:
String queryString = "from Item i where i.description like '" + search + "'";
List result = session.createQuery(queryString).list();
If a malicious user searches:
foo' and callSomeStoredProcedure() and 'bar' = 'bar
He will execute callSomeStoredProcedure(), The quoted charactes aren't escaped !
JPA HQL Query Hints
Let’s assume that you make modifications to persistent objects before executing a query. Hibernate flushes the persistence context before executing your query. This guarantees consistency between DB and in-memory objects.
Sometimes this is not necessary so use:
Query q = session.createQuery(queryString).setFlushMode(FlushMode.COMMIT);
Criteria criteria = session.createCriteria(Item.class).setFlushMode(FlushMode.COMMIT);
Query q = em.createQuery(queryString).setFlushMode(FlushModeType.COMMIT);
Query cache and Level 2 cache relation settings:
Query q = session.createQuery("from Item").setCacheMode(CacheMode.IGNORE);
Criteria criteria = session.createCriteria(Item.class).setCacheMode(CacheMode.IGNORE);
Query q = em.createQuery(queryString).setHint("org.hibernate.cacheMode", CacheMode.IGNORE);
read only but in persistent state (not detached)
Query q = session.createQuery("from Item").setReadOnly(true);
Criteria criteria = session.createCriteria(Item.class).setReadOnly(true);
Query q = em.createQuery("select i from Item i").setHint("org.hibernate.readOnly", true);
Hibernate does not persist any modifications automatically unless one disables read-only mode with:
session.setReadOnly(object, false);
Time out setting for JDBC connections:
Query q = session.createQuery("from Item").setTimeout(60); // 1 minute
Criteria criteria = session.createCriteria(Item.class).setTimeout(60);
Query q = em.createQuery("select i from Item i").setHint("org.hibernate.timeout", 60);
Fetche size setting for JDBC connectins:
Query q = session.createQuery("from Item").setFetchSize(50);
Criteria criteria = session.createCriteria(Item.class).setFetchSize(50);
Query q = em.createQuery("select i from Item i").setHint("org.hibernate.fetchSize", 50);
Adding SQL comment for logs:
Query q = session.createQuery("from Item").setComment("My Comment...");
Criteria criteria = session.createCriteria(Item.class).setComment("My Comment...");
Query q = em.createQuery("select i from Item i").setHint("org.hibernate.comment", "My Comment...");
Reducing memory comsumption and prevent long dirty checking cycles:
session.setReadOnly(object, false);
EntityManager doesn't supports this.
Query q = session.createQuery("from Item").setReadOnly(true);
Criteria criteria = session.createCriteria(Item.class).setReadOnly(true);
Query q = em.createQuery("select i from Item i").setHint("org.hibernate.readOnly", true);
Query is persistent but no automatic dirty checking and does not
persist any modifications automatically.
Pessimistic Lock settings:
Query q = session.createQuery("from Item item").setLockMode("item", LockMode.UPGRADE);
Criteria criteria = session.createCriteria(Item.class).setLockMode(LockMode.UPGRADE);
Execution Queries
retrieves all the states in a first select.
List result = myQuery.list();
List result = myCriteria.list();
JPA:
List result = myJPAQuery.getResultList();
retrieves only primary keys in a first select then tries to find the rest of the object states in the level1 then (if enabled) level2 cache.
Query categoryByName = session.createQuery("from Category c where c.name like :name");
categoryByName.setString("name", categoryNamePattern);
Iterator categories = categoryByName.iterate();
the initial query retrieves only pk values, the iteration checks Level1, level2 caches, if cannot find executes additional select for each step.
uses cursor that is held on DB system
ScrollableResults itemCursor = session.createQuery("from Item").scroll();
itemCursor.first();
itemCursor.last();
itemCursor.get();
itemCursor.next();
itemCursor.scroll(3);
itemCursor.getRowNumber();
itemCursor.setRowNumber(5);
itemCursor.previous();
itemCursor.scroll(-3);
itemCursor.close();
itemCursor.get() supplies Item instance
Named queries
To externilize query strings to mapping meta-data
session.getNamedQuery("findItemsByDescription").setString("desc", description);
em.createNamedQuery("findItemsByDescription").setParameter("desc", description);
in XML
<query name="findItemsByDescription"><![CDATA[
from Item item where item.description like :desc
]]></query>
annotations
@NamedQueries({
@NamedQuery(
name = "findItemsByDescription",
query = "select i from Item i where i.description like :desc"
),
// ...
})
@Entity
@Table(name = "ITEM")
public class Item {
// ...
}
Polymorphic query
If subtypes of BillingDetails are: CreditCard and BankAccount
from BillingDetails
brings all of them
from CreditCard
brings only CreditCard
from java.lang.Object
brings all
from java.io.Serializable
brings all serializable
Examples:
Null check:
from User u where u.email is null
from Item i where i.successfulBid is not null
like:
from User u where u.firstname not like '%Foo B%'
collections check:
from Item i where i.bids is not empty
from Item i, Category c where i.id = '123' and i member of c.items
calling SQL functions:
from User u where lower(u.email) = 'foo@hibernate.org'
from User user where concat(user.firstname, user.lastname) like 'G% K%'
from Item i where size(i.bids) > 3
select item.startDate, current_date() from Item item
adding SQL functions which is not registered in our SQL Dialect:
Configuration cfg = new Configuration();
cfg.addSqlFunction("lpad", new StandardSQLFunction("lpad", Hibernate.STRING));
cfg.buildSessionFactory();
Projections:
Query q = session.createQuery("from Item i, Bid b");
// Query q = em.createQuery("select i, b from Item i, Bid b");
Iterator pairs = q.list().iterator();
// Iterator pairs = q.getResultList().iterator();
while (pairs.hasNext()) {
Object[] pair = (Object[] ) pairs.next();
Item item = (Item ) pair[0];
Bid bid = (Bid ) pair[1];
}
Joins:
from User u where u.homeAddress.city = 'Bangkok'
Or
from Bid bid
where bid.item.category.name like 'Laptop%'
and bid.item.successfulBid.amount > 100
which creates SQL:
select ...
from BID B
inner join ITEM I on B.ITEM_ID = I.ITEM_ID
inner join CATEGORY C on I.CATEGORY_ID = C.CATEGORY_ID
inner join BID SB on I.SUCCESSFUL_BID_ID = SB.BID_ID
where C.NAME like 'Laptop%'
and SB.AMOUNT > 100
Or
select i
from Item i join i.bids b
where i.description like '%Foo%'
and b.amount > 100
creates SQL request:
select i.DESCRIPTION, i.INITIAL_PRICE, ...
from ITEM i
inner join BID b on i.ITEM_ID = b.ITEM_ID
where i.DESCRIPTION like '%Foo%'
and b.AMOUNT > 100
Terminology : join fetch = inner join fetch (means fetch EAGERLY)
Instead of lazy=true (default) if we want to use lazy=false dynamically only for one query (eagerly FETCHED), we may use outer joins as:
from Item i
left join fetch i.bids
where i.description like '%Foo%'
returns all items with a description that contains 'Foo' and their bid collections in a single SQL operation.
when executed it returns list of Item instants with their bid collections fully initialized.
select i.DESCRIPTION, i.INITIAL_PRICE, ...
b.BID_ID, b.AMOUNT, b.ITEM_ID, b.CREATED_ON
from ITEM i
left outer join BID b on i.ITEM_ID = b.ITEM_ID
where i.DESCRIPTION like '%Foo%'
Prefetching many-to-one and one-to-one associations:
from Bid bid
left join fetch bid.item
left join fetch bid.bidder
where bid.amount > 100
executes SQL query:
select b.BID_ID, b.AMOUNT, b.ITEM_ID, b.CREATED_ON
i.DESCRIPTION, i.INITIAL_PRICE, ...
u.USERNAME, 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 b.AMOUNT > 100
ATTENTION ! :
- one cannot assign an alias like: left join fetch i.bids b where b = - INVALID
- one shouldn't fecth more than one collection in paralel, it creates cartesian product as :
select item.*, bid.*, image.*
from ITEM item
left outer join fetch BID bid on item.ITEM_ID = bid.ITEM_ID
left outer join fetch ITEM_IMAGE image on item.ITEM_ID = image.ITEM_ID
- HQL JPAQL queries ignore any fetching strategy defined in XML or annotations, but apply globally defined fetching strategies.
- if you eager fetch a collection, duplicates may be returned:
select i from Item i join fetch i.bids
Each item is duplicated on the left side as many times as related Bid data is present.
To prevent it use Set as:
Set noDupes = new LinkedHashSet(resultList);
or use keyword DISTINCT:
select distinct i from Item i join fetch i.bids
distict does not operate at the SQL level, but forces Hibernate to fileter out duplicates.
- Pagination with setMaxResults()/setFirstResult() dont work with join fetch
useful when a join condition isn't a foreign key relationship.
from User user, LogRecord log where user.username = log.username
Here user and log has no relation with keys.
from Item i, User u where i.seller = u and u.username = 'steve'
Here i.seller is a foreign key to user and canbe re-expressed with join statements as :
from Item i join i.seller u where u.username = 'steve'
But
from Item i, Bid b where i.seller = b.bidder
here i.seller and b.bidder are foreing keys to user, this cannot be re-espressed with join statements.
Reporting queries:
Long count = (Long) session.createQuery("select count(i) from Item i").uniqueResult();
select sum(i.successfulBid.amount) from Item i
select min(bid.amount), max(bid.amount) from Bid bid where bid.item.id = 1
select count(distinct i.description) from Item i
select u.lastname, count(u) from User u group by u.lastname
creates SQL query:
select u.LAST_NAME, count(u.USER_ID) from USER u group by u.LAST_NAME
Group by with condition:
select item.id, count(bid), avg(bid.amount)
from Item item
join item.bids bid
where item.successfulBid is null
group by item.id
having count(bid) > 10
If you define class ItemBidSummary with a constructor that takes Long, Long, BigDecimal
select new ItemBidSummary(bid.item.id, count(bid), avg(bid.amount))
from Bid bid
where bid.item.successfulBid is null
group by bid.item.id
returns instances of ItemBidSummary
correlated:
from User u where 10 < (
select count(i) from u.items i where i.successfulBid is not null
)
it refers to an alias u at sub-select
Returns users who have sold more than 10 items
uncorrelated:
from Bid bid where bid.amount + 1 >= (
select max(b.amount) from Bid b
)
Returns all bids whose amount is within one ($) of that amount.
sub-selects must be enclosed in parentheses
Be careful with the performance of correlated sub-selects, on matured DB it must equal to join performance.
if a sub-query returns multiple rows, it's combined with quantification
from Item i where 100 > all ( select b.amount from i.bids b )
Returns items where all bids are less than 100
from Item i where 100 <= any ( select b.amount from i.bids b )
Returns others: items where all bids are greater than 100
from Item i where 100 = some ( select b.amount from i.bids b )
from Item i where 100 in ( select b.amount from i.bids b )
Return items with a bid of exactly 100
Criteriaaaaas
session.createCriteria(Item.class);
session.createCriteria(BillingDetails.class);
Polymorphism, the following criteria returns all the persistent objects:
session.createCriteria(java.lang.Object.class);
session.createCriteria(User.class)
.addOrder( Order.asc("lastname") )
.addOrder( Order.asc("firstname") );
Restrictions
DetachedCriteria crit = DetachedCriteria.forClass(User.class)
.addOrder( Order.asc("lastname") )
.addOrder( Order.asc("firstname") );
List result = crit.getExecutableCriteria(session).list();
Criterion emailEq = Restrictions.eq("email", "foo@hibernate.org");
Criteria crit = session.createCriteria(User.class);
crit.add(emailEq);
User user = (User ) crit.uniqueResult();
User user = (User ) session.createCriteria(User.class)
.add(Restrictions.eq("email", "foo@hibernate.org"))
.uniqueResult();
Comparison:
Criterion restriction = Restrictions.between("amount", new BigDecimal(100), new BigDecimal(200));
session.createCriteria(Bid.class).add(restriction);
session.createCriteria(Bid.class).add(Restrictions.gt("amount", new BigDecimal(100)));
String[] emails = {"foo@hibernate.org", "bar@hibernate.org" };
session.createCriteria(User.class).add(Restrictions.in("email", emails));
null:
session.createCriteria(User.class).add(Restrictions.isNull("email"));
session.createCriteria(User.class).add(Restrictions.isNotNull("email"));
collection:
session.createCriteria(Item.class).add(Restrictions.isEmpty("bids"));
session.createCriteria(Item.class).add(Restrictions.sizeGt("bids", 3));
property:
session.createCriteria(User.class).add(Restrictions.eqProperty("firstname", "username"));
string:
session.createCriteria(User.class).add(Restrictions.like("username", "G%"));
session.createCriteria(User.class).add(Restrictions.like("username", "G", MatchMode.START));
session.createCriteria(User.class).add(Restrictions.eq("username", "foo").ignoreCase());
logical operators:
session.createCriteria(User.class).add(Restrictions.like("firstname", "G%")).add(
Restrictions.like("lastname", "K%"));
session.createCriteria(User.class).add(
Restrictions.or(Restrictions.and(Restrictions.like("firstname", "G%"), Restrictions.like(
"lastname", "K%")), Restrictions.in("email", emails)));
Restrictions.conjunction():
session.createCriteria(User.class).add(
Restrictions.disjunction().add(
Restrictions.conjunction().add(Restrictions.like("firstname", "G%")).add(
Restrictions.like("lastname", "K%"))).add(
Restrictions.in("email", emails)));
sql expressions
session.createCriteria(User.class).add(
Restrictions.sqlRestriction("length({alias}.PASSWORD) < ?", 5, Hibernate.INTEGER));
subqueries:
DetachedCriteria subquery = DetachedCriteria.forClass(Item.class, "i");
subquery.add(Restrictions.eqProperty("i.seller.id", "u.id")).add(Restrictions.isNotNull("i.successfulBid"))
.setProjection(Property.forName("i.id").count());
Criteria criteria = session.createCriteria(User.class, "u").add(Subqueries.lt(10, subquery));
joining associations 1
Criteria itemCriteria = session.createCriteria(Item.class);
itemCriteria.add(Restrictions.like("description", "Foo", MatchMode.ANYWHERE));
Criteria bidCriteria = itemCriteria.createCriteria("bids");
bidCriteria.add(Restrictions.gt("amount", new BigDecimal(99)));
List result = itemCriteria.list();
joining associations 2
session.createCriteria(Item.class)
.createAlias("bids", "b")
.add( Restrictions.like("description", "%Foo%") )
.add( Restrictions.gt("b.amount", new BigDecimal(99) ) );
joining associations 3
session.createCriteria(Item.class)
.createAlias("images", "img")
.add( Restrictions.gt("img.sizeX", 320 ) );
dynamic fetching with criteria:
outer join with eagerly loaded bids:
session.createCriteria(Item.class)
.setFetchMode("bids", FetchMode.JOIN)
.add( Restrictions.like("description", "%Foo%") );
inner join:
session.createCriteria(Item.class)
.createAlias("bids", "b", CriteriaSpecification.INNER_JOIN)
.setFetchMode("b", FetchMode.JOIN)
.add( Restrictions.like("description", "%Foo%") );
prefetching many-to-one and one-to-one:
session.createCriteria(Item.class)
.setFetchMode("bids", FetchMode.JOIN)
.setFetchMode("seller", FetchMode.JOIN)
.add( Restrictions.like("description", "%Foo%") );
Spring Hibernate lazy init missing session error
https://www.hibernate.org/43.html
The problem
A common issue in a typical (web-)application is the rendering of the view, after the main logic of the action has been completed, and therefore, the Hibernate Session has already been closed and the database transaction has ended. If you access detached objects that have been loaded in the Session inside your JSP (or any other view rendering mechanism), you might hit an unloaded collection or a proxy that isn't initialized. The exception you get is: LazyInitializationException: Session has been closed (or a very similar message). Of course, this is to be expected, after all you already ended your unit of work.
A first solution would be to open another unit of work for rendering the view. This can easily be done but is usually not the right approach. Rendering the view for a completed action is supposed to be inside the first unit of work, not a separate one. The solution, in two-tiered systems, with the action execution, data access through the Session, and the rendering of the view all in the same virtual machine, is to keep the Session open until the view has been rendered.
A good standard interceptor in a servlet container is a ServletFilter. It's rather trivial to put some lines into a custom filter that runs on every request and before every response, taken from CaveatEmptor:
public class HibernateSessionRequestFilter implements Filter {
private static Log log = LogFactory.getLog(HibernateSessionRequestFilter.class);
private SessionFactory sf;
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
try {
log.debug("Starting a database transaction");
sf.getCurrentSession().beginTransaction();
// Call the next filter (continue request processing)
chain.doFilter(request, response);
// Commit and cleanup
log.debug("Committing the database transaction");
sf.getCurrentSession().getTransaction().commit();
} catch (StaleObjectStateException staleEx) {
log.error("This interceptor does not implement optimistic concurrency control!");
log.error("Your application will not work until you add compensation actions!");
// Rollback, close everything, possibly compensate for any permanent changes
// during the conversation, and finally restart business conversation. Maybe
// give the user of the application a chance to merge some of his work with
// fresh data... what you do here depends on your applications design.
throw staleEx;
} catch (Throwable ex) {
// Rollback only
ex.printStackTrace();
try {
if (sf.getCurrentSession().getTransaction().isActive()) {
log.debug("Trying to rollback database transaction after exception");
sf.getCurrentSession().getTransaction().rollback();
}
} catch (Throwable rbEx) {
log.error("Could not rollback transaction after exception!", rbEx);
}
// Let others handle it... maybe another interceptor for exceptions?
throw new ServletException(ex);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
log.debug("Initializing filter...");
log.debug("Obtaining SessionFactory from static HibernateUtil singleton");
sf = HibernateUtil.getSessionFactory();
}
public void destroy() {}
}
http://forum.springsource.org/showthread.php?t=26080
More updates: I find that the Spring OSIV is creating a Session. But in spite of that the HibernateInterceptor interceptor still creates another Session for the Facade and closes it after the Facade operation is done.
So it is something like this:
1 Spring OSIV Session opened
2 View layer calls Facade method
3 Hibernate Interceptor creates NEW session2
4 Facade method called
5 session2 committed and closed
6 View tries to access a Lazy association and gets Lazy exception Session closed. This is obvious as the Session2 was used for this association.
7 Spring OSIV session closed
So it comes down to how I can use the OSIV session in the Facade method, instead of the hibernate interceptor creating a new Session 2.
so the problem was: There were two sessionFactories!
OSIV was instantiating one with ContextServlet and the App was instantiating another with ClassPathXML Context.
When I changed the App Code to get its context using WebAppUtils.getContext(), all issues magically go away! The OSIV session is used by Hibernate Interceptors.
So now I've taken the first step in the right direction.
HIBERNATE TEMPLATE (SPRING)
Spring Framework provides HibernateTemplate to integrate with Hibernate. There are two main reasons:
Hiding away the session and transaction management details
Template based approach provides consistency and makes the code maintainable.
HIBERNATE MAVEN PLUGIN
http://mojo.codehaus.org/maven-hibernate3/hibernate3-maven-plugin/
hibernate3:hbm2cfgxml: Generates hibernate.cfg.xml
hibernate3:hbm2ddl: Generates database schema.
hibernate3:hbm2doc: Generates HTML documentation for the database schema.
hibernate3:hbm2hbmxml: Generates a set of hbm.xml files
hibernate3:hbm2java: Generates Java classes from set of *.hbm.xml files
hibernate3:hbmtemplate: Renders arbitrary templates against Hibernate Mapping information.
mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.javadevhome.example -DartifactId=hibernate