...

среда, 7 мая 2014 г.

[Из песочницы] 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/Hibernate и SSP. Все остальное по вкусу.



<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, ибо нравится он мне.


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


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


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




Код примера:

http://ift.tt/Qd9Ltm


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


Тоже самое на Java 7:

http://ift.tt/Qd9Ltq


Тоже самое на Scala:

http://ift.tt/Qd9Ltu


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




Я предпочитаю запускать из-под IDE, а не пользоваться плагинами для IDE.

Поэтому в модуле web-app-launcher находим класс Launcher.

Если вы пользуетесь Idea, то все запустится без проблем.

Если вы пользуетесь Eclipse/Netbeans, то нужны некоторые манипуляции.

Для Eclipse — достать изначально Eclipse с поддержкой JDK 8: http://ift.tt/1iWkkOs

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 — http://ift.tt/1lUQacu

Hibernate ORM — hibernate.org/orm/

JDK 8 — http://ift.tt/1h8zgnv

Spring — http://ift.tt/1cwu3ne

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий