GWT + Hibernate + Dispatch

в 10:43, , рубрики: Dispatch, Google Web Toolkit, GWT, hibernate, java, метки: , , ,

При создании портала крайне часто основной функцией BackEnd является работа с БД. Как правило сейчас никто не использует обычные и не очень удобные для написания Statement’ы, а в место этого используют ORM фреймворки. В случае с GWT этим фреймворком скорее всего становится Hibernate, так как на сервере у нас Java. Мне далеко не первому пришла в голову мысль о том, что крайне удобно и просто было бы передавать не отдельные данные а целиком POJO(персистентные объекты) используемые Hibernate. Вот я создал свой сервис, возвращающий на клиентскую часть POJO. POJO пронаследовал от Serializable. Запускаю приложение и получаю вот такое вот исключение:

Caused by: com.google.gwt.user.client.rpc.SerializationException: Type 'org.hibernate.collection.PersistentSet' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized.
at com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy.validateSerialize(StandardSerializationPolicy.java:83)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:591

Не сразу понятно что пошло не так. Дело в том, что как только объект становится персистентным, в нем происходит ряд изменений, необходимых для работы Hibernate. Например обычные Set меняются на PersistentSet. Из-за этого браузер уже никак не может десериализовать отправленный ему объект, ни при каких условиях.

Способы интеграции

Как я уже говорил, я далеко не первый кто захотел передавать POJO на клиентскую сторону, и как следствие не первый кто столкнулся с подобной проблемой. В интернете можно найти три основных способа решения данной проблемы и интеграции Hibernate с GWT.
• DTO(Data Transfer Objects) – вы можете создать свои объекты для переноса данных на клиентскую часть приложения не являющиеся персистентными, но в остальном содержащими все те же поля что и в используемых вами POJO.
• Использование библиотеки Dozer для маппинга объектов – Эта библиотека автоматически копирует данные из одного объекта в другой. Для ее корректной работы Объекты должны содержать одинаковые поля и геттеры и сеттеры именованные в соответствии с JavaBean нотацией.
• Использование библиотеки Gilead так же известной как hibernate4gwt.
Первый вариант достаточно тривиален, и я не стану его рассматривать, как впрочем и второй, который является просто чуть более продвинутым первым. Если вас заинтересовал второй вариант всю необходимую информацию о библиотеке вы можете найти на официальном сайте dozer.sourceforge.net/

Gilead

Куда более интересен последний вариант, о котором я сейчас расскажу. Glead — открытая библиотека, предоставляющая возможность свободно передавать POJO между клиентской и серверной частью приложения. При использовании данной библиотеки на сервере между Java-сервером и клиентом появляется прослойка называемая PersistentBeanManager. При передаче объекта от сервера клиенту PBM автоматически клонирует POJO в аналогичный клиентский объект, при этом игнорируя все LazyInitiaslization и заменяя еще не проинициализированные поля на NULL. При этом сохраняя POJO в памяти. На обратном ходе PBM мерджит клиентский объект с сохраненным в нем POJO и передает на сервер POJO с уже обновленными данными.
Для использования этого невероятно удобного чуда необходимо добавить библиотеку проект. Подключить нужный модуль в gwt.xml, пронаследовать всеми POJO LightEntity, а сервлетом вместо RemoteServiceServlet PersistantRemoteService. Возможно стоило бы просто описать этот процесс и закончить на этом статью, но как вы видите в названии статьи присутствует слово Dispatch. Дело в том что очень многие с целью пере использования кода пользуются паттерном программирования Dispatch с библиотекой gwt-dispatch основанной на внедрении зависимостей с помощью библиотеки Guice. При использовании такой библиотеки в вашем проекте нет класса сервиса. Вы пользуетесь стандартным сервисом GuiceStandardDispatchServlet, который зашит внутри библиотеки gwt-dispatch. Для того что бы обойти это я слегка пересобрал эту библиотеку.

Gilead + Dispatch

