Всем доброго времени суток. Вот решил поделиться тем, как можно объединить двух монстров Spring Framework и Hibernate с мощным j2ee фреймворком ZK. Для начала почему же именно ZK, а не GWT или вообще native jsp? Потому что (чисто субъективно) у ZK самая безболезненная интеграция с этими фреймворками, да и вообще проще я пока не встречал, что и вам попытаюсь доказать.
Мой пример будет прост, так как моя цель показать как эти все фреймворки заставить заработать, причем с наименьшей головной болью. В качестве базы данных возьмем Oracle. И напишем простое веб-приложение, которое будет отображать имена пользователей. Также, дабы показать мощь и простоту ZK, добавим немного функциональности, к примеру, удаление пользователей из системы и редактирование его имени.
Первое, что мы должны сделать — создать ZK project (опция появляется, только после установки плагина в Eclipse), или просто dynamic web project, разница в сгенерированных web.xml файле, в файлах index.zul, timeout.zul, zk.xml и в библиотеках фреймворка, которые автоматом будут добавлены.
Так как наше приложение будет строиться по MVC шаблону, то сначала опишем model. Создадим в папке src маппинг-таблицу под названием Person, которая должна быть у нас в схеме Oracle:
Следующим шагом определимся в том, что будем делать с данным классом: отображать коллекцию и редактировать имя пользователя. Опишем интерфейс для доступа к нашему классу:
и конечно же реализацию:
Как видно из кода класса PersonImpl, работу с транзакциями, коннект к базе мы возложили на плечи Spring, а все CRUD действия будет выполнять hibernate.
Придерживаясь идеологии MVC опишем контроллер. В ZK это делается несколькими способами, я опишу самый простой — наследованием от класса Window:
Теперь перейдем к файлам конфигурации. Начнем с контекста соединения к базе данных, а именно с настройки Connection Pool. Создадим в папке META-INF файл context.xml с таким текстом:
так как при маппинге к таблице Person мы использовали аннотации, то создадим файл hibernate.cfg.xml в папке WEB-INF, в котором укажем наш класс:
Создадим spring конфигурационный файл, в котором опишем пусть к контексту соединения с Oracle при помощи jndi, bean sessionFactory, конфигурацию hibernate и пакет, в который должен смотреть spring, для применения аннотаций. Создадим файл spring-config.xml в папке WEB-INF:
Теперь пришел черед web.xml. В нем, кроме настроек ZK, надо будет указать листенер для spring, и путь файлов конфигурации spring и hibernate:
Следующее звено — это view, опять-таки исходя из MVC терминологии. Опишем наше веб-приложение с zul разметкой (файл index.zul, путь -WebContent ):
Одно маленькое уточнение, в конфигурационном файле zk.xml, который должен находиться в папке WEB-INF, укажем класс, ответственный за сериализацию:
Вот теперь все, обратите внимание как легко, удобно и главное без промежуточных звеньев и костылей, можно работать с refcursor-ом, и вообще с базой данных.
А это минимальный список библиотек, необходимых для запуска приложения:
P.S. надеюсь у всех заведется, ну а если нет, то пишите, вышлю проект. И еще, если эта статья вам показалась интересной, и хотелось бы еще узнать либо про сам фреймворк ZK, либо про интеграции например со Spring Security, то дайте знать.
Мой пример будет прост, так как моя цель показать как эти все фреймворки заставить заработать, причем с наименьшей головной болью. В качестве базы данных возьмем Oracle. И напишем простое веб-приложение, которое будет отображать имена пользователей. Также, дабы показать мощь и простоту ZK, добавим немного функциональности, к примеру, удаление пользователей из системы и редактирование его имени.
Первое, что мы должны сделать — создать ZK project (опция появляется, только после установки плагина в Eclipse), или просто dynamic web project, разница в сгенерированных web.xml файле, в файлах index.zul, timeout.zul, zk.xml и в библиотеках фреймворка, которые автоматом будут добавлены.
Так как наше приложение будет строиться по MVC шаблону, то сначала опишем model. Создадим в папке src маппинг-таблицу под названием Person, которая должна быть у нас в схеме Oracle:
package com.sample.data;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "PERSON")
public class Person implements Serializable {
private static final long serialVersionUID = 4624748294368212545L;
@Id
private long id;
@Column(name = "Name")
private String personName;
public Person() {
}
public Person(long id, String personName) {
super();
this.id = id;
this.personName = personName;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
}
Следующим шагом определимся в том, что будем делать с данным классом: отображать коллекцию и редактировать имя пользователя. Опишем интерфейс для доступа к нашему классу:
package com.sample.service;
import java.util.List;
import com.sample.data.Person;
public interface IPerson
{
List<Person> findAllPerson();
boolean delete(Person pers);
boolean saveOrUpdate(Person person);
}
и конечно же реализацию:
package com.sample.service;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.sample.data.Person;
@Repository
@Transactional(readOnly = true)
public class PersonImpl implements IPerson
{
private static SessionFactory sessionFactory;
//получаем наш datasource
@Autowired
public void setSessionFactory(SessionFactory sessionFactory)
{
PersonImpl.sessionFactory = sessionFactory;
}
@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public List<Person> findAllPerson()
{
Session session = sessionFactory.getCurrentSession();
return (List<Person>) session.createQuery("from Person").list();
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public boolean delete(Person pers)
{
try
{
Session session = sessionFactory.getCurrentSession();
session.delete(pers);
return true;
} catch (Exception e)
{
return false;
}
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public boolean saveOrUpdate(Person person)
{
try
{
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(person);
return true;
} catch (Exception e)
{
return false;
}
}
}
Как видно из кода класса PersonImpl, работу с транзакциями, коннект к базе мы возложили на плечи Spring, а все CRUD действия будет выполнять hibernate.
Придерживаясь идеологии MVC опишем контроллер. В ZK это делается несколькими способами, я опишу самый простой — наследованием от класса Window:
package ui.component;
import java.util.List;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Toolbarbutton;
import org.zkoss.zul.Window;
import com.sample.data.Person;
import com.sample.service.IPerson;
public class PersonInfo extends Window
{
private static final long serialVersionUID = -6186588639173052172L;
// компонен для отоброжения наших persons в виде списка
private Listbox personListBox;
private Toolbarbutton tbbDelete;
private List<Person> personList;
private IPerson dao;
private Textbox tbPersonName;
private Toolbarbutton tbbSave;
// переопределим метод, который выполняется при первой загрузки страницы
public void onCreate()
{
// получаем наш контекст
ApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext((ServletContext) getDesktop().getWebApp().getNativeContext());
// получаем бин
dao = (IPerson) ctx.getBean("personImpl");
personList = dao.findAllPerson();
tbPersonName = (Textbox) this.getFellow("tbPersonName");
tbbSave = (Toolbarbutton) this.getFellow("tbbSave");
if (personList.size() > 0)
{
// получаем компонент из нашей веб-страницы
personListBox = (Listbox) this.getFellow("lbPerson");
tbbDelete = (Toolbarbutton) this.getFellow("tbbDelete");
// заполняем его данными
populateListBox();
}
}
private void populateListBox()
{
for (Person pers : personList)
{
personListBox.appendChild(new Listitem(pers.getPersonName(), pers));
}
}
//удаление пользователя из системы
@SuppressWarnings("unchecked")
public void onDeletePerson()
{
for (Listitem li : (List<Listitem>) personListBox.getItems())
{
if (li.isSelected())
{
if (dao.delete((Person) li.getValue()))
{
//удаляем также из коллекции
personList.remove((Person) li.getValue());
personListBox.removeChild(li);
}
}
}
}
// если пользователь выбран, то активируем кнопку удаления пользователя
public void onSelectLB()
{
if (personListBox.getSelectedCount() > 0)
{
tbbDelete.setDisabled(false);
tbbSave.setDisabled(true);
tbPersonName.setValue(((Person) personListBox.getSelectedItem().getValue()).getPersonName());
} else
{
tbbDelete.setDisabled(true);
}
}
// сохраняем изменения
public void onSave() throws InterruptedException
{
if (tbPersonName.getValue() != null && tbPersonName.getValue().trim().length() > 0)
{
Person pers = (Person) personListBox.getSelectedItem().getValue();
pers.setPersonName(tbPersonName.getValue().trim());
if (!dao.saveOrUpdate(pers))
{
Messagebox.show("Не удалось сохранить изменения в базу данных");
} else
{
personListBox.getSelectedItem().setLabel(pers.getPersonName());
personListBox.getSelectedItem().setValue(pers);
}
} else
{
Messagebox.show("Поле \"Имя\" не должно быть пустым");
}
}
}
Теперь перейдем к файлам конфигурации. Начнем с контекста соединения к базе данных, а именно с настройки Connection Pool. Создадим в папке META-INF файл context.xml с таким текстом:
<?xml version="1.0" encoding="UTF-8"?>
<!-- JDBC -->
<Context path="/db1" docBase="db1" debug="5" reloadable="true"
crossContext="true">
<Resource name="jdbc/taskdb" username="test" password="test"
url="jdbc:oracle:thin:@localhost:1521:test"
auth="Container" defaultAutoCommit="false" driverClassName="oracle.jdbc.driver.OracleDriver"
maxActive="2" maxIdle="10" maxWait="-1" timeBetweenEvictionRunsMillis="60000"
testOnBorrow="true" testWhileIdle="true" queryTimeout="20000"
validationQueryTimeout="2" initialSize="1" removeAbandoned="true"
removeAbandonedTimeout="60" logAbandoned="true" type="javax.sql.DataSource" />
</Context>
так как при маппинге к таблице Person мы использовали аннотации, то создадим файл hibernate.cfg.xml в папке WEB-INF, в котором укажем наш класс:
<?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>
<session-factory>
<mapping class="com.sample.data.Person" />
</session-factory>
</hibernate-configuration>
Создадим spring конфигурационный файл, в котором опишем пусть к контексту соединения с Oracle при помощи jndi, bean sessionFactory, конфигурацию hibernate и пакет, в который должен смотреть spring, для применения аннотаций. Создадим файл spring-config.xml в папке WEB-INF:
<?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">
<context:annotation-config />
<context:component-scan base-package="com.sample" />
<tx:annotation-driven transaction-manager="txManager" />
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/taskdb</value>
</property>
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="dataSource" ref="dataSource" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
</bean>
</beans>
Теперь пришел черед web.xml. В нем, кроме настроек ZK, надо будет указать листенер для spring, и путь файлов конфигурации spring и hibernate:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>zkSpringHibernate</display-name>
<listener>
<description>
Used to cleanup when a session is destroyed</description>
<display-name>ZK Session cleaner</display-name>
<listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
</listener>
<servlet>
<description>
The ZK loader for ZUML pages</description>
<servlet-name>zkLoader</servlet-name>
<servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
<init-param>
<param-name>update-uri</param-name>
<param-value>/zkau</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<description>
The asynchronous update engine for ZK</description>
<servlet-name>auEngine</servlet-name>
<servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zul</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>zkLoader</servlet-name>
<url-pattern>*.zhtml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>auEngine</servlet-name>
<url-pattern>/zkau/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.zul</welcome-file>
</welcome-file-list>
</web-app>
Следующее звено — это view, опять-таки исходя из MVC терминологии. Опишем наше веб-приложение с zul разметкой (файл index.zul, путь -WebContent ):
<?page title="ZK integration"?>
<window title="view person" border="normal" width="100%"
id="wndViewPerson" use="ui.component.PersonInfo">
<listbox id="lbPerson" hflex="1" vflex="1" checkmark="true"
emptyMessage="data not found" onSelect="wndViewPerson.onSelectLB()">
<listhead>
<listheader label="Имя" />
</listhead>
</listbox>
<hlayout>
<label value="Имя" />
<textbox width="150px" id="tbPersonName"
onChanging="tbbSave.setDisabled(false)" />
<toolbarbutton label="save" onClick="wndViewPerson.onSave()"
id="tbbSave" disabled="true" />
<toolbarbutton label="delete" disabled="true" id="tbbDelete"
onClick="wndViewPerson.onDeletePerson()" />
</hlayout>
</window>
Одно маленькое уточнение, в конфигурационном файле zk.xml, который должен находиться в папке WEB-INF, укажем класс, ответственный за сериализацию:
<?xml version="1.0" encoding="UTF-8"?>
<zk>
<system-config>
<ui-factory-class>
org.zkoss.zk.ui.http.SerializableUiFactory
</ui-factory-class>
</system-config>
</zk>
Вот теперь все, обратите внимание как легко, удобно и главное без промежуточных звеньев и костылей, можно работать с refcursor-ом, и вообще с базой данных.
А это минимальный список библиотек, необходимых для запуска приложения:
- antlr-2.7.7.jar
- aopalliance-1.0.jar
- bsh.jar
- commons-collections-3.1.jar
- commons-fileupload.jar
- commons-io.jar
- commons-logging-1.1.1.jar
- dom4j-1.6.1.jar
- hibernate3.jar
- hibernate-jpa-2.0-api-1.0.0.Final.jar
- javassist-3.12.0.GA.jar
- js.jar
- jta-1.1.jar
- ojdbc6.jar
- org.springframework.aop-3.1.0.M1.jar
- org.springframework.asm-3.1.0.M1.jar
- org.springframework.aspects-3.1.0.M1.jar
- org.springframework.beans-3.1.0.M1.jar
- org.springframework.context-3.1.0.M1.jar
- org.springframework.core-3.1.0.M1.jar
- org.springframework.expression-3.1.0.M1.jar
- org.springframework.instrument.tomcat-3.1.0.M1.jar
- org.springframework.instrument-3.1.0.M1.jar
- org.springframework.jdbc-3.1.0.M1.jar
- org.springframework.jms-3.1.0.M1.jar
- org.springframework.orm-3.1.0.M1.jar
- org.springframework.oxm-3.1.0.M1.jar
- org.springframework.transaction-3.1.0.M1.jar
- org.springframework.web.servlet-3.1.0.M1.jar
- org.springframework.web-3.1.0.M1.jar
- slf4j-api-1.6.0.jar
- zcommon.jar
- zcommons-el.jar
- zhtml.jar
- zk.jar
- zkplus.jar
- zkspring-core.jar
- zul.jar
- zweb.jar
P.S. надеюсь у всех заведется, ну а если нет, то пишите, вышлю проект. И еще, если эта статья вам показалась интересной, и хотелось бы еще узнать либо про сам фреймворк ZK, либо про интеграции например со Spring Security, то дайте знать.