Hibernate"

From Documentation
m (remove empty version history (via JWB))
 
(32 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
{{ZKDevelopersReferencePageHeader}}
 
{{ZKDevelopersReferencePageHeader}}
  
=Overview=
 
Hibernate is an object-relational mapping (ORM) solution for the Java language. The main feature of Hibernate is that it simplifies the job of accessing a relational database.
 
  
Example here we use with Hibernate 3.3 and hsqldb 1.8. It shall work with the newer version.
 
  
=Installing Hibernate=
+
= Overview =
Before using Hibernate, you have to install it into your application first.
 
  
#Download hibernate core and hibernate annotations from [http://www.hibernate.org Hibernate]
+
Due to ''object/relational paradigm mismatch'' <ref> Hibernate in Action, Christian Bauer, Gavin King, Manning</ref>, developers tend to use ORM (''object/relational mapping'') framework to convert object-oriented model to relational model and vice versa. ''Hibernate'' is the most popular ORM framework in Java world. We will talk about some integration topics in this chapter such as lazy initialization with Spring. If you haven't read about basic concepts and installation of Hibernate, please refer to [http://www.hibernate.org/docs Hibernate Dcumentation]. The example we give in this chapter is based on Hibernate '''4.0.0.final''' and Spring '''3.1.2.RELEASE'''.
#Put *.jar files from hibernate core and hibernate annotations into your $myApp/WEB-INF/lib/
 
  
$myApp represents the name of your web application. ex. event
+
= Integrate with Different DAO implementation =
  
=Configuring the ZK Configuration File=
+
The ''Data Access Object (DAO)'' pattern is a good practice to implement a persistence layer. This pattern encapsulates data access codes written by Hibernate API from business tier. A DAO object exposes an interface to business object and performs persistence operation relating to a particular persistent entity.  
To make ZK works with Hibernate smoothly, you have to use the following utilities.
 
  
# Create <tt>zk.xml</tt> under <tt>$myApp/WEB-INF/</tt>(if not exists)
+
According to Hibernate Reference Manual's suggestion <ref> [http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#transactions-basics-uow Unit of Work in Hibernate Core Reference Manual]</ref>, we should apply ''session-per-request'' pattern to manage sessions and transactions. This pattern needs an interceptor to open a contextual session with a transaction  when a request is going to be handled and close the session before respond is sent to client (aka. ''open session in view'' pattern). A common implementation for page-based application is a servlet filter.
# Copy the following lines into your <tt>zk.xml</tt>
 
  
<source lang="xml" >
+
Applying this pattern also solves a common "LazyInitializationException" problem that most developers encounter when using lazy fetching strategy. In brief, Hibernate session is usually closed after a DAO object has performed an operation (a.k.a ''session-per-operation'' pattern). Those persistent objects become ''detached'' after the associated sessions are closed. If we access a detached object's lazy-loaded collection when rendering the view. We will get an error message like <code>LazyInitializationException: no session or session was closed</code>. For detailed explanation, please refer to the article "Open Session in View" on Hibernate community<ref> [https://community.jboss.org/wiki/OpenSessionInView#The_problem Open Session in View\Problem]</ref>. As we apply ''session-per-request'' pattern, a Hibernate session is kept open when a View is accessing lazy-loaded collection. Those objects queried by the Hibernate session becomes detached later (after the interceptor closes the Hibernate session), so the previously mentioned problem is resolved.
<!-- Hibernate SessionFactory lifecycle -->
 
<listener>
 
<description>Hibernate SessionFactory lifecycle</description>
 
<listener-class>org.zkoss.zkplus.hibernate.HibernateSessionFactoryListener</listener-class>
 
</listener>
 
  
<!-- Hibernate OpenSessionInView Pattern -->
 
<listener>
 
<description>Hibernate Open Session In View life-cycle</description>
 
<listener-class>org.zkoss.zkplus.hibernate.OpenSessionInViewListener</listener-class>
 
</listener>
 
  
<!-- Hibernate thread session context handler -->
+
== Homemade DAO==
<listener>
+
Here we introduce how to implement an DAO without other frameworks (e.g. Spring).
<description>Hibernate thread session context handler</description>
+
 
<listener-class>org.zkoss.zkplus.hibernate.HibernateSessionContextListener</listener-class>
+
=== Configuration ===
</listener>
+
The minimal Maven dependency you need is:
 +
 
 +
<source lang='xml'>
 +
 
 +
    <dependency>
 +
      <groupId>org.hibernate</groupId>
 +
      <artifactId>hibernate-core</artifactId>
 +
      <version>4.0.0.Final</version>
 +
      <scope>compile</scope>
 +
    </dependency>
 +
 
 
</source>
 
</source>
  
<tt>$myApp</tt> represents the name of your web application. ex. <tt>event</tt>
+
<div style="-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;color:#c06330;padding:15px 40px;background:#fed no-repeat 13px 13px;margin-bottom:10px">
 +
[[File:Icon_info.png ]] '''Note:''' If you don't use Maven, please refer to Hibernate Reference Documentation to know which JAR file you need.
 +
</div>
 +
 
 +
 
 +
=== Utility Class ===
  
=Creating the Java Objects=
+
A simple way to implement a DAO is to control Hibernate sessions and transactions manually, hence we need a utility class to get <code>SessionFactory</code>. ZK's <javadoc>org.zkoss.zkplus.hibernate.HibernateUtil</javadoc> has been deprecated since 6.0.2, you can write your own one according to the code example in Hibernate Reference Manual.<ref> [http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#tutorial-firstapp-helpers Hibernate Reference Documentation\ Tutorial] </ref> Here we provide a simple example.
You have to create simple JavaBean class with some properties.
 
  
# Create your first Java class (<tt>Event.java</tt>)
+
'''Utility class to get SessionFactory'''
 +
<source lang="java">
 +
package org.zkoss.reference.developer.hibernate.dao;
  
<source lang="java" >
+
import org.hibernate.SessionFactory;
package events;
+
import org.hibernate.cfg.Configuration;
  
import java.util.Date;
+
public class HibernateUtil {
 +
    private static SessionFactory sessionFactory;
 +
    static {
 +
        try {
 +
            sessionFactory = new Configuration().configure().buildSessionFactory();
 +
        } catch (Throwable ex) {
 +
            throw new ExceptionInInitializerError(ex);
 +
        }
 +
    }
  
public class Event {
+
    public static SessionFactory getSessionFactory() {
    private Long id;
+
        return sessionFactory;
    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;
 
    }
 
}
 
 
</source>
 
</source>
  
# You have to compile the Java source, and place the class file in a directory called <tt>classes</tt> in the Web development folder, and in its correct package. (ex.<tt>$myApp/WEB-INF/classes/event/Event.class</tt>)
+
=== An Open Session Listener ===
  
The next step is to tell Hibernate how to map this persistent class with database.
+
For ''open session in view'' pattern, we need an interceptor to open sessions. In ZK, we need to intercept all requests including AU requests, so we implement ZK's [[ZK Developer's Reference/Customization/Life Cycle Listener| Life Cycle Listener]] to achieve this. (Our listener's implementation is based on the filter mentioned by a Hibernate article "Open Session in View". <ref> [https://community.jboss.org/wiki/OpenSessionInView#Using_an_interceptor Open Session in View\Using an intercepto]</ref>.) This listener opens a session and begins a transaction at the beginning of an execution (ZK's HTTP request wrapper, then commits (or rollback) at the end of the execution.
  
=Mapping the Java Objects=
+
'''Extracted from OpenSessionInViewListener'''
There are two ways to tell Hibernate how to load and store objects of the persistent class, one is using Java Annotation, and the other older way is using Hibernate mapping file.
+
<source lang="java" highlight="7, 15,18">
  
== Using Java Annotation ==
+
public class OpenSessionInViewListener implements ExecutionInit, ExecutionCleanup {
The benefit of using Java annotation instead of Hibernate mapping file is that no additional file is required. Simply add Java annotation on your Java class to tell Hibernate about the mappings.
+
private static final Log log = Log.lookup(OpenSessionInViewListener.class);
  
<source lang="java" >
+
public void init(Execution exec, Execution parent) {
package events;
+
if (parent == null) { //the root execution of a servlet request
 +
log.debug("Starting a database transaction: "+exec);
 +
HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
 +
}
 +
}
  
import java.util.Date;
+
public void cleanup(Execution exec, Execution parent, List errs) {
 +
if (parent == null) { //the root execution of a servlet request
 +
if (errs == null || errs.isEmpty()) {
 +
log.debug("Committing the database transaction: "+exec);
 +
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
 +
} else {
 +
final Throwable ex = (Throwable) errs.get(0);
 +
rollback(exec, ex);
 +
}
 +
}
 +
}
  
import javax.persistence.Column;
+
private void rollback(Execution exec, Throwable ex) {
import javax.persistence.Entity;
+
try {
import javax.persistence.GeneratedValue;
+
if (HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().isActive()) {
import javax.persistence.GenerationType;
+
log.debug("Trying to rollback database transaction after exception:"+ex);
import javax.persistence.Id;
+
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
import javax.persistence.Table;
+
}
 +
} catch (Throwable rbEx) {
 +
log.error("Could not rollback transaction after exception! Original Exception:\n"+ex, rbEx);
 +
}
 +
}
 +
}
 +
</source>
 +
* Line 7: Call <code>getCurrentSession()</code> to get a contextual session. <ref> [http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#architecture-current-session Hibernate Reference Documentation/contextual session]</ref>
  
@Entity
+
Add configuration in zk.xml to make the listener work.
@Table(name="EVENTS")
 
public class Event {
 
    private Long id;
 
    private String title;
 
    private Date date;
 
  
    @Id
+
'''Configure listener in zk.xml'''
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
+
<source lang="xml">
    @Column(name = "EVENT_ID")
+
<zk>
    public Long getId() {
+
<listener>
        return id;
+
<listener-class>org.zkoss.reference.developer.hibernate.web.OpenSessionInViewListener</listener-class>
    }
+
</listener>
    private void setId(Long id) {
+
</zk>
        this.id = id;
 
    }
 
    @Column(name = "EVENT_DATE")
 
    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;
 
    }
 
}
 
 
</source>
 
</source>
  
* @Entity declares this class as a persistence object
+
=== DAO Implementation ===
* @Table(name = "EVENTS") annotation tells that the entity is mapped with the table EVENTS in the database
+
 
* @Column element is used to map the entities with the column in the database.
+
The listener begins and commits transactions keeping DAO's implementation simple. Just use the utility class to get current Hibernate session to perform the operation.
* @Id element defines the mapping from that property to the primary key column.
+
 
 +
'''Simple DAO implementation'''
 +
<source lang="java" highlight="4">
 +
public class OrderDao {
 +
 
 +
public List<Order> findAll() {
 +
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
 +
Query query = session.createQuery("select o from Order as o");
 +
List<Order> result = query.list();
 +
return result;
 +
}
 +
 
 +
/**
 +
* rollback is handled in filter.
 +
* @param newOrder
 +
* @return
 +
* @throws HibernateException
 +
*/
 +
public Order save(Order newOrder) throws HibernateException{
 +
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
 +
session.save(newOrder);
 +
session.flush();
 +
return newOrder;
 +
}
 +
}
 +
</source>
 +
 
 +
Finally, we can use the DAO class in a ViewModel to manipulate domain objects.
 +
 
 +
'''Use DAO in a ViewModel'''
 +
<source lang="java">
 +
 
 +
public class OrderViewModel {
  
== Using the Mapping Files ==
+
private OrderDao orderDao = new OrderDao();
# Simply create <tt>Event.hbm.xml</tt> for the persistent class <tt>Event.java</tt>.
 
  
<source lang="xml" >
+
private List<Order> orders ;
<?xml version="1.0"?>
+
private Order selectedItem;
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+
"[http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd]">
+
@Init
 +
public void init(){
 +
orders = orderDao.findAll();
 +
if (!orders.isEmpty()){
 +
setSelectedItem(orders.get(0));
 +
}
 +
}
  
<hibernate-mapping>
+
//omit setter and getter for brevity
    <class name="events.Event" table="EVENTS">
+
}
        <id name="id" column="EVENT_ID">
 
            <generator class="native"/>
 
        </id>
 
        <property name="date" type="timestamp" column="EVENT_DATE"/>
 
        <property name="title"/>
 
    </class>
 
</hibernate-mapping>
 
 
</source>
 
</source>
  
# Place this <tt>Event.hbm.xml</tt> in the directory called <tt>src</tt> in the development folder, and its correct package. (ex.<tt>$myApp/WEB-INF/src/event/Event.hbm.xml</tt>)
+
==Spring-based DAO ==
  
=Creating the Hibernate Configuration File=
+
With Spring provided dependency injection and ORM support, here our efforts are simplified quite substantially. We'll demonstrate one typical usage in non-managed environment. To apply ''session-per-request'' pattern, we can use Spring provided <code>OpenSessionInViewFilter</code> instead of writing our own one. According to the suggestion in Spring Reference Documentation 3.1, using plain Hibernate API to implement a DAO is the current recommended usage pattern. In the DAO, we can also easily retrieve <code>SessionFactory</code> by dependency inject without any utility classes (<code>HibernateUtil</code>). Besides, declarative transaction management and rollback rule also reduces our work. The following are the the related code snippets.
  
The next step is to setup Hibernate to use a database. HSQL DB, a java-based SQL DBMS, can be downloaded from the HSQL DB website([http://hsqldb.org/ http://hsqldb.org/]).
+
=== Configuration ===
 +
The minimal dependencies you need are Hibernate-Core, Spring-Web, Spring-ORM, and Cglib:
  
# Unzip it to a directory, say c:/hsqldb. (note: For hsqldb, the directory must be at the same partition of your web application, and under root directory)
+
<source lang='xml'>
# Copy hsqldb.jar to <tt>$myApp/WEB-INF/lib</tt>
 
# Open a command box and change to c:/hsqldb/lib
 
# In the command prompt, execute<tt> java -cp hsqldb.jar org.hsqldb.Server</tt>
 
  
After installing the database, we have to setup Hibernate Configuration file. Create <tt>hibernate.cfg.xml</tt> in the directory called <tt>src</tt> in the development folder(ex.<tt>$myApp/WEB-INF/src/hibernate.cfg.xml</tt>). Copy the following lines into your <tt>hibernate.cfg.xml</tt>. It depends on how you map your Java objects. (Note: if you're using eclipse, hibernate.cfg.xml should be placed under src folder of Java Resources)
+
  <dependency>
 +
  <groupId>org.hibernate</groupId>
 +
  <artifactId>hibernate-core</artifactId>
 +
  <version>4.0.0.Final</version>
 +
  </dependency>
 +
  <dependency>
 +
  <groupId>org.springframework</groupId>
 +
  <artifactId>spring-web</artifactId>
 +
  <version>3.1.2.RELEASE</version>
 +
  </dependency>
 +
  <dependency>
 +
  <groupId>org.springframework</groupId>
 +
  <artifactId>spring-orm</artifactId>
 +
  <version>3.1.2.RELEASE</version>
 +
  </dependency>
 +
  <dependency>
 +
  <groupId>cglib</groupId>
 +
  <artifactId>cglib</artifactId>
 +
  <version>2.2</version>
 +
  </dependency>
 +
</source>
  
== Using Java Annotation ==
+
We use basic configuration for Spring.
  
<source lang="xml" >
+
'''Spring configuration for Hibernate'''
 +
<source lang="xml" highlight='40'>
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
+
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
+
xmlns:context="http://www.springframework.org/schema/context"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+
xmlns:tx="http://www.springframework.org/schema/tx"
 +
xsi:schemaLocation="
 +
          http://www.springframework.org/schema/beans
 +
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 +
          http://www.springframework.org/schema/context
 +
          http://www.springframework.org/schema/context/spring-context-3.0.xsd
 +
          http://www.springframework.org/schema/tx
 +
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 +
 
 +
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
 +
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
 +
<property name="url" value="jdbc:hsqldb:file:data/store" />
 +
<property name="username" value="sa" />
 +
<property name="password" value="" />
 +
</bean>
 +
<!--
 +
hibernate.current_session_context_class=thread
 +
-->
 +
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
 +
<property name="dataSource" ref="dataSource" />
 +
<property name="hibernateProperties">
 +
<value>
 +
hibernate.dialect=org.hibernate.dialect.HSQLDialect
 +
hibernate.hbm2ddl.auto=crate
 +
hibernate.show_sql=true
 +
hibernate.connection.pool_size=5
 +
hibernate.connection.autocommit=false
 +
</value>
 +
</property>
 +
<property name="annotatedClasses">
 +
<list>
 +
<value>org.zkoss.reference.developer.hibernate.domain.Order</value>
 +
<value>org.zkoss.reference.developer.hibernate.domain.OrderItem</value>
 +
</list>
 +
</property>
 +
</bean>
 +
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
 +
<property name="sessionFactory" ref="sessionFactory" />
 +
</bean>
 +
 
 +
<tx:annotation-driven />
 +
 +
<!-- Scans for application @Components to deploy -->
 +
<context:component-scan base-package="org.zkoss.reference.developer.hibernate" />
 +
</beans>
 +
 
 +
</source>
 +
* Line 40: For Hibernate 3.x, some package names should be changed to <code>org.springframework.orm.hibernate3.*</code>, e.g. <code>org.springframework.orm.hibernate3.HibernateTransactionManager</code>.
 +
 
 +
=== OpenSessionInViewFilter ===
  
<hibernate-configuration>
+
Spring already provides a <b>OpenSessionInViewFilter</b> to solve lazy loading in web views problems. This filter makes Hibernate Sessions available via the current thread, which will be auto-detected by Spring's transaction managers. For detailed description and usage, please refer to Spring's Javadoc.
    <session-factory>
 
    <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) -->
+
''' Configure OpenSessionInViewFilter in web.xml'''
        <property name="connection.pool_size">1</property>
+
<source lang="xml" highlight="3">
  
        <!-- Enable Hibernate's automatic session context management -->
+
<filter>
        <property name="current_session_context_class">thread</property>
+
<filter-name>OpenSessionInViewFilter</filter-name>
 +
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
 +
</filter>
 +
<filter-mapping>
 +
<filter-name>OpenSessionInViewFilter</filter-name>
 +
<url-pattern>/*</url-pattern>
 +
</filter-mapping>
 +
</source>
  
        <!-- Disable the second-level cache -->
 
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
 
  
        <!-- Echo all executed SQL to stdout -->
+
=== DAO Implementation ===
        <property name="show_sql">true</property>
 
  
        <!-- Drop and re-create the database schema on startup -->
+
For a Spring-powered DAO, we can use injected <code>SessionFactory</code> and <code>@Transactional</code> to perform persistence operation.
        <property name="hbm2ddl.auto">create</property>
 
 
 
        <mapping class="events.Event" />
 
    </session-factory>
 
</hibernate-configuration>
 
</source>  
 
  
== Using the Mapping Files ==
+
'''DAO empowered by Spring'''
<source lang="xml" >
+
<source lang="java" highlight="4,5,7,9,15,23">
<?xml version='1.0' encoding='utf-8'?>
 
<!DOCTYPE hibernate-configuration PUBLIC
 
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
 
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
 
  
<hibernate-configuration>
+
@Repository
 +
public class SpringOrderDao {
  
    <session-factory>
+
@Autowired
 +
private SessionFactory sessionFactory;
 +
 +
@Transactional(readOnly=true)
 +
public List<Order> queryAll() {
 +
Session session = sessionFactory.getCurrentSession();
 +
Query query = session.createQuery("select o from Order as o");
 +
List<Order> result = query.list();
 +
return result;
 +
}
  
        <!-- Database connection settings -->
+
@Transactional
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
+
public Order save(Order newOrder){
        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
+
Session session = sessionFactory.getCurrentSession();
        <property name="connection.username">sa</property>
+
session.save(newOrder);
        <property name="connection.password"></property>
+
session.flush();
 +
return newOrder;
 +
}
  
        <!-- JDBC connection pool (use the built-in) -->
+
//omit other codes
        <property name="connection.pool_size">1</property>
+
}
 +
</source>
  
        <!-- SQL dialect -->
 
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
 
  
        <!-- Enable Hibernate's automatic session context management -->
+
To use this Spring-based DAO in a composer (or a ViewModel), ZK provides several ways like variable resolvers. Please refer to [[ZK Developer's Reference/Integration/Middleware Layer/Spring]].
        <property name="current_session_context_class">thread</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 -->
+
org.zkoss.zkplus.hibernate.OpenSessionInViewListener
        <property name="hbm2ddl.auto">create</property>
+
* deprecated because it depends on deprecated HibernateUtil.
  
        <mapping resource="events/Event.hbm.xml"/>
+
org.zkoss.zkplus.spring.SpringTransactionSynchronizationListener
 +
* not support Spring 3.x (member fields changed in TransactionSynchronizationManager)
 +
* OpenSessionInViewListener in Spring context needs this listener
 +
* used when ZK event thread is enabled. Because event thread is a new thread, it needs to access parent thread's ThreadLocal variable of Spring.
 +
* It uses reflection to get member field variable from org.springframework.transaction.support.TransactionSynchronizationManager
  
    </session-factory>
+
-->
  
</hibernate-configuration>
+
= Lazy Initialization Issue among AU Requests =
 +
 
 +
Although we apply ''open session in view'' pattern to keep a session open when a page is rendered, this makes ZUL access a lazy-loaded collection without errors '''when you visit the ZUL at first request'''. However, if your event handling methods (or command methods) accesses a lazy-loaded property of a detached object, you will still get a <code>LazyInitializationException</code> when a user interacts with the ZUL. This is because even though the filter opens a session for each request, the detached objects don't attach to the session automatically. <ref> The reason is explained in a [https://community.jboss.org/wiki/OpenSessionInView#Why_cant_Hibernate_just_load_objects_on_demand Hibernate article "Open Session in View"] </ref> The two DAO implementations demonstrated previously both have this problem and there are two solutions for it.
 +
 
 +
# set fetching strategy to '''eagerly fetch'''.
 +
# Re-query the detached object manually.
 +
 
 +
If you are not dealing with large amount of data, you can choose the first solution by simply changing your fetching strategy to eager for properties.
 +
 
 +
We will use an example to demonstrate the second solution. The following is a "Order Viewer" system. The upper ''Listbox'' contains a list of orders and the lower ''Grid'' contains the details of items that are selected in the order. One order may contain many order items (one-to-many mapping), and we set order items collection to lazy fetching . When we select an order, the ''Grid'' displays detailed items of the selected order which means accessing a lazy-loaded collection.
 +
 
 +
<source lang="java" highlight="11,12">
 +
@Entity
 +
@Table(name="orders")
 +
public class Order {
 +
 
 +
@Id
 +
@GeneratedValue(strategy = GenerationType.IDENTITY)
 +
private Long id;
 +
private String status = PROCESSING;
 +
private String description;
 +
 
 +
@OneToMany(mappedBy="orderId", fetch=FetchType.LAZY)
 +
private List<OrderItem> items = new ArrayList<OrderItem>();
 +
 
 +
//other codes...
 +
}
 
</source>
 
</source>
  
We continue with how to create a class to handle data accessing jobs.
 
  
=Creating DAO Objects=
+
By default, we set the ''Listbox'' selection on the first row. When ZUL accesses the selected order's lazy-loaded items collection, Hibernate can load it successfully with the help of open-session-in-view filter because session is still open. However, if we click the second row which also accesses a detached <code>Order</code> object's items collection, we should re-load the object with Hibernate session or we'll get <code>LazyInitializationException</code>.
 +
 
 +
[[File:Hibernate-beginning.png | 500px | center]]
 +
 
 +
<div style="text-align:center">[https://code.google.com/p/zkbooks/source/browse/trunk/developersreference/integration.hibernate/src/main/webapp/homemade/order.zul source of above screen]</div>
 +
 
 +
In order to reload a detached object, we pass the selected order to DAO object and reload it.
 +
 
 +
'''Reload selected object'''
 +
<source lang="java" highlight="11, 16">
  
For ease of maintenance, we used to create another Java class to handle data accessing jobs.
+
public class OrderViewModel {
  
# Create <tt>EventDAO.java</tt>
+
private OrderDao orderDao = new OrderDao();
  
<source lang="java" >
+
private List<Order> orders ;
package events;
+
private Order selectedItem;
 +
 +
@Init
 +
public void init(){
 +
orders = orderDao.findAll();
 +
setSelectedItem(orders.get(0));
 +
}
  
import java.util.Date;
+
public Order getSelectedItem() {
import java.util.List;
+
if (selectedItem!=null){
 +
selectedItem = orderDao.reload(selectedItem);
 +
}
 +
return selectedItem;
 +
}
  
import org.hibernate.Session;
+
//omit other methods for brevity
import org.zkoss.zkplus.hibernate.HibernateUtil;
+
}
  
public class EventDAO {
+
</source>
    Session currentSession() {
+
* Line 11: Initialize the ''Listbox'' selection with the <code>Order</code> object at index 0 of <code>orders</code>.
        return HibernateUtil.currentSession();
+
* Line 16: Re-query the <code>selectedItem</code>.
    }
 
    public void saveOrUpdate(Event anEvent, String title, Date date) {
 
        Session sess =  currentSession();
 
        anEvent.setTitle(title);
 
        anEvent.setDate(date);
 
        sess.saveOrUpdate(anEvent);
 
    }
 
    public void delete(Event anEvent) {
 
        Session sess =  currentSession();
 
        sess.delete(anEvent);
 
    }
 
    public Event findById(Long id) {
 
        Session sess =  currentSession();
 
        return (Event) sess.load(Event.class, id);
 
    }
 
    public List findAll() {
 
        Session sess =  currentSession();
 
        return sess.createQuery("from Event").list();
 
    }
 
}
 
</source>  
 
  
# You have to compile the Java source, and place the class file in a directory called <tt>classes</tt> in the Web development folder, and in its correct package. (ex.<tt>$myApp/WEB-INF/classes/event/EventDAO.class</tt>)
 
  
=Accessing Persistence Objects in ZUML Page=
+
We use <code>Session.load()</code> to re-query the <code>Order</code> object with its id, this newly-retrieved object still has an open Hibernate session. Then when we access the lazy-loaded collection (<code>items</code>) in the ZUL, Hibernate can retrieve the collection for us. After doing so, we can eliminate <code>LazyInitializationException</code>.  
To access persistence objects in ZUML page is simple, simply declare a persistence object, and uses it to get data from database.  
 
  
# Create a <tt>event.zul</tt> in the root directory of web development. (ex. <tt>$myApp/event.zul</tt>)
+
'''Re-attach to a session'''
 +
<source lang="java" highlight="11">
  
<source lang="xml" >
+
public class OrderDao {
<zk>
 
<zscript><![CDATA[
 
import java.util.Date;
 
import java.text.SimpleDateFormat;
 
import events.Event;
 
import events.EventDAO;
 
  
//fetch all allEvents from database
+
//...
List allEvents = new EventDAO().findAll();
 
  
]]></zscript>
+
/**
<listbox id="lbxEvents">               
+
* Initialize lazy-loaded collection.
    <listhead>
+
* @param order
        <listheader label="Title" width="200px"/>
+
* @return
        <listheader label="Date" width="100px"/>
+
*/
    </listhead>
+
public Order reload(Order order){
    <listitem forEach="${allEvents}" value="${each}">
+
return (Order)HibernateUtil.getSessionFactory().getCurrentSession().load(Order.class,order.getId());
        <listcell label="${each.title}"/>
+
}
        <zscript>String datestr = new SimpleDateFormat("yyyy/MM/dd").format(each.date);</zscript>
+
}
        <listcell label="${datestr}"/>
 
    </listitem>
 
</listbox>
 
</zk>
 
 
</source>
 
</source>
  
# Open a browser and visit the ZUML page. (ex. <tt>http://localhost:8080/event/event.zul</tt>)
+
= Lazy Initialization Issue Under Render on Demand =
 +
 
 +
Some AU requests cannot be interfered by developers,such as a "[[ZK Developer's Reference/Performance Tips/Listbox, Grid and Tree for Huge Data/Turn on Render on Demand|Render On Demand]]" request where the rendering request is handled implicitly by a component itself. Under this situation, if a component needs to '''render some data from a detached object's lazy-loaded collection''', developers won't have a chance to reload detached objects during the rendering to avoid <code>LazyInitailizationException</code>.
 +
 
 +
Assume we have a ''Listbox'', it only displays 10 rows by default and it'a not in "page" mold. One of its columns display a lazy-loaded collection's size (<code>each.items.size()</code> )of an <code>Order</code> object.
 +
 
 +
'''Listbox that accesses lazy-loaded property'''
 +
<source lang="xml" highlight="4, 14">
 +
 
 +
<window title="" border="normal" width="600px" apply="org.zkoss.bind.BindComposer"
 +
viewModel="@id('vm') @init('org.zkoss.reference.developer.hibernate.vm.RodViewModel')">
 +
Contain a customized model that reload lazy-loaded collection from database
 +
<listbox model="@load(vm.orderListModel)" rows="10">
 +
<listhead>
 +
<listheader label="ID" width="50px" />
 +
<listheader label="Description" />
 +
<listheader label="Item Count" width="150px" />
 +
</listhead>
 +
<template name="model">
 +
<listitem>
 +
<listcell label="@load(each.id) " />
 +
<listcell label="@load(each.description)" />
 +
<listcell label="@load(each.items.size())" />
 +
</listitem>
 +
</template>
 +
</listbox>
 +
...
 +
</source>
 +
 
 +
If we just pass a Java <code>List</code> object to be the model of the ''Listbox'', when a user scrolls down to view other rows, ZK will send AU request to retrieve data for those un-rendered rows. ''Listbox'' will try to access lazy-loaded collection but objects in the list are already detached, and we will get <code>LazyInitailizationException</code>. During this rendering process, developers will not be notified and cannot interfere this process to reload detached objects. One solution is to '''implement a custom <javadoc>org.zkoss.zul.ListModel</javadoc>''' for the component.
 +
 
 +
We demonstrate 2 implementations here for your reference. The first one is simpler but less efficient; it re-queries each detached object when a component requests it.
 +
 
 +
'''Reloaded ListModel'''
 +
<source lang="java" highlight="1, 6,14">
 +
public class OrderListModel extends AbstractListModel<Order>{
 +
 
 +
private OrderDao orderDao;
 +
List<Order> orderList = new LinkedList<Order>();
 +
 +
public OrderListModel(List<Order> orders,OrderDao orderDao){
 +
this.orderList = orders;
 +
this.orderDao = orderDao;
 +
}
 +
 +
@Override
 +
public Order getElementAt(int index) {
 +
//throw a runtime exception if orderDao does not find target object
 +
Order renewOrder = orderDao.reload(orderList.get(index));
 +
return renewOrder;
 +
}
 +
 
 +
@Override
 +
public int getSize() {
 +
return orderList.size();
 +
}
 +
}
 +
</source>
 +
* Line 1: We extends <javadoc>org.zkoss.zul.AbstractListModel</javadoc> to build our list model for it handles selection for us, but we have to implement Order's <code>equals()</code> and <code>hashCode()</code>.
 +
* Line 14: Re-query the detached object by its id and return a persistent one.
 +
 
 +
 
 +
The second one is more complicated but more efficient; it re-queries a one page size data each time and stores as a cache in an execution. If the cache has the object that the component requests, it returns the one in cache without re-querying again.
 +
 
 +
'''Lived ListModel'''
 +
<source lang="java" highlight="16, 42">
 +
public class LiveOrderListModel extends AbstractListModel<Order>{
 +
 
 +
private OrderDao orderDao;
 +
private Integer totalSize;
 +
private int pageSize = 30;
 +
private final String CACHE_KEY= LiveOrderListModel.class+"_cache";
 +
 +
public LiveOrderListModel(OrderDao orderDao){
 +
this.orderDao = orderDao;
 +
}
 +
 
 +
/**
 +
* query one page size of entity for one execution a time.
 +
*/
 +
@Override
 +
public Order getElementAt(int index) {
 +
Map<Integer, Order> cache = getCache();
 +
 
 +
Order targetOrder = cache.get(index);
 +
if (targetOrder == null){
 +
//if cache doesn't contain target object, query a page starting from the index
 +
List<Order> pageResult = orderDao.findAll(index, pageSize);
 +
int indexKey = index;
 +
for (Order o : pageResult ){
 +
cache.put(indexKey, o);
 +
indexKey++;
 +
}
 +
}else{
 +
return targetOrder;
 +
}
 +
 
 +
//get the target after query from database
 +
targetOrder = cache.get(index);
 +
if (targetOrder == null){
 +
//if we still cannot find the target object from database, there is inconsistency in the database
 +
throw new HibernateException("Element at index "+index+" cannot be found in the database.");
 +
}else{
 +
return targetOrder;
 +
}
 +
}
 +
 
 +
private Map<Integer, Order> getCache(){
 +
Execution execution = Executions.getCurrent();
 +
//we only reuse this cache in one execution to avoid accessing detached objects.
 +
//our filter opens a session during a HTTP request
 +
Map<Integer, Order> cache = (Map)execution.getAttribute(CACHE_KEY);
 +
if (cache == null){
 +
cache = new HashMap<Integer, Order>();
 +
execution.setAttribute(CACHE_KEY, cache);
 +
}
 +
return cache;
 +
}
 +
 +
@Override
 +
public int getSize() {
 +
if (totalSize == null){
 +
totalSize = orderDao.findAllSize().intValue();
 +
}
 +
return totalSize;
 +
}
 +
}
 +
</source>
 +
* Line 16: If the cache doesn't contain target <code>Order</code>, we query a one page size of <code>Order</code> starting from the index as passed <code>index</code> doesn't always increase sequentially.
 +
* Line 42: The <code>getElementAt(int)</code> will be invoked multiple times during an execution. In order to avoid using a cache of detached objects, we make the cache as an attribute of an execution which is dropped after being handled.
 +
 
 +
= Get Example Source Code =
 +
All source code used in this chapter can be found [https://github.com/zkoss/zkbooks/tree/master/developersreference/integration.hibernate here].
 +
 
 +
= Reference =
 +
<references/>
 +
 
  
=Version History=
 
{{LastUpdated}}
 
{| border='1px' | width="100%"
 
! Version !! Date !! Content
 
|-
 
| &nbsp;
 
| &nbsp;
 
| &nbsp;
 
|}
 
  
 
{{ZKDevelopersReferencePageFooter}}
 
{{ZKDevelopersReferencePageFooter}}

Latest revision as of 04:34, 5 February 2024


Overview

Due to object/relational paradigm mismatch [1], developers tend to use ORM (object/relational mapping) framework to convert object-oriented model to relational model and vice versa. Hibernate is the most popular ORM framework in Java world. We will talk about some integration topics in this chapter such as lazy initialization with Spring. If you haven't read about basic concepts and installation of Hibernate, please refer to Hibernate Dcumentation. The example we give in this chapter is based on Hibernate 4.0.0.final and Spring 3.1.2.RELEASE.

Integrate with Different DAO implementation

The Data Access Object (DAO) pattern is a good practice to implement a persistence layer. This pattern encapsulates data access codes written by Hibernate API from business tier. A DAO object exposes an interface to business object and performs persistence operation relating to a particular persistent entity.

According to Hibernate Reference Manual's suggestion [2], we should apply session-per-request pattern to manage sessions and transactions. This pattern needs an interceptor to open a contextual session with a transaction when a request is going to be handled and close the session before respond is sent to client (aka. open session in view pattern). A common implementation for page-based application is a servlet filter.

Applying this pattern also solves a common "LazyInitializationException" problem that most developers encounter when using lazy fetching strategy. In brief, Hibernate session is usually closed after a DAO object has performed an operation (a.k.a session-per-operation pattern). Those persistent objects become detached after the associated sessions are closed. If we access a detached object's lazy-loaded collection when rendering the view. We will get an error message like LazyInitializationException: no session or session was closed. For detailed explanation, please refer to the article "Open Session in View" on Hibernate community[3]. As we apply session-per-request pattern, a Hibernate session is kept open when a View is accessing lazy-loaded collection. Those objects queried by the Hibernate session becomes detached later (after the interceptor closes the Hibernate session), so the previously mentioned problem is resolved.


Homemade DAO

Here we introduce how to implement an DAO without other frameworks (e.g. Spring).

Configuration

The minimal Maven dependency you need is:

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.0.0.Final</version>
      <scope>compile</scope>
    </dependency>

Icon info.png Note: If you don't use Maven, please refer to Hibernate Reference Documentation to know which JAR file you need.


Utility Class

A simple way to implement a DAO is to control Hibernate sessions and transactions manually, hence we need a utility class to get SessionFactory. ZK's HibernateUtil has been deprecated since 6.0.2, you can write your own one according to the code example in Hibernate Reference Manual.[4] Here we provide a simple example.

Utility class to get SessionFactory

package org.zkoss.reference.developer.hibernate.dao;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
    private static SessionFactory sessionFactory;
    static {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

}

An Open Session Listener

For open session in view pattern, we need an interceptor to open sessions. In ZK, we need to intercept all requests including AU requests, so we implement ZK's Life Cycle Listener to achieve this. (Our listener's implementation is based on the filter mentioned by a Hibernate article "Open Session in View". [5].) This listener opens a session and begins a transaction at the beginning of an execution (ZK's HTTP request wrapper, then commits (or rollback) at the end of the execution.

Extracted from OpenSessionInViewListener

public class OpenSessionInViewListener implements ExecutionInit, ExecutionCleanup {
	private static final Log log = Log.lookup(OpenSessionInViewListener.class);

	public void init(Execution exec, Execution parent) {
		if (parent == null) { //the root execution of a servlet request
			log.debug("Starting a database transaction: "+exec);
			HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
		}
	}

	public void cleanup(Execution exec, Execution parent, List errs) {
		if (parent == null) { //the root execution of a servlet request
			if (errs == null || errs.isEmpty()) {
				log.debug("Committing the database transaction: "+exec);
				HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
			} else {
				final Throwable ex = (Throwable) errs.get(0);
				rollback(exec, ex);
			}
		}
	}

	private void rollback(Execution exec, Throwable ex) {
		try {
			if (HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().isActive()) {
				log.debug("Trying to rollback database transaction after exception:"+ex);
				HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
			}
		} catch (Throwable rbEx) {
			log.error("Could not rollback transaction after exception! Original Exception:\n"+ex, rbEx);
		}
	}
}
  • Line 7: Call getCurrentSession() to get a contextual session. [6]

Add configuration in zk.xml to make the listener work.

Configure listener in zk.xml

<zk>
	<listener>
		<listener-class>org.zkoss.reference.developer.hibernate.web.OpenSessionInViewListener</listener-class>
	</listener>
</zk>

DAO Implementation

The listener begins and commits transactions keeping DAO's implementation simple. Just use the utility class to get current Hibernate session to perform the operation.

Simple DAO implementation

public class OrderDao {

	public List<Order> findAll() {
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		Query query = session.createQuery("select o from Order as o");
		List<Order> result = query.list();
		return result;
	}

	/**
	 * rollback is handled in filter.
	 * @param newOrder
	 * @return
	 * @throws HibernateException
	 */
	public Order save(Order newOrder) throws HibernateException{
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.save(newOrder);
		session.flush();
		return newOrder;
	}
}

Finally, we can use the DAO class in a ViewModel to manipulate domain objects.

Use DAO in a ViewModel

public class OrderViewModel {

	private OrderDao orderDao = new OrderDao();

	private List<Order> orders ;
	private Order selectedItem;
	
	@Init
	public void init(){
		orders = orderDao.findAll();
		if (!orders.isEmpty()){
			setSelectedItem(orders.get(0));
		}
	}

	//omit setter and getter for brevity
}

Spring-based DAO

With Spring provided dependency injection and ORM support, here our efforts are simplified quite substantially. We'll demonstrate one typical usage in non-managed environment. To apply session-per-request pattern, we can use Spring provided OpenSessionInViewFilter instead of writing our own one. According to the suggestion in Spring Reference Documentation 3.1, using plain Hibernate API to implement a DAO is the current recommended usage pattern. In the DAO, we can also easily retrieve SessionFactory by dependency inject without any utility classes (HibernateUtil). Besides, declarative transaction management and rollback rule also reduces our work. The following are the the related code snippets.

Configuration

The minimal dependencies you need are Hibernate-Core, Spring-Web, Spring-ORM, and Cglib:

  	<dependency>
  		<groupId>org.hibernate</groupId>
  		<artifactId>hibernate-core</artifactId>
  		<version>4.0.0.Final</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-web</artifactId>
  		<version>3.1.2.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-orm</artifactId>
  		<version>3.1.2.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>cglib</groupId>
  		<artifactId>cglib</artifactId>
  		<version>2.2</version>
  	</dependency>

We use basic configuration for Spring.

Spring configuration for Hibernate

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/tx
		   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

 	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
		<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
		<property name="url" value="jdbc:hsqldb:file:data/store" />
		<property name="username" value="sa" />
		<property name="password" value="" />
	</bean>
	<!-- 
		hibernate.current_session_context_class=thread
 	-->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<value>
				hibernate.dialect=org.hibernate.dialect.HSQLDialect
				hibernate.hbm2ddl.auto=crate
				hibernate.show_sql=true
				hibernate.connection.pool_size=5
				hibernate.connection.autocommit=false
			</value>
		</property>
		<property name="annotatedClasses">
			<list>
				<value>org.zkoss.reference.developer.hibernate.domain.Order</value>
				<value>org.zkoss.reference.developer.hibernate.domain.OrderItem</value>
			</list>
		</property>
	</bean>
	<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<tx:annotation-driven />
	
	<!-- Scans for application @Components to deploy -->
	<context:component-scan base-package="org.zkoss.reference.developer.hibernate" />
</beans>
  • Line 40: For Hibernate 3.x, some package names should be changed to org.springframework.orm.hibernate3.*, e.g. org.springframework.orm.hibernate3.HibernateTransactionManager.

OpenSessionInViewFilter

Spring already provides a OpenSessionInViewFilter to solve lazy loading in web views problems. This filter makes Hibernate Sessions available via the current thread, which will be auto-detected by Spring's transaction managers. For detailed description and usage, please refer to Spring's Javadoc.

Configure OpenSessionInViewFilter in web.xml

	<filter>
		<filter-name>OpenSessionInViewFilter</filter-name>
		<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>OpenSessionInViewFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


DAO Implementation

For a Spring-powered DAO, we can use injected SessionFactory and @Transactional to perform persistence operation.

DAO empowered by Spring

@Repository
public class SpringOrderDao {

	@Autowired
	private SessionFactory sessionFactory;
	
	@Transactional(readOnly=true)
	public List<Order> queryAll() {
		Session session = sessionFactory.getCurrentSession();
		Query query = session.createQuery("select o from Order as o");
		List<Order> result = query.list();
		return result;
	}

	@Transactional
	public Order save(Order newOrder){
		Session session = sessionFactory.getCurrentSession();
		session.save(newOrder);
		session.flush();
		return newOrder;
	}

	//omit other codes
}


To use this Spring-based DAO in a composer (or a ViewModel), ZK provides several ways like variable resolvers. Please refer to ZK Developer's Reference/Integration/Middleware Layer/Spring.


Lazy Initialization Issue among AU Requests

Although we apply open session in view pattern to keep a session open when a page is rendered, this makes ZUL access a lazy-loaded collection without errors when you visit the ZUL at first request. However, if your event handling methods (or command methods) accesses a lazy-loaded property of a detached object, you will still get a LazyInitializationException when a user interacts with the ZUL. This is because even though the filter opens a session for each request, the detached objects don't attach to the session automatically. [7] The two DAO implementations demonstrated previously both have this problem and there are two solutions for it.

  1. set fetching strategy to eagerly fetch.
  2. Re-query the detached object manually.

If you are not dealing with large amount of data, you can choose the first solution by simply changing your fetching strategy to eager for properties.

We will use an example to demonstrate the second solution. The following is a "Order Viewer" system. The upper Listbox contains a list of orders and the lower Grid contains the details of items that are selected in the order. One order may contain many order items (one-to-many mapping), and we set order items collection to lazy fetching . When we select an order, the Grid displays detailed items of the selected order which means accessing a lazy-loaded collection.

@Entity
@Table(name="orders")
public class Order {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String status = PROCESSING;
	private String description;

	@OneToMany(mappedBy="orderId", fetch=FetchType.LAZY)
	private List<OrderItem> items = new ArrayList<OrderItem>();

	//other codes...
}


By default, we set the Listbox selection on the first row. When ZUL accesses the selected order's lazy-loaded items collection, Hibernate can load it successfully with the help of open-session-in-view filter because session is still open. However, if we click the second row which also accesses a detached Order object's items collection, we should re-load the object with Hibernate session or we'll get LazyInitializationException.

Hibernate-beginning.png

In order to reload a detached object, we pass the selected order to DAO object and reload it.

Reload selected object

public class OrderViewModel {

	private OrderDao orderDao = new OrderDao();

	private List<Order> orders ;
	private Order selectedItem;
	
	@Init
	public void init(){
		orders = orderDao.findAll();
		setSelectedItem(orders.get(0));
	}

	public Order getSelectedItem() {
		if (selectedItem!=null){
			selectedItem = orderDao.reload(selectedItem);
		}
		return selectedItem;
	}

	//omit other methods for brevity
}
  • Line 11: Initialize the Listbox selection with the Order object at index 0 of orders.
  • Line 16: Re-query the selectedItem.


We use Session.load() to re-query the Order object with its id, this newly-retrieved object still has an open Hibernate session. Then when we access the lazy-loaded collection (items) in the ZUL, Hibernate can retrieve the collection for us. After doing so, we can eliminate LazyInitializationException.

Re-attach to a session

public class OrderDao {

	//...

	/**
	 * Initialize lazy-loaded collection.
	 * @param order
	 * @return
	 */
	public Order reload(Order order){
		return (Order)HibernateUtil.getSessionFactory().getCurrentSession().load(Order.class,order.getId());
	}
}

Lazy Initialization Issue Under Render on Demand

Some AU requests cannot be interfered by developers,such as a "Render On Demand" request where the rendering request is handled implicitly by a component itself. Under this situation, if a component needs to render some data from a detached object's lazy-loaded collection, developers won't have a chance to reload detached objects during the rendering to avoid LazyInitailizationException.

Assume we have a Listbox, it only displays 10 rows by default and it'a not in "page" mold. One of its columns display a lazy-loaded collection's size (each.items.size() )of an Order object.

Listbox that accesses lazy-loaded property

	<window title="" border="normal" width="600px" apply="org.zkoss.bind.BindComposer"
		viewModel="@id('vm') @init('org.zkoss.reference.developer.hibernate.vm.RodViewModel')">
		Contain a customized model that reload lazy-loaded collection from database
		<listbox model="@load(vm.orderListModel)" rows="10">
			<listhead>
				<listheader label="ID" width="50px" />
				<listheader label="Description" />
				<listheader label="Item Count" width="150px" />
			</listhead>
			<template name="model">
				<listitem>
					<listcell label="@load(each.id) " />
					<listcell label="@load(each.description)" />
					<listcell label="@load(each.items.size())" />
				</listitem>
			</template>
		</listbox>
...

If we just pass a Java List object to be the model of the Listbox, when a user scrolls down to view other rows, ZK will send AU request to retrieve data for those un-rendered rows. Listbox will try to access lazy-loaded collection but objects in the list are already detached, and we will get LazyInitailizationException. During this rendering process, developers will not be notified and cannot interfere this process to reload detached objects. One solution is to implement a custom ListModel for the component.

We demonstrate 2 implementations here for your reference. The first one is simpler but less efficient; it re-queries each detached object when a component requests it.

Reloaded ListModel

public class OrderListModel extends AbstractListModel<Order>{

	private OrderDao orderDao;
	List<Order> orderList = new LinkedList<Order>();
	
	public OrderListModel(List<Order> orders,OrderDao orderDao){
		this.orderList = orders;
		this.orderDao = orderDao;
	}
	
	@Override
	public Order getElementAt(int index) {
		//throw a runtime exception if orderDao does not find target object
		Order renewOrder = orderDao.reload(orderList.get(index));
		return renewOrder;
	}

	@Override
	public int getSize() {
		return orderList.size();
	}
}
  • Line 1: We extends AbstractListModel to build our list model for it handles selection for us, but we have to implement Order's equals() and hashCode().
  • Line 14: Re-query the detached object by its id and return a persistent one.


The second one is more complicated but more efficient; it re-queries a one page size data each time and stores as a cache in an execution. If the cache has the object that the component requests, it returns the one in cache without re-querying again.

Lived ListModel

public class LiveOrderListModel extends AbstractListModel<Order>{

	private OrderDao orderDao;
	private Integer totalSize;	
	private int pageSize = 30;
	private final String CACHE_KEY= LiveOrderListModel.class+"_cache";
	
	public LiveOrderListModel(OrderDao orderDao){
		this.orderDao = orderDao;
	}

	/**
	 * query one page size of entity for one execution a time. 
	 */
	@Override
	public Order getElementAt(int index) {
		Map<Integer, Order> cache = getCache();

		Order targetOrder = cache.get(index);
		if (targetOrder == null){
			//if cache doesn't contain target object, query a page starting from the index
			List<Order> pageResult = orderDao.findAll(index, pageSize);
			int indexKey = index;
			for (Order o : pageResult ){
				cache.put(indexKey, o);
				indexKey++;
			}
		}else{
			return targetOrder;
		}

		//get the target after query from database
		targetOrder = cache.get(index);
		if (targetOrder == null){
			//if we still cannot find the target object from database, there is inconsistency in the database
			throw new HibernateException("Element at index "+index+" cannot be found in the database.");
		}else{
			return targetOrder;
		}
	}

	private Map<Integer, Order> getCache(){
		Execution execution = Executions.getCurrent();
		//we only reuse this cache in one execution to avoid accessing detached objects.
		//our filter opens a session during a HTTP request
		Map<Integer, Order> cache = (Map)execution.getAttribute(CACHE_KEY);
		if (cache == null){
			cache = new HashMap<Integer, Order>();
			execution.setAttribute(CACHE_KEY, cache);
		}
		return cache;
	}
	
	@Override
	public int getSize() {
		if (totalSize == null){
			totalSize = orderDao.findAllSize().intValue();
		}
		return totalSize; 
	}
}
  • Line 16: If the cache doesn't contain target Order, we query a one page size of Order starting from the index as passed index doesn't always increase sequentially.
  • Line 42: The getElementAt(int) will be invoked multiple times during an execution. In order to avoid using a cache of detached objects, we make the cache as an attribute of an execution which is dropped after being handled.

Get Example Source Code

All source code used in this chapter can be found here.

Reference




Last Update : 2024/02/05

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.