GWT + Hibernate 5. Пример использования Hibernate 5 с Google Web Toolkit 2.7.0
Подключение Hibernate 5 к приложению GWT. Базовые настройки программы.
Рекомендую: GWT 2.7.0 + JPA 2.1 + Hibernate 5.
Используемые технологии
- GWT 2.7.0.
- Hibernate 5.0.1
- MySQL 5.6.25
- Intellij Idea 15
1 Описание задачи
Подключить фреймворк Hibernate к приложению, написанному на GWT (Google Web Toolkit). Рассмотреть необходимые зависимости и классы для получения данных из базы данных MySQL.
2. Структура проекта
2.1. Клиентская часть
Главный модуль (вход в приложение) — класс Main, его описание Main.gwt.xml и представление Main.html + Main.css.
Два интерфейса MainRpcService и MainRpcServiceAsync служат для связи клиентской и серверной части.
Единственное представление, которое будет добавлено в Main.html выбрано в качестве обычной формы аутентификации (два текстовых поля логин-пароль). Класс LoginViewImpl содержит описание графических компонентов, которые с помощью UIBinder описываются в LoginViewImpl.ui.xml. Интерфейс LoginView описывает к каким компонентам необходимо предоставить доступ из класса Main (два геттера для доступа к компонентам TextBox для ввода логина и пароля). Интерфейс LoginResources необходим для взаимодействия UiBinder и Login.css.
2.2. Серверная часть
В серверной части описан сервлет MainRpcServiceImpl в котором будет происходить работа с данными (получение из БД записей) и отправка их на клиентскую часть.
Класс HibernateUtil — инициализация org.hibernate.SessionFactory и Session для Hibernate.
2.3. Общая часть
В пакете Shared находятся сущности Enitity из БД (здесь используется только таблица Users).
3. Создание проекта GWT + Hibernate
3.1. Maven проект
Для начала создадим пустой проект maven.
3.2. pom.xml
Добавим зависимости в pom.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>ru.javastudy</groupId> <artifactId>gwtPersistence</artifactId> <version>1.0</version> <properties> <gwtVersion>2.7.0</gwtVersion> <hibernate-version>5.0.1.Final</hibernate-version> </properties> <dependencyManagement> <dependencies> <!-- ensure all GWT deps use the same version (unless overridden) --> <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt</artifactId> <version>${gwtVersion}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate-version}</version> </dependency> <!--driver for connection to MYSql database --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project> |
3.2.1
web.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>ru.javastudy.gwtPersistence.Main MainRpcService</servlet-name> <servlet-class>ru.javastudy.gwtPersistence.server.MainRpcServiceImpl</servlet-class> </servlet> <!--@see MainRpcService interface --> <servlet-mapping> <servlet-name>ru.javastudy.gwtPersistence.Main MainRpcService</servlet-name> <url-pattern>/gwtPersist/gwtPersistService</url-pattern> </servlet-mapping> </web-app> |
Объявлен один сервлет и маппинг. MainRpcServiceImpl — смотрите описание серверной части.
<url-pattern>/gwtPersist/gwtPersistService</url-pattern> — gwtPersist прописан в Main.gwt.xml, а gwtPersistService — прописан в интерфейсе MainRpcService.
3.3. Добавление фреймворков
Теперь добавим поддержку GWT и Hibernate. Для этого на заголовке приложения нажимаем правой кнопкой и выбираем Add Framework Support..
Зависимости библиотек должны отобразиться в списке при добавлении фреймворка как показано на скриншоте.
3.4 Подключение базы данных
Как подключить базу данных MySQL к Idea описано в Создание MySQL базы данных и подключение к IntelliJ IDEA. Дамп базы данных будет доступе в конце статьи.
4. Настройка Hibernate
Для работы Hibernate в проекте необходим обязательный файл настроек hibernate.cfg.xml. Если вы не создали его при добавлении поддержки хибернейт, то сделать это можно через настройки как показано на скриншоте.
Facets — плюсик — добавить xml. Отмечу, что по умолчанию он должен находится в папке resources (но это можно менять если очень нужно). Итак сами настройки:
hibernate.cfg.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//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="connection.url">jdbc:mysql://localhost:3306</property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <!-- ЭТИ СТРОЧКИ ПРОПАДУТ ПОСЛЕ АВТОГЕНЕРАЦИИ МАППИНГА НИЖЕ --> <property name="connection.username">root</property> <property name="connection.password">admin</property> <!-- Mapping --> <mapping class="ru.javastudy.gwtPersistence.shared.Document"/> <mapping class="ru.javastudy.gwtPersistence.shared.Goods"/> <mapping class="ru.javastudy.gwtPersistence.shared.Users"/> </session-factory> </hibernate-configuration> |
Указан драйвер подключения к БД (прописан в pom.xml). Указан путь к БД, имя_пароль. А так же прописаны классы сущности замаппиные на таблицы в базе данных (описаны ниже).
4.1. Инициализация Hibernate Session
HibernateUtil.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package ru.javastudy.gwtPersistence.server; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; /** * Created for JavaStudy.ru on 23.11.2015. */ public class HibernateUtil { private static SessionFactory sessionFactory = buildSessionFactory(); protected static SessionFactory buildSessionFactory() { // A SessionFactory is set up once for an application! final StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .configure() // configures settings from hibernate.cfg.xml .build(); try { sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory(); } catch (Exception e) { // The registry would be destroyed by the SessionFactory, but we had trouble building the SessionFactory // so destroy it manually. StandardServiceRegistryBuilder.destroy( registry ); } return sessionFactory; } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void shutdown() { // Close caches and connection pools getSessionFactory().close(); } } |
Стандартный код инициализации взят из мануала по Hibernate 5. Методы из этого класса вызываются на серверной части (описано ниже).
5. Маппинг таблиц в Java классы
Воспользуемся функционалом среды разработки для создания классов сущностей.
Вкладка Persistence — Generate Persistence Mapping — By Database Schema.
Далее выбираем что хотим маппить и нажимаем ок.
5.1. Сущность Entity
Здесь будет использована только одна таблица Users всего с тремя полями: id, name, password. Всё создано автоматически и не изменялось (добавлена информация в @Table).
Users.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package ru.javastudy.gwtPersistence.shared; import javax.persistence.*; import java.io.Serializable; /** * Created for JavaStudy.ru on 23.11.2015. */ @Entity @Table(name = "users", schema = "shop") public class Users implements Serializable{ private int id; private String name; private String password; @Id @Column(name = "id", nullable = false) public int getId() { return id; } public void setId(int id) { this.id = id; } @Basic @Column(name = "name", nullable = false, length = 50) public String getName() { return name; } public void setName(String name) { this.name = name; } @Basic @Column(name = "password", nullable = false, length = 50) public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Users users = (Users) o; if (id != users.id) return false; if (name != null ? !name.equals(users.name) : users.name != null) return false; if (password != null ? !password.equals(users.password) : users.password != null) return false; return true; } @Override public int hashCode() { int result = id; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (password != null ? password.hashCode() : 0); return result; } } |
6. Описание клиентской части
Это приложение частично основано на вступительной статье Пример создания приложения Google Web Toolkit (GWT) Hello World Example. Для тех кто еще не очень разбирается в GWT рекомендую начать с нее, т.к. там описаны шаги по запуску простенького GWT приложения. Здесь большинство описания кода не относящиеся к GWT + Hibernate будет опущено, т.к. это будет загромождать статью.
6.1. Описание модуля GWT
Чтобы создать модуль автоматически можно использовать встроенный функционал Idea.
Правая кнопка — New — Google Web Toolkit — Gwt Module. Будет создано несколько файлов.
Main.html:
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html> <head> <title>Main Application</title> <link rel="stylesheet" href="Main.css"> </head> <body> <script type="text/javascript" language="javascript" src="gwtPersist/gwtPersist.nocache.js"></script> <h1>Main Application</h1> </body> </html> |
Main.css:
1 2 3 4 5 6 7 |
body { background-color: white; color: black; font-family: Arial, sans-serif; font-size: small; margin: 8px; } |
Main.gwt.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN" "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd"> <module rename-to="gwtPersist"> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <entry-point class='ru.javastudy.gwtPersistence.client.Main'/> <!-- Inherit the UiBinder module. --> <inherits name="com.google.gwt.uibinder.UiBinder"/> <!-- Specify the paths for translatable code --> <source path='client' /> <source path='shared' /> </module> |
Как видите html пустой, в css добавлены несколько простых настроек, а gwt.xml от стандартного отличается только добавлением поддержки UIBinder.
Main.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package ru.javastudy.gwtPersistence.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.*; import ru.javastudy.gwtPersistence.client.login.LoginViewImpl; import ru.javastudy.gwtPersistence.shared.Users; import java.util.List; /** * Created for JavaStudy.ru on 23.11.2015. */ public class Main implements EntryPoint { public void onModuleLoad() { /*create view */ final LoginViewImpl loginView = new LoginViewImpl(); /*init rpc service */ MainRpcServiceAsync rpcService = GWT.create(MainRpcService.class); rpcService.getAllUsers(new AsyncCallback<List<Users>>() { @Override public void onFailure(Throwable caught) { Window.alert("Fail getAllUsers"); } @Override public void onSuccess(List<Users> result) { Users user = result.get(0); loginView.getUserInputBox().setText(user.getName()); loginView.getPassInputBox().setText(user.getPassword()); loginView.getCompletionLabel1().setText("Users in data base: " + result.size()); /*add (refresh) view to Main.html with data*/ RootPanel.get().add(loginView); } }); /*add view to Main.html without data*/ RootPanel.get().add(loginView); } } |
При запуске приложения сначала будет инициализировано представление LoginView. Затем будет вызван асинхронный метод на серверной части для получения списка пользователей из базы данных MySQL и имя и пароль первого пользователя из списка подставятся в InputBox поля формы аутентификации.
6.2. Описание представления LoginView
LoginViewImpl.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
package ru.javastudy.gwtPersistence.client.login; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.uibinder.client.UiTemplate; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.*; public class LoginViewImpl extends Composite implements LoginView { private static LoginUiBinder uiBinder = GWT.create(LoginUiBinder.class); /* * @UiTemplate is not mandatory but allows multiple XML templates * to be used for the same widget. * Default file loaded will be <class-name>.ui.xml */ @UiTemplate("LoginViewImpl.ui.xml") interface LoginUiBinder extends UiBinder<Widget, LoginViewImpl> { } @UiField(provided = true) final LoginResources res; public LoginViewImpl() { this.res = GWT.create(LoginResources.class); res.style().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } @UiField TextBox loginBox; @UiField TextBox passwordBox; @UiField Label completionLabel1; @UiField Label completionLabel2; @UiField Button buttonSubmit; private Boolean tooShort = false; /* * Method name is not relevant, the binding is done according to the class * of the parameter. */ @UiHandler("buttonSubmit") void doClickSubmit(ClickEvent event) { if (tooShort) { Window.alert("LoginViewImpl or Password is too short!"); } else { Window.alert("Submit!"); } } @UiHandler("loginBox") void handleLoginChange(ValueChangeEvent<String> event) { if (event.getValue().length() < 2) { completionLabel1.setText("LoginViewImpl too short (Size must be > 2)"); tooShort = true; } else { tooShort = false; completionLabel1.setText(""); } } @UiHandler("loginBox" ) void handleLoginKeyboardKey(KeyDownEvent event) { if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { Window.alert("Sumbit by Enter!"); } } @UiHandler("passwordBox" ) void handlePasswordKeyboardKey(KeyDownEvent event) { if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { Window.alert("Sumbit by Enter!"); } } @UiHandler("passwordBox") void handlePasswordChange(ValueChangeEvent<String> event) { if (event.getValue().length() < 2) { tooShort = true; completionLabel2.setText("Password too short (Size must be > 2)"); } else { tooShort = false; completionLabel2.setText(""); } } @Override public HasText getUserInputBox() { return loginBox; } @Override public HasText getPassInputBox() { return passwordBox; } public Label getCompletionLabel1() { return completionLabel1; } } |
Описаны две формы для ввода логина и пароля и лейблы для них; добавлены слушатели для клика мышкой или нажатия клавиши Enter.
LoginViewImpl.ui.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:gwt='urn:import:com.google.gwt.user.client.ui'> <ui:with type="ru.javastudy.gwtPersistence.client.login.LoginResources" field="res"/> <gwt:HTMLPanel> <div align="center"> <gwt:VerticalPanel styleName="{res.style.blackText}"> <gwt:Label text="Login" styleName="{res.style.blackText}" /> <gwt:TextBox ui:field="loginBox" styleName="{res.style.box}" /> <gwt:Label text="Password" styleName="{res.style.blackText}" /> <gwt:PasswordTextBox ui:field="passwordBox" styleName="{res.style.box}" /> <gwt:HorizontalPanel verticalAlignment="middle"> <gwt:Button ui:field="buttonSubmit" text="Submit" styleName="{res.style.loginButton}" /> <gwt:CheckBox ui:field="myCheckBox" /> <gwt:Label ui:field="myLabel" text="Remember me" styleName="{res.style.blackText}" /> </gwt:HorizontalPanel> <gwt:Label ui:field="completionLabel1" styleName="{res.style.blackText}" /> <gwt:Label ui:field="completionLabel2" styleName="{res.style.blackText}" /> </gwt:VerticalPanel> </div> </gwt:HTMLPanel> </ui:UiBinder> |
Если вы не знаете как работать с UIBinder, то рекомендую прочитать вводную статью GWT – UIBinder Hello World. Пример использования UIBinder в GWT (код представления и css из неё с небольшими изменениями).
7. Серверная часть
На стороне клиента создан интерфейс для вызова методов (получение списка пользователей и сохранения пользователей) на серверной части:
MainRpcService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package ru.javastudy.gwtPersistence.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import ru.javastudy.gwtPersistence.shared.Users; import java.util.List; /** * Created for JavaStudy.ru on 23.11.2015. */ @RemoteServiceRelativePath("gwtPersistService") public interface MainRpcService extends RemoteService { List<Users> getAllUsers(); Users saveUser(Users user); } |
Реализация на серверной стороне MainRpcServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package ru.javastudy.gwtPersistence.server; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import ru.javastudy.gwtPersistence.client.MainRpcService; import ru.javastudy.gwtPersistence.shared.Users; import java.util.ArrayList; import java.util.List; /** * Created for JavaStudy.ru on 23.11.2015. */ public class MainRpcServiceImpl extends RemoteServiceServlet implements MainRpcService { private SessionFactory sessionFactory; private Session session; private List usersList = new ArrayList<>(); public MainRpcServiceImpl() { sessionFactory = HibernateUtil.getSessionFactory(); session = sessionFactory.openSession(); } @Override public List<Users> getAllUsers() { Query query = session.createQuery("from Users"); usersList = query.list(); return usersList; } @Override public Users saveUser(Users user) { session.save(user); return null; } } |
Инициализируется hibernate session (описано в разделе настройка Hibernate) и далее создается запрос для получения или сохранения пользователя.
8. Запуск приложения
Перед запуском проверьте, что все библиотеки добавлены в артефакт.
После запуска в поля для логина и пароля будут подставлены данные из базы данных (скрин таблицы users был выше).
В нашем случае это user.name = name; user.password = password;
Может быть интересно
GWT 2.7.0 + JPA 2.1 + Hibernate 5. Пример доступа к базе данных из GWT с помощью JPA + Hibernate
Исходные коды:
GwtHibernate SQL — база данных с записями.
GWT — Persistence — Java код.
11