Для начала необходимо пересобрать библиотеку gwt-dispatch. Для это используем какой-нибудь клиент меркуриала и забираем сурсы с репозитория. hg clone https://code.google.com/p/gwt-dispatch/
Создаем проект в IDE в котором вам удобней, куда все это и запихиваем. Я использую Eclipse. Далее что бы проект собрался я добавил в .classpath следующие библиотеки
• gilead-core-1.3.2.1839.jar
• gliead-hibernate-1.3.2.1839.jar
• glead4gwt-1.3.2.1839.jar
• gin-1.5-post-gwt-2.2.jar
• guice-3.0.jar
• hibernate.jar
• org.springframework.beans-3.1.2.REALEASE.jar
• org.springframework.context-3.1.2.REALEASE.jar
• org.springframework.web-3.1.2.REALEASE.jar
Открываем класс net.customware.gwt.dispatch.server.standard.AbstractStandardDispatchServlet и вносим в него несколько изменений для корректной работы нашей библиотеки:

public abstract class AbstractStandardDispatchServlet extends
		PersistentRemoteService implements StandardDispatchService {

	public AbstractStandardDispatchServlet() {
		HibernateUtil hibernateUtil = new HibernateUtil();

		SessionFactory sf;
		Configuration conf = new Configuration();
		conf.configure();
		conf.getProperties();
		sf = conf.buildSessionFactory();

		hibernateUtil.setSessionFactory(sf);

		PersistentBeanManager persistentBeanManager = new PersistentBeanManager();
		persistentBeanManager.setPersistenceUtil(hibernateUtil);
		
		StatelessProxyStore sps = new StatelessProxyStore();
		sps.setProxySerializer(new GwtProxySerialization());
		persistentBeanManager.setProxyStore(sps);

		setBeanManager(persistentBeanManager);
	}

	public <R extends Result> R execute(Action<R> action)
			throws DispatchException {
		try {
			Dispatch dispatch = getDispatch();

			if (dispatch == null)
				throw new ServiceException("No dispatch found for servlet '"
						+ getServletName()
						+ "' . Please verify your server-side configuration.");

			return dispatch.execute(action);
		} catch (RuntimeException e) {
			log("Exception while executing " + action.getClass().getName()
					+ ": " + e.getMessage(), e);
			throw new ServiceException(e);
		}
	}

	/**
	 * 
	 * @return The Dispatch instance.
	 */
	protected abstract Dispatch getDispatch();

}

После того как все изменения внесены можем спокойно собирать библиотеку и добавлять ее в проект(или же можно просто подменить .class файл в старой). Так же необходим добавить в проект следующие библиотеки
• beanlib-5.0.5.jar
• beanlib-hibernate-5.0.5.jar
• com.springsource.net.sf.cglib-2.2.0.jar

Включаем в gwt.xml gilead чтобы клиентская сторона знала о наших LightEntity

<inherits name="net.sf.gilead.Gilead4Gwt" />

И наследуем всеми POJO LightEntity как показано ниже

@Entity
@Table(name = "CARD")
public class Card extends LightEntity implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "CARDID", columnDefinition = "BIGINT NOT NULL AUTO_INCREMENT")
	private long cardId;

	@Column(name = "CARDNUMBER")
	private String cardNumber;

	@ManyToOne
	@JoinColumn(name = "USERID", insertable = false, updatable = false, nullable = false)
	private User user;

	@Column(name = "ACTIVATED")
	private Integer activated;

	public Long getCardId() {
		return cardId;
	}

	public void setCardId(Long cardId) {
		this.cardId = cardId;
	}

	public String getCardNumber() {
		return cardNumber;
	}

	public void setCardNumber(String cardNumber) {
		this.cardNumber = cardNumber;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public Integer getActivated() {
		return activated;
	}

	public void setActivated(Integer activated) {
		this.activated = activated;
	}

}

В общем все. Теперь вы можете спокойной передавать ваши персистентные объекты между серверной и клиентской частями GWT приложения при этом продолжая использовать Dispatch.

Автор: bazarnazar

Поделиться

* - обязательные к заполнению поля