7 мая 2014 в 12:07

Java 8, Spring, Hibernate, SSP — начинаем играться из песочницы

Scala*, Java*
Совсем недавно вышла Java 8. И у меня возникло желание написать что-то с использованием новых плюшечек, которые дает 8-ерка.
Конкретно Лямбы, новый collection api, позволяющий работать с коллекциями в более функциональном стиле и default-методы в интерфейсах.

Статья является кратким обзором моего опыта по интегрированию Java 8, Spring MVC, Hibernate и SSP.

Кому интересно, прошу под кат.

Предисловие


Я долгое время(и все еще) продолжаю восхищаться языком Scala, но к сожалению, мне все еще мой пропитанный ынтырпрайзом мозг мешает перейти на чистую Scala.

В первую очередь из-за привязки ко внешним библиотекам (Hibernate, Spring, Spring MVC), к которым я до сих пор питаю слабость.
Я пытался их и использовать в Scala-проектах, но остается постоянное впечатление, что занимаешься постоянной расстановкой костылей и подпорок и не удается писать в Scala-стиле,
скорее пишешь на Java, но со Scala синтаксисом + костыли и подпорки в виде неявных преобразований Java-коллекций в Scala-коллекции и обратно.

Поэтому я решил пойти немного более «мягким» путем и использовать знакомый стек. Единственное изменение, к которому я пришел — использовать SSP(Scala Server Pages) вместо JSP(Java Server Pages),
что бы получить статическую поддержку на стороне View и не иметь сильной головной боли с тем,
что что-то ломается при рефакторинге и ты узнаешь это уже после деплоймента(когда какой-то блок тупо перестает отображаться либо что еще хуже подпортит данные в БД)

Начало


Итак, начнем.

Будем использовать мой любимый Maven.

Дадим Maven'у нампек, что проект у нас будет использовать Java 8:
...
<build>
	<plugins>
	...
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<executions>
				<execution>
					<phase>compile</phase>
					<goals>
						<goal>compile</goal>
					</goals>
				</execution>
			</executions>
			<configuration>
				<_source>1.8</_source>
				<target>1.8</target>
			</configuration>
		</plugin>
...
	</plugins>
</build>
...


Добавляем нужные зависимости на Spring(4-ой версии, который поддерживает изменения в новой версией JDK, а так же тянет библиотеку, которая умеет работать с байткодом, сгенерированной 8-ой Java'ой)/Hibernate и SSP. Все остальное по вкусу. Версии вынесены в «dependency management» секцию в parent pom'e.
<dependencies>
...
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-orm</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
	</dependency>

	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-entitymanager</artifactId>
	</dependency>

	<dependency>
		<groupId>org.hibernate.javax.persistence</groupId>
		<artifactId>hibernate-jpa-2.0-api</artifactId>
	</dependency>
	
	<!-- Scalate (SSP) support-->
	<dependency>
		<groupId>org.fusesource.scalate</groupId>
		<artifactId>scalate-core_2.10</artifactId>
	</dependency>

	<dependency>
		<groupId>org.fusesource.scalate</groupId>
		<artifactId>scalate-spring-mvc_2.10</artifactId>
	</dependency>
...
</dependencies>


Первая проблема, на которую натолкнулся — несовместимость Scala компилятора, который идет зависимостью с библиотекой «Scalate»(именно благодаря ей мы имеем поддержку SSP) с байт-кодом Java 8.
Пришлось явно прописать зависимость в проект на Scala компилятор, что бы все взлетело:

<dependency>
	<groupId>org.scala-lang</groupId>
	<artifactId>scala-compiler</artifactId>
	<version>2.10.4</version>
</dependency>


//изначально хотел проапгрейдить зависимость до 2.11, но «кишки» сильно поменялись и в последней доступной версии Scalate (1.6.1) это пока что еще не поддержано.

Так же мы хотим, что бы наши SSP были прекомпилированы и мы узнали о проблеме при компиляции, а ни на продакшне.
Поэтому добавляем плагин для этого:

<build>
	<plugins>
		...
		<plugin>
			<groupId>org.fusesource.scalate</groupId>
			<artifactId>maven-scalate-plugin_2.10</artifactId>
			<version>1.6.1</version>
			<!--Support jdk 8-->
			<dependencies>
				<dependency>
					<groupId>org.scala-lang</groupId>
					<artifactId>scala-compiler</artifactId>
					<version>2.10.4</version>
				</dependency>
			</dependencies>
			<executions>
				<execution>
					<goals>
						<goal>precompile</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
		...
	</plugins>
</build>

//как заметно, добавлен тот же хачек со Scala компилятором

Немного кода



Ну с конфигурацией почти все

Теперь можно начать баловаться с кодом и радоваться плюшкам JDK 8:

Мой базовый DAO:

public interface BaseDAO<T extends Model<ID>, ID extends Serializable> extends EntityManagerAware {
    Class<T> getEntityClass();

    default void persist(T entity) {
        if (entity.isNew()) {
            entity.assignId();
        }

        getEntityManager().persist(entity);
        getEntityManager().flush();
    }

    default T find(ID id) {
        return getEntityManager().find(getEntityClass(), id);
    }

    default void delete(T entity) {
        getEntityManager().remove(entity);
    }

    default List<T> findByQuery(String jpqlQueryString) {
        return findByQueryWithParams(jpqlQueryString, Collections.emptyMap());
    }

    default List<T> findByQueryWithParams(String jpqlQueryString, Map<String, Object> params) {
        TypedQuery<T> query = getEntityManager().createQuery(jpqlQueryString, getEntityClass());
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
        return query.getResultList();
    }
}


К сожалению, без дополнптиельной прослойки в виде абстрактного класса обойтись не удалось:
public abstract class AbstractBaseDAO<T extends Model<ID>, ID extends Serializable> implements BaseDAO<T, ID> {
    @PersistenceContext
    EntityManager entityManager;

    @Override
    public EntityManager getEntityManager() {
        return entityManager;
    }
}


Конкретный интерфейс DAO:
public interface PersonDAO extends BaseDAO<Person, UUID> {
    @Override
    default Class<Person> getEntityClass() {
        return Person.class;
    }

    List<Person> findAll();
}


Ну и соотвественно имплементация:

@Repository
public class PersonDAOImpl extends AbstractBaseDAO<Person, UUID> implements PersonDAO {

    @Override
    public List<Person> findAll() {
        return findByQuery("select p from Person p");
    }
}


В результате мы получаем CRUD для репозитория и, на мой взгляд, очищаем имплементацию от побочного шума.
//Конечно можно было использовать Spring Data JPA и тогда ручками CRUD вообще не пришлось бы писать, но некоторые вещи оттуда мне не нравятся: В случае вручную генерируемых/присвоенных ID он будет всегда делать merge вместо persist. Да и таким образом довольно проще контролировать поведение системы.

Так же избавляемся от нужды использовать сторонние библиотеки типа Guava, который позволяют писать в более функциональном стиле и получаем все из коробки:

List<PersonForm> all = personService.findAll().stream().map(PersonForm::from).collect(Collectors.<PersonForm>toList());


View для отображения списка:

<%@ val people: java.util.List[name.dargiri.web.controller.PeopleController.PersonForm]%>
<div class="page-header">
<h1>People</h1>
</div>
<table class="table table-striped">
<thead>
    <tr>
        <th>#</th>
        <th>Username</th>
        <th>Action</th>
    </tr>
</thead>
<tbody>
    <% for(person <- people ) { %>
    <tr>
        <td>
            <%=person.id%>
        </td>
        <td>
            <%=person.username%>
        </td>
        <td>
            <a href="<%=uri("/people/edit/" + person.id)%>">Edit</a> |
            <a href="<%=uri("/people/delete/" + person.id)%>">Delete</a>
        </td>
    </tr>
    <% } %>
</tbody>
</table>


Сборка проекта проста. В случае если вы выкачали мой проект и хотите его подеплоить в сервлет контейнер, скажем в Tomcat, то запустим:
mvn -P=build clean package

И видим как пре-компилируются наши SSP'шечки:
...
[INFO] --- maven-scalate-plugin_2.10:1.6.1:precompile (default) @ web ---
[INFO] Precompiling Scalate Templates into Scala classes...
[INFO]     processing /Users/dionis/projects/spring-mvc-java8-web-app-template/web/src/main/webapp/WEB-INF/views/scalate/main/person.ssp
[INFO]     processing /Users/dionis/projects/spring-mvc-java8-web-app-template/web/src/main/webapp/WEB-INF/views/scalate/main/people.ssp
[INFO]     processing /Users/dionis/projects/spring-mvc-java8-web-app-template/web/src/main/webapp/WEB-INF/scalate/layouts/default.ssp
...


Так что если не дай бог что-то пошло не так и мы что-то поломали в них из того, что компилируется, то мы узнаем об этом сейчас, а не после деплоймента на dev/qa/staging/production environment.

В примере использовал Twitter Bootstrap, ибо нравится он мне.

Скриншоты
Создание пользователя:


Список пользователей:


Редактирование пользователя:




Код примера:
github.com/dargiri/spring-mvc-java8-web-app-template

В качестве бесплатного бонуса:

Тоже самое на Java 7:
github.com/dargiri/spring-mvc-java-web-app-template

Тоже самое на Scala:
github.com/dargiri/spring-mvc-scala-web-app-template

И если вы все еще это читаете и вытащили себе код и хотите с ним поиграться.


Я предпочитаю запускать из-под IDE, а не пользоваться плагинами для IDE.
Поэтому в модуле web-app-launcher находим класс Launcher.
Если вы пользуетесь Idea, то все запустится без проблем.
Если вы пользуетесь Eclipse/Netbeans, то нужны некоторые манипуляции.
Для Eclipse — достать изначально Eclipse с поддержкой JDK 8: www.eclipse.org/downloads/index-java8.php

P.P.S. Пишите код и да пребудет с вами сила.

Далее для проекта нужно выбрать maven-профайл build.
И в классе Launcher значение переменной MULTI_MODULE_DEFAULT_PATH сменить с «web/src/main/webapp» на "../web/src/main/webapp" либо на полный путь от корня вашей файловой системы.

Ссылки:


Apache Maven — maven.apache.org
Scalate — scalate.fusesource.org
Scala — www.scala-lang.org
Apache Tomcat — tomcat.apache.org
Twitter Bootstrap — getbootstrap.com
Spring Data JPA — projects.spring.io/spring-data-jpa
Hibernate ORM — hibernate.org/orm
JDK 8 — www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Spring — projects.spring.io/spring-framework
@fspare
карма
5,0
рейтинг 0,0
Серверный Java разработчик
Похожие публикации
Самое читаемое Разработка

Комментарии (15)

  • 0
    А jade не рассматривали или чем то не угодил? scalate.fusesource.org/documentation/jade.html
    После двухгодичного использования slim на ruby (потомок jade, кстати) обычные теги глаза режут)
    • 0
      К сожалению с Jade не работал пока что и хотелось оставаться как можно ближе к старому(-доброму?) JSP+HTML с проверкой на уровне компиляции. Пока что до попробовать Jade/Mustache/Scaml руки не дошли.
  • 0
    Scalate, круто, мы вот с thymeleaf мучаемся, но тихонько смотрим в сторону Scalate. Пугают огромные размеры scala библиотек.
    • 0
      Действительно, мой собранный проект-пустышка занимает сейчас 39 мегабайт. Scala библиотеки отъедают около половины(~ 25 мегабайт).
      В моем случае размер получаемого war-архива не столь критичен.

      Подробнее какие библиотеки(что сколько чего занимает в предлагаемом примере):

      drwxr-xr-x  42 dionis  staff   1.4K May  6 13:20 ./
      drwxr-xr-x  10 dionis  staff   340B May  6 13:20 ../
      -rw-r--r--   1 dionis  staff   435K Feb 20 10:58 antlr-2.7.7.jar
      -rw-r--r--   1 dionis  staff   4.4K Feb 20 10:57 aopalliance-1.0.jar
      -rw-r--r--   1 dionis  staff   108K Feb 20 21:44 bonecp-0.8.0.RELEASE.jar
      -rw-r--r--   1 dionis  staff   1.6K May  5 14:11 common-1.0-SNAPSHOT.jar
      -rw-r--r--   1 dionis  staff   376K Feb 20 21:44 commons-lang3-3.2.1.jar
      -rw-r--r--   1 dionis  staff    61K Feb 20 10:58 commons-logging-1.1.3.jar
      -rw-r--r--   1 dionis  staff    12K May  5 14:11 data-1.0-SNAPSHOT.jar
      -rw-r--r--   1 dionis  staff   307K Feb 20 10:58 dom4j-1.6.1.jar
      -rw-r--r--   1 dionis  staff   2.1M Feb 20 21:45 guava-15.0.jar
      -rw-r--r--   1 dionis  staff   1.5M Feb 20 21:45 h2-1.3.172.jar
      -rw-r--r--   1 dionis  staff    80K Feb 20 10:58 hibernate-commons-annotations-4.0.2.Final.jar
      -rw-r--r--   1 dionis  staff   4.4M Feb 20 10:58 hibernate-core-4.2.2.Final.jar
      -rw-r--r--   1 dionis  staff   473K Feb 20 10:58 hibernate-entitymanager-4.2.2.Final.jar
      -rw-r--r--   1 dionis  staff   100K Feb 20 10:58 hibernate-jpa-2.0-api-1.0.1.Final.jar
      -rw-r--r--   1 dionis  staff    34K Feb 20 21:44 jackson-annotations-2.3.0.jar
      -rw-r--r--   1 dionis  staff   193K Feb 20 21:44 jackson-core-2.3.0.jar
      -rw-r--r--   1 dionis  staff   893K Feb 20 21:44 jackson-databind-2.3.0.jar
      -rw-r--r--   1 dionis  staff   633K Feb 20 10:58 javassist-3.15.0-GA.jar
      -rw-r--r--   1 dionis  staff    59K Feb 20 10:58 jboss-logging-3.1.0.GA.jar
      -rw-r--r--   1 dionis  staff    25K Feb 20 10:58 jboss-transaction-api_1.1_spec-1.0.1.Final.jar
      -rw-r--r--   1 dionis  staff   470K Feb 20 21:44 log4j-1.2.16.jar
      -rw-r--r--   1 dionis  staff    14M May  5 13:54 scala-compiler-2.10.4.jar
      -rw-r--r--   1 dionis  staff   6.8M Mar 19 20:08 scala-library-2.10.0.jar
      -rw-r--r--   1 dionis  staff   3.0M Mar 19 20:06 scala-reflect-2.10.0.jar
      -rw-r--r--   1 dionis  staff   1.9M Feb 20 21:45 scalate-core_2.10-1.6.1.jar
      -rw-r--r--   1 dionis  staff    24K Feb 20 21:45 scalate-spring-mvc_2.10-1.6.1.jar
      -rw-r--r--   1 dionis  staff   288K Feb 20 21:44 scalate-util_2.10-1.6.1.jar
      -rw-r--r--   1 dionis  staff    25K Feb 20 21:44 slf4j-api-1.7.5.jar
      -rw-r--r--   1 dionis  staff   8.7K Feb 20 21:44 slf4j-log4j12-1.7.5.jar
      -rw-r--r--   1 dionis  staff   344K Mar 19 17:24 spring-aop-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   653K Mar 19 17:26 spring-beans-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   951K Mar 19 17:26 spring-context-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   132K Mar 19 17:26 spring-context-support-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   938K Mar 19 17:26 spring-core-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   200K Mar 19 17:26 spring-expression-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   410K Mar 19 17:26 spring-jdbc-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   358K Mar 19 17:26 spring-orm-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   242K Mar 19 17:26 spring-tx-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   649K Mar 19 17:26 spring-web-4.0.2.RELEASE.jar
      -rw-r--r--   1 dionis  staff   645K Mar 19 17:26 spring-webmvc-4.0.2.RELEASE.jar
      
      
      • 0
        Так ведь большинство библиотек можно положить в контейнер и не таскать каждый раз в war.
        • 0
          Да, но на мой взгляд в этом нет большого смысла — кроме нескольких секунд сэкономленных при заливании war'a на сервер и его распаковке мы фактически ничего не выигрываем, а скорее наоборот обновлять библиотеки становится проблематичным, ведь допустим с минорным апдейтом того же Spring'a, скажем с версии 4.0.2 на 4.0.3 какая-то из его транзитивных зависимостей может поменяться и, получается, придется вручную все это просматривать и следить за всем этим.
          • 0
            а так можем словить OOM при deploy, ежели со всеми зависимостями заливать.
            Библиотеки когда меняются, несложно их перезалить в shared-lib каталог Tomcat. Да и перезаливка war всяко чаще происходит чем обновление версий зависимостей.
            • 0
              Я, и в фирме где я работаю, существует внегласное правило — под одним контейнером бежит одно приложение. Поэтому, у нас деплоймент выглядит следующим образом:
              1). Останавливается контейнер(в нашем случае — Tomcat).
              2). Стирается все из webapps директории
              3). Стирается все из work директории
              4). Заливается новый war
              5). Контейнер(Tomcat) запускается

              Таким образом мы избегаем главной проблемы, связанной с redeployment'ом — OOM.

              Это работает в случае с приложениями, где down-time возможен и не мешает.

              В случае, где downtime недопустим — у нас бежит несколько инстансов томката за лоадбалансером, и каждый инстанс редеплоится именно таким образом, что исключает downtime при backward-compatible изменениях базы данных.
  • 0
    Делаю проект на Java 8 + Spring + JDBC. Из очень серьёзных минусов — JRebel работает очень нестабильно и часто приходится перезапускать сервер. Для меня это критично, к хорошему (обновление работающего приложения при любых изменениях за доли секунды) быстро привыкаешь. Поверх JDBC хотелось бы обёртку для Java 8. Есть все возможности, чтобы это выглядело и работало очень удобно.

    В целом Java 8 это клёво, но всё же не идеально интегрировано в стандартную библиотеку. Но плюшки очень вкусные.
    • 0
      Что вы имеете ввиду под «Поверх JDBC хотелось бы обёртку для Java 8. Есть все возможности, чтобы это выглядело и работало очень удобно.»?
      Что spring-jdbc модуль не возвращает Optional, там где мог бы его возвращать вместо того что бы кидать искллючение(в queryForObject методе например)?
      • 0
        Optional, Instant вместо Timestamp. Ну вообще хотелось бы в JDBC кучу функций похожих на Spring JDBC, чтобы работать через callbacks, а не напрямую итерировать.
  • 0
    Мне в последние годы казалось, что Server Pages — сильно устаревшая технология, т.к. все тонкие клиенты для JEE, с которыми сталкивался (существующие или которые начинал с нуля сам) делались на JSF либо GWT. Да и вообще, за 10+ лет разработки на Java ни разу не видел JSP в качестве основного инструмента «рисования» View. Мне вот интересно, кроме примера в статье, у Вас реальные JEE проекты крутятся на JSP/SSP?

    По поводу перехода на Java 8. Есть у меня проект на Java 7, Tomcat 7, MySQL, JSF, Hibernate. Попытался переключиться на Java 8, вроде без проблем. Попытался тут же применить что-то новое, нашел подходящую структуру, в которую «просился» default метод интерфейса. Переделал, запустил, но — увы — реализация EL на JSF страничке, которая раньше «понимала» метод в абстрактном классе, не нашла тот же метод в интерфейсе. Ну понятно, думаю, 7-й Tomcat восьмую Java не поддерживает, надо 8-й Tomcat попробовать (библиотека EL идет в комплекте с Tomcat-ом). Ставлю 8-й Tomcat, который как оказалось стартует вдвое дольше 7-го, и вижу, что ничего не изменилось — та же ошибка. Дальше копать не стал, т.к. в моем случае овчинка выделки не стоит… И, кстати, поиграйтесь с фичами Java 8 на JSP страничке, вполне вероятно, что интерпретатор JSP тоже «сломается», как и парсер EL…
    • +1
      У нас Web(фронт-энд для нашего rest-service'a) и Admin Panel крутится сейчас именно на Spring, Spring MVC и JSP используется в качестве View. Насчет фич 8-ой java и дергания логики из View — на мой взгляд не очень хорошая идея. У нас на/c JSP передаются DTO объекты, которые в свою очередь трансформируются в DTO объекты сервисного уровня, которые уже в свою очередь преобразуются из/в объекты модельного уровня. С одной стороны, это может показаться избыточным(для простых приложений), с другой стороны это лучше позволяет разделить слои приложений и переиспользовать код.

      Насчет SSP — я его обкатываю на своих личных проектах в первую очередь, прежде чем предложить использовать его в Production(с большой буквы), но пока что в целях — есть желание внедрить эту технологию отображения для начала, хотя бы для Admin Panel нашего сервиса.

      Насчет JSF — личного опыта работы с ним я не имею, но от своих коллег, которые работали с данной технологией, я слышал не лестные отзывы. К ним относится — тормозит, если написаны какие-то кастомные компоненты, то при апгрейде на новую версию JSF их нужно зачастую сильно переделывать, что бы они заработали, кастомизировать под свои нужды внешний вид — тоже то еще занятие.

      Насчет GWT — я немного пощупал эту технологию, и мне в принципе даже понравилось, но из минусов — довольно сложно изменять внешний вид, то есть я бы стал использовать только в том случае, если нужно набросать по-быстрому какой-то административный UI, где внешний вид не важен, так как кастомизировать отображение выходит сложнее, чем если это написать все с использованием более простых технологий.
      • 0
        Насчет фич 8-ой java и дергания логики из View — на мой взгляд не очень хорошая идея
        Ну, логика — понятие растяжимое, даже в вашем примере person.id вполне мог бы быть default методом какого-нибудь интерфейса Identifier, например, при каждом обращении к getId генерирующим новый сиквенс, чисто теоретически…

        По поводу технологий — вечный холивар, у каждой есть плюсы и минусы, не будем об этом. Но вот по поводу JSP — мне кажется, что довольно тяжело расписывать для каждой странички каждый тег. Либо ваша Admin Panel выглядит очень убого, либо на каждую страничку у вас исходники на много сотен строк, либо у вас не чистый JSP, а подключена какая-то библиотека виджетов. Насколько я помню, на заре JSF нередко View рисовался именно на JSP-странице. В общем, чисто из любопытства, скажите, что представляет из себя ваша Admin Panel: много ли страничек, каков внешний вид, какой объем кода типичной странички?
        • +1
          Общий шаблон у нас вынесен и используется sitemesh. Остальные странички выглядят довольно просто. Сейчас в admin panel'e что-то около 15-20 страниц. Используется twitter bootstrap, что делает страницу относительно симпатичной, jQuery библиотека, underscore js и еще несколько по мелочи. Объем кода на страничках варьируется. В среднем что-то около 50-100, если нигде js не понаписан дополнительный.

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