GrabDuck

Java 8, Spring, Hibernate, SSP — начинаем играться

:

Совсем недавно вышла 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