Integrate ZK5 with Spring 3 and Hibernate
Vincent Jian, Engineer, Potix Corporation
December 12, 2011
ZK5+, Spring 3, Hibernate 3.6
Introduction
ZK + Spring + Hibernate is one of the most popular combination used among ZK users. Contributor Fernando De Leon wrote an article on how to integrate ZK 2.1.1, Spring and Hibernate back in 2006. However, since ZK has evolved and improved greatly, this article will inherit and extend the previous article to illustrates how you can use the latest ZK 5/6, Spring 3, and Hibernate 3.6 to create a simple project that loads data from MySQL database.
Step 1: MySQL Database Schema
First, we create a database in MySQL called "support" with two tables - company and contact. Here company and contact have a "one-to-many" relationship.
Step 2: Create a Web Application Project
This could be done either by ZK Studio or by Maven.
By ZK Studio
- Create a ZK project
- If you have already installed ZK Studio plugin in Eclispe, refer here to create a simple ZK project.
- Add Spring 3.0.6 jar files
- Download jar files from SpringSource Community and copy the following jar files into WEB-INF/lib directory.
- Add Hibernate jar files
- Download jar files from Hibernate and copy the following jar files into WEB-INF/lib directory.
By Maven
- Create a Maven Project
- If you prefer to using maven, refer here to create a ZK project with maven.
- Modify the pom.xml file to add Spring and Hibernate jar files.
- a) Add version properties and repositories of Spring and Hibernate.
- b) Add dependencies of Spring and Hibernate
<properties>
<zk.version>5.0.9</zk.version>
<org.springframework.version>3.0.6.RELEASE</org.springframework.version>
<hibernate.version>3.6.8.Final</hibernate.version>
</properties>
...
<repository>
<id>repository.springframework.maven.release</id>
<name>Spring Framework Maven Release Repository</name>
<url>http://maven.springframework.org/release</url>
</repository>
<repository>
<id>Hibernate repository</id>
<url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
</repository>
<!-- Spring dependency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Hibernate dependency -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- MySql dependency -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
Step 3: Configure Hibernate and Spring
Setup relative configuration files
In WEB-INF folder of the project we create a Spring configuration file called applicationContext.xml, this file defines the data source (line 9), session factory (line 16) and DAOs (line 29) that are needed to manage Hibernate resources and manage the business objects.
<!-- applicationContext.xml -->
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/support" />
<property name="user" value="username" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- set other Hibernate properties in hibernate.cfg.xml file -->
<property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- for using annotation @Transaction in DAOs -->
<tx:annotation-driven />
<bean id="companyDAO" class="org.zkoss.model.dao.CompanyDAO">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="companyManager" class="org.zkoss.service.CompanyManagerImpl">
<property name="companyDAO" ref="companyDAO" />
</bean>
<bean id="contactDAO" class="org.zkoss.model.dao.ContactDAO">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="contactManager" class="org.zkoss.service.ContactManagerImpl">
<property name="contactDAO" ref="contactDAO" />
</bean>
</beans>
The configurations above manage Hibernate’s connection to MySQL database by dataSource bean and sessionFactory bean. It also manages the injection of DAOs which are needed to perform different operations on the table (e.g., CRUD operations). Other properties needed in hibernate.cfg.xml are as follow:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<mapping resource="Company.hbm.xml" />
<mapping resource="Contact.hbm.xml" />
</session-factory>
</hibernate-configuration>
Notice that Spring is also able to manage the business layer, review the company manager declaration. The company manager is a business object that uses a company DAO to define different business rules.
For completeness here are Hibernate resources. Create two hibernate bean mapping XML files under the classpath folder.
Company.hbm.xml describes the Company bean.
<hibernate-mapping package="org.zkoss.model.bean">
<class name="Company" table="company">
<id name="idcompany" column="idcompany" type="integer">
<generator class="increment" />
</id>
<property name="name" column="name" type="string"/>
<property name="country" column="country" type="string"/>
<set name="contacts">
<key column="companyId"/>
<one-to-many class="org.zkoss.model.bean.Contact"/>
</set>
</class>
</hibernate-mapping>
Contact.hbm.xml describes the Contact bean.
<?xml version="1.0" encoding="UTF-8"?>
<hibernate-mapping package="org.zkoss.model.bean">
<class name="Contact" table="contact">
<id name="idcontact" column="idcontact" type="integer">
<generator class="increment" />
</id>
<property name="name" column="name" type="string" />
<property name="email" column="email" type="string" />
<many-to-one name="company" column="companyId" class="org.zkoss.model.bean.Company" outer-join="true" />
</class>
</hibernate-mapping>
Create relative Bean and DAO class
Create Company.java to represent the Company bean in memory.
public class Company implements Serializable {
private Integer idcompany;
private String name;
private String country;
public Company() {}
// getters and setters
}
And Contact.java to represent the Contact bean in memory.
public class Contact implements Serializable {
private Integer idcontact;
private Company company;
private String name;
private String email;
public Contact() {}
// getters and setters
}
The purpose of Spring is not just to provide the service for injecting DAOs. In fact Spring provides with a DAO support object (HibernateDaoSupport) which is a base DAO that your DAOs can use. This Spring base DAO provide getHibernateTemplate() API that enables you to perform simple CRUD operations in a simple manner.
public class CompanyDAO extends HibernateDaoSupport {
public void saveOrUpdate(Company company) throws DataAccessException {
getHibernateTemplate().saveOrUpdate(company);
}
public void delete(Company company) throws DataAccessException {
getHibernateTemplate().delete(company);
}
public Company find(Class<Company> clazz, Integer id) throws DataAccessException {
Company company = (Company) getHibernateTemplate().load(clazz, id);
return company;
}
public List<Company> findAll(Class<Company> clazz) throws DataAccessException {
List<Company> list = getHibernateTemplate().find("from " + clazz.getName());
return list;
}
}
public class ContactDAO extends HibernateDaoSupport {
// similar with CompanyDAO.java
}
The CompanyDAO inherits HibernateDaoSupport which is a DAO support object provided by Spring. It is this HibernateDaoSupport that has a setter called setSessionFactory (SessionFactory sessionFactory). And this is how you are abstracted from the complexity of opening and closing a Hibernate session. Now that we have a DAO which we can do simple CRUD operations on our Company table we need the business object (CompanyManager) that will support our business rules. Note that Spring also manages the injection of the CompanyManager object.
In keeping with well defined OOP principles we define a Company manager interface.
public interface CompanyManager {
public boolean save(Company company);
public boolean delete(Company company);
public Company getCompany(Integer id);
public List<Company> getCompanyList();
}
public interface ContactManager {
// similar with CompanyManager.java
}
And the implementation:
public class CompanyManagerImpl implements CompanyManager {
private CompanyDAO companyDAO;
public CompanyDAO getCompanyDAO() {
return companyDAO;
}
public void setCompanyDAO(CompanyDAO companyDAO) {
this.companyDAO = companyDAO;
}
@Transactional
public boolean save(Company company) {
try {
companyDAO.saveOrUpdate(company);
return true;
} catch (DataAccessException e) {
return false;
}
}
@Transactional
public boolean delete(Company company) {
try {
companyDAO.delete(company);
return true;
} catch (DataAccessException e) {
return false;
}
}
@Transactional(readOnly = true)
public Company getCompany(Integer id) {
try {
return companyDAO.find(Company.class, id);
} catch (DataAccessException e) {
return null;
}
}
@Transactional(readOnly = true)
public List<Company> getCompanyList() {
try {
return companyDAO.findAll(Company.class);
} catch (DataAccessException e) {
return null;
}
}
}
public class ContactManagerImpl implements ContactManager {
// similar with CompanyManagerImpl.java
}
Here we use @Transactional annotation for save and delete method with default setting (readOnly = false) because the transaction have to change data in database. On the other hand, the prefix get* methods are used only for retrieving data from database, thus the annotation is @Transactional(readOnly=true).
If annotation is not applicable, these settings can also be defined in applicationContext.xml.
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
Inspect the applicationContext.xml notice how we are injecting the companyManager and the reason the operations work in getCompanyList() is because the applicationContex injects the companyDAO bean into a setDao() method. All of this is good enough, however so far we had described the Hibernate + Spring configuration. But whatever happened to ZK? ZK should be agnostic to the data layer and it should not know about business rules, it should only use them. We have seen how Spring’s applicationContext.xml file is the central hub for managing the data and business layer resources. However how is the view layer able to communicate to the business layer? For this we use a special object that ZK can call to load up a manager. This becomes the link between the presentation layer and the business layer. We use a ServiceLocator object. ServiceLocator is responsible in loading up the applicationContext.xml file and thus inspect this file and provide a service to locate different managers. Therefore ServiceLocator would use many managers.
public class ServiceLocator {
private ServiceLocator() {}
public static Session getHibernateSession() {
return ((SessionFactory) SpringUtil.getBean("sessionFactory", SessionFactory.class)).getCurrentSession();
}
public static CompanyManager getCompanyManager() {
return (CompanyManager) SpringUtil.getBean("companyManager", CompanyManager.class);
}
public static ContactManager getContactManager() {
return (ContactManager) SpringUtil.getBean("contactManager", ContactManager.class);
}
}
The service locator is to create singleton objects, as the loading up of the applicationContext.xml needs to happen only once! Here we use SpringUtil object provided by ZK that do the job. It is therefore your ZK object that needs to use the ServiceLocator to get manager objects to perform the desire business operations.
Step 4: Implement User Interfaces with ZK
Here we design the view with left and right parts. The left part will display a list of all companies in a listbox and CRUD operation UI in a grid. The right part will display a list of all contacts based by selected company in a listbox and CRUD operation UI in a grid. We will first develop the index.zul file which will contain two DIV components that consist of listbox and grid.
Company List View
The left part is for company list and company CRUD operation. Here, we can find out the data binding is used for CRUD operation since it is convenient to load and save bean data.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<div id="companyDiv" apply="org.zkoss.view.ctrl.CompanyCtrl">
<vbox>
<listbox id="companyList" width="450px" height="300px">
<listhead>
<listheader width="50px" label="id" />
<listheader width="250px" label="name" sort="auto(name)" />
<listheader width="140px" label="country" sort="auto(country)" />
</listhead>
</listbox>
<grid id="editCompanyGrid" width="450px">
<auxhead>
<auxheader colspan="2" label="Add/Edit Company Info" />
</auxhead>
<columns visible="false">
<column width="50px" />
<column width="250px" />
<column width="140px" />
</columns>
<rows>
<row>
<cell><intbox width="50px" value="@{companyDiv$composer.company.idcompany}" disabled="true" /></cell>
<cell><textbox width="250px" value="@{companyDiv$composer.company.name}" /></cell>
<cell><textbox width="140px" value="@{companyDiv$composer.company.country}" /></cell>
</row>
<row>
<cell colspan="4" align="center">
<button id="createCompany" label="add" /><space spacing="10px"/>
<button id="updateCompany" label="update" disabled="true"/><space spacing="10px"/>
<button id="deleteCompany" label="delete" disabled="true"/>
<button id="resetCompany" label="reset" />
</cell>
</row>
</rows>
</grid>
</vbox>
</div>
Apply left DIV component to ZK MVC pattern. Create CompanyCtrl.java for loading data and CRUD operation.
public class CompanyCtrl extends GenericForwardComposer {
// data binding create/edit Company bean
private AnnotateDataBinder binder;
private Company _company = new Company();
// wire component as member fields
private Listbox companyList;
// other component in ZUL file
// get singleton CompanyManager object for CRUD operation
private CompanyManager manager = ServiceLocator.getCompanyManager();
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
binder = (AnnotateDataBinder) page.getAttribute("binder");
companyList.setModel(new ListModelList(manager.getCompanyList()));
companyList.setItemRenderer(new CompanyListRenderer());
}
public void onClick$resetCompany() {
// process reset view
}
//set selection to edit data
public void onSelect$companyList() {
_company = (Company) companyList.getSelectedItem().getValue();
binder.loadComponent(editCompanyGrid);
createCompany.setDisabled(true);
updateCompany.setDisabled(false);
deleteCompany.setDisabled(false);
// used for Hibernate lazy-loading
_company = (Company) ServiceLocator.getHibernateSession().merge(_company);
Event event = new Event("onLoad", page.getFellow("contactDiv"), _company);
EventQueues.lookup("loadContact", EventQueues.DESKTOP, true).publish(event);
}
public void onClick$createCompany() throws InterruptedException {
// process create
}
public void onClick$updateCompany() throws InterruptedException {
// process update
}
public void onClick$deleteCompany() throws InterruptedException {
// process delete
}
private ListModelList getModel() {
return (ListModelList) companyList.getModel();
}
public Company getCompany() { return _company; }
public void setCompany(Company company) { _company = company; }
}
Here we notice the line 31, before sending selected company information to contact controller, we need to bind the company object into Hibernate session because the Hibernate session is closed once it loads data from database successfully.
To make Hiberante one-to-many lazy-loading works easily as described above, we suggest not to use data binding in collection data because it is hard to bind company object to Hibernate session. We use model-renderer method instead to render data:
public class CompanyListRenderer implements ListitemRenderer {
public void render(Listitem item, Object data) throws Exception {
Company company = (Company) data;
item.setValue(company);
new Listcell(String.valueOf(company.getIdcompany())).setParent(item);
new Listcell(company.getName()).setParent(item);
new Listcell(company.getCountry()).setParent(item);
}
}
Contact List View
The right part is for contact list based on selected Company and contact CRUD operation.
<div id="contactDiv" apply="org.zkoss.view.ctrl.ContactCtrl" visible="false">
<vbox>
<listbox id="contactList" width="450px" height="300px">
<listhead>
<listheader width="50px" label="id" />
<listheader width="140px" label="name" sort="auto(name)" />
<listheader width="250px" label="email" sort="auto(email)" />
</listhead>
</listbox>
<grid id="editContactGrid" width="450px">
<auxhead>
<auxheader colspan="2" label="Add/Edit Contact Info" />
</auxhead>
<columns visible="false">
<column width="50px" />
<column width="140px" />
<column width="250px" />
</columns>
<rows>
<row>
<cell><intbox width="50px" value="@{contactDiv$composer.contact.idcontact}" disabled="true" /></cell>
<cell><textbox width="140px" value="@{contactDiv$composer.contact.name}" /></cell>
<cell><textbox width="250px" value="@{contactDiv$composer.contact.email}" /></cell>
</row>
<row>
<cell colspan="4" align="center">
<button id="createContact" label="add" /><space spacing="10px"/>
<button id="updateContact" label="update" /><space spacing="10px"/>
<button id="deleteContact" label="delete" />
<button id="resetContact" label="reset" />
</cell>
</row>
</rows>
</grid>
</vbox>
</div>
Apply right DIV component to ZK MVC pattern. Create ContactCtrl.java for loading data and CRUD operation.
public class ContactCtrl extends GenericForwardComposer {
// data binding create/edit Contact bean
private AnnotateDataBinder binder;
private Contact _contact = new Contact();
// wire component as member fields
private Listbox contactList;
// ... other components in ZUL
// get singleton ContactManager object for CRUD operation
private ContactManager manager = ServiceLocator.getContactManager();
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
binder = (AnnotateDataBinder) page.getAttribute("binder");
EventQueues.lookup("loadContact", EventQueues.DESKTOP, true).subscribe(new EventListener() {
public void onEvent(Event event) throws Exception {
event.getTarget().setVisible(true);
Company company = (Company) event.getData();
contactList.setModel(setContactModel(company));
}
});
contactList.setItemRenderer(new ContactListRenderer());
}
private ListModel setContactModel(Company company) {
List<Contact> contacts = new ArrayList<Contact>();
if(company.getContacts() != null)
contacts.addAll(company.getContacts());
return new ListModelList(contacts);
}
private ListModelList getModel() {
return (ListModelList) contactList.getModel();
}
public void onClick$resetContact() {
contactList.clearSelection();
_contact = new Contact();
binder.loadComponent(editContactGrid);
createContact.setDisabled(false);
updateContact.setDisabled(true);
deleteContact.setDisabled(true);
}
//set selection to edit data
public void onSelect$contactList() {
_contact = (Contact) contactList.getSelectedItem().getValue();
System.out.println(_contact);
binder.loadComponent(editContactGrid);
createContact.setDisabled(true);
updateContact.setDisabled(false);
deleteContact.setDisabled(false);
}
public void onClick$createContact(ForwardEvent event) throws InterruptedException {
// process create
}
public void onClick$updateContact() throws InterruptedException {
// process update
}
public void onClick$deleteContact() throws InterruptedException {
// process delete
}
public Contact getContact() { return _contact; }
public void setContact(Contact contact) { _contact = contact; }
}
Summary
This article is an updated version of Hibernate + Spring + ZK smalltalk written by Fernando De Leon. Following this article you should be able to build a simple CRUD application that consists of ZK 5/6, Spring 3, and Hibernate.
Download
Sample source code and war file used in the article is available here.
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |