GWT + Hibernate + Dispatch

При создании портала крайне часто основной функцией 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 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.RELEASE.jar
• org.springframework.context-3.1.2.RELEASE.jar
• org.springframework.web-3.1.2.RELEASE.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.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 8

    +1
    Знакомая ситуация. В свое время писал велосипед использующий PropertyDescriptor'ы. Там рекурсивно обходились поля в hibernate объекте и заменялись HibernateProxy на реализацию.
    result = (T) ((HibernateProxy) entity)
    					.getHibernateLazyInitializer()
    					.getImplementation();
    


    Кстати, SerializationException съело кучу времени и нервов в свое время. Хорошо когда оно выскакивает с описанием, как в вашем случае. Но часто вместо описания было просто null и приходилось вручную лопатить объекты и искать, где забыл добавить конструктор без аргументов, где забыл унаследовать Serializable а где забыл сделать внутренний тип публичным.
      0
      Незнал о существовании такого способа — спасибо, позновательно. Единственное что в «Способы интеграции» первые два все таки это один, разве что перекладываение автоматизировано с помощью dozer mappings. Ах да — еще dozer умеет перекладывать не только одноименные поля как вы описали — он достаточно гибок в конфигурировании.
        0
        Про Dozer я в курсе. Просто решил не тратить время на разъяснения, так как статья все-таки о другом.
        Возможно первые два способа и являются по механике одним. Но с точки зрения затрат времени на реализацию, структуры и возможности конфигурации код получается очень разный. По этому я и решил их разделить.
        0
        Жалко, что это LightEntity — класс. Если бы оно было trait — можно было бы не ломать свою систему наследования.
          0
          Ну тут уж только сам Gilead ковырять, или искать аналоги, о которых впрочем я не слышал. В принципе Gilead опенсурсный, но не думаю что будет просто пропатчить его так как нужно. Если найдется решение, пишите.
            0
            На самом деле заменить один абстрактный класс на котлинский или скаловский trait никакого труда не составит — но как это будет работать с GWT — я не знаю. Если это client-side — никак работать не будет ( а это, видимо, клиентсайд), потому что GWT использует свой компилятор Java.
              0
              Объекты передающиеся между сервером и клиентом компилятся на обоих сторонах. В данном случае как байткод для сервреной части, так и в JavaScript для клиентской, так что ты прав, такой способ не будет работать
          0
          Gilead уже давно не поддерживается разработчиком, поэтому настоятельно рекомендую отнестись с опаской к его применению в проекте, особенно, если это новый проект и вы вольны в выборе технологий. Лично у меня унаследованный проект и мне не раз приходилось лазить в исходниках, и перекомпилировать части Gilead.

          К тому же, список возможных вариантов далеко не полон. Я вскользь интересовался темой (т.к. в планах есть отказ от Gilead в проекте), и навскидку могу предложить еще два варианта передачи DTO: Calling REST from GWT и RequestFactory. Лично я пока смотрю в сторону RequestFactory, т.к. это встроенная в GWT технология, поэтому не требуется ничего лишнего. На практике пока не пробовал.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое