Integrate ZK5 with Spring 3 and Hibernate

From Documentation


DocumentationSmall Talks2011DecemberIntegrate ZK5 with Spring 3 and Hibernate
Integrate ZK5 with Spring 3 and Hibernate

Author
Vincent Jian, Engineer, Potix Corporation
Date
December 12, 2011
Version
ZK5+/6, 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. Database schema.jpg

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.
    Spring jar.jpg
  • Add Hibernate jar files
  • Download jar files from Hibernate and copy the following jar files into WEB-INF/lib directory.
    Hibernate jar.jpg

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.
    <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>
    
    b) Add dependencies of Spring and Hibernate
    <!-- 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 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 manages 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 only provide the service for injecting DAOs, in fact, Spring also provides a DAO support object (HibernateDaoSupport) which is a base DAO that your DAOs can use. This Spring base DAO provides 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 exactly how developers are shielded 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 needs the business object (CompanyManager) that supports 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 the save and delete method with default setting (readOnly = false) because the transaction requires a change of data in the database. On the other hand, the prefix get* methods are used only for retrieving data from the 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>

By inspecting applicationContext.xml, notice how we are injecting companyManager and the reason the operations work in getCompanyList() is because the applicationContext injects the companyDAO bean into a setDao() method.

All of this is good enough, however so far, we have only described the configurations in Hibernate + Spring. But what happened to ZK? ZK should be agnostic to the data layer and 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 data and business layer resources. However how is the view layer able to communicate with 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 ServiceLocator which is an object responsible for loading and inspecting the applicationContext.xml file providing 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 purpose of 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 to do the job. It is therefore your ZK object that needs to use the ServiceLocator to get manager objects to perform desired business operations.

Step 4: Implement User Interfaces with ZK

Here we design the View with two parts - left and right. The left part will display a list of all companies in a listbox and CRUD operation UIs in a grid. The right part will display a list of all contacts based on selected company in a listbox and CRUD operation UIs 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 of a company list and company CRUD operations. Here, we find that data binding is used for CRUD operations 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; }
}

Notice that line 31, before sending selected company information to contact controller, we need to bind the company object into Hibernate session because the Hibernate session will close once it successfully loads data from the database.

To make the lazy-loading of one-to-many relation in Hibernate to work, we suggest not to use data binding in collection data as it becomes harder to bind company objects 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 consists of a 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. By 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.