По следам Spring Pet Clinic. Maven/ Spring Context/ Spring Test/ Spring ORM/ Spring Data JPA

8720470528074f95bc4ff8d0c08b461d.jpgЗдравствуйте! Spring MVC, согласно обзору инструментов и технологий Java за 2014 г. от RevbelLabs, является самым популярным веб фреймворком. Далее тот же обзор называет лидера ORM — Hibernate и лидера веб-контейнеров — Apache Tomcat. Добавим сюда самую используемую java script библиотеку jQuery, самый популярный css фреймворк Bootstrap, до сих пор самую популярную (несморя на наступление Gradle) инструмент сборки Maven, абсолютный лидер среди тестовый фреймворков JUnit и получим пример приложения на Spring от его создателей: Spring Pet Clinic (демо приложение). Кроме перечисленного, в этот достаточно несложный по функциональности проект влючены также Spring-Jdbc, Spring-ORM, Spring Data JPA, Hibernate Validator, SLF4J, Json Jackson, JSP, JSTL, WebJars, Dandelion DataTables, HSQLDB, Hamcrest, Mockito и десятки других зависимостей.Прогресс в разработке ПО подразумевает сокращение объема собственного кода приложения, в идеале, только до бизнес логики приложения. Однако это дается не бесплатно — количество зависимостей даже для простого проекта перевалило за полсотни (в PetClinic в WEB-INF\lib находится 61 jar). Конечно не объязательно знать их все, некоторые jar подтягиваются в фоне, и мы даже не подозреваем о них, пока не посмотрим на готовый war или невыполним mvn project-info-reports: dependencies (в IDEA: Show Dependencies… на проекте Maven). Но с основными приходится работать. И на борьбу с некоторыми их особенностями иногда тратятся часы, а то и дни. А еще приходится сталкиваться с багами самих фреймворков…Недавно, вдохновленный Pet Clinic, при создании вебинара по этим технологиям я сделал приложение «Todo Management List»: управление списоком дел с авторизацией и регистрацией пользователей. К зависимостям Pet Clinic добавились еще Spring Security/ совсем свежий Spring Security Test и плагины к jQuery Jeditable и jQuery notification. Объем статьи не позволяет описать шаги создания приложения (вебинар по созданию приложения занимает 30 часов), поэтому здесь делюсь ресурсами, некоторыми мыслями и решениями, пришедшими в процессе его создания.На PaaS Heroku можно найти демо приложения (первый раз при запуске возможна долгая загрузка и ошибка сервера, повторить).

На просторах интернета немало приложений, построенных на Spring/ JPA/ MVC/ Security. Можно скачать сорсы и выбрать наиболее подходящее вам решение.

В конфигурировании Spring есть тенденция прятать детали реализации под свои пространства имен. Конфигурация становится меньше и понятнее, однако процесс кастомизации или дебага становится не совсем тривиальный: сначала нужно найти бины, где это реализовано. Сравните например инициализацию базы:

classpath*: db/${jdbc.initLocation} classpath*: db/populateDB.sql и  Особенно это видно при сравнении бывшего Acegi Security cо Spring Security (все фильтры спрятаны под namespace security).

В тестах Spring принято использовать транзакционность: после выполнения каждого теста происходит rollback базы в исходное состояние. Однако сам @Transactional сильно влияет на поведение тестов: например, вы забыли в сервисе/репозитории @Transactional, тест прошел, а приложение упало. Еще хуже, когда в тесте достаются для сравнения сущности из базы: они попадают в тот же транзакционный контекст и поведение тестируемых методов становится несколько другим (спасает только evict или detach). Состояние базы при дебаге теста также не отображается, пока не закончилась транзакция теста. Более честно использовать инициализатор базы перед каждым тестом: public class DbPopulator extends ResourceDatabasePopulator { private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader ();

@Autowired private DataSource dataSource;

public DbPopulator (String scriptLocation) { super (RESOURCE_LOADER.getResource (scriptLocation)); }

public void execute () { DatabasePopulatorUtils.execute (this, dataSource); } } @ContextConfiguration («classpath: spring/spring-app.xml») @RunWith (SpringJUnit4ClassRunner.class) @ActiveProfiles ({«postgres», «jpa»}) public class TodoItemServiceTest {

@Autowired private DbPopulator dbPopulator;

@Before public void setUp () throws Exception { dbPopulator.execute (); }

Привыкнув в Spring 3.0 к багам о необъявленной в persistence.xml сущности был удивлен, что все работает без этого! После некоторого копания в коде увидел, что весь target/classes сканируется на entity анотации. Также порадовала возможность конфигурировать JPA без persistence.xml. Можно задавать конкретные пакеты для сканирования модели, конфигурировать специфичные для провайдера и общие JPA параметры. Причем их можно вынести в общий db.properties файл:

${hibernate.format_sql} ${hibernate.use_sql_comments}

Традиционный выбор реализации DataSource Commons DBCP похоже сдает свои позиции. По данным StackOverflow для реализации нужно брать BoneCP, используемый в playframework (если вы его уже используете или собираетесь, учтите, что требуются некоторые услилия, чтобы избежать утечек памяти, озвученных в докладе от разработчика Plumbr). А в PetClinic используется tomcat-jdbc. Если приложение деплоится в Tomcat, можно не включать его в war (scope=provided), но при этом в $TOMCAT_HOME/libнеобходимо положить драйвер базы, т.к из родного tomcat-jdbc библиотеки вашего war недоступены.Ну и конечно при деплое в Tomcat не стоит забывать про возможность брать пул коннектов из ресурсов context.xml конфигурации Tomcat:

Привыкнув в каждом проекте создавать собственный AbstractDAO, параметризированный сущностью и ключем с имплементацией основных CRUD на основе EntityManager был обрадован, что наконец-то он вошел в Spring, правда, в проект Spring Data JPA: JpaRepositoryОн наследуется от более общего CrudRepository из Spring Data Commons.Работа с JPA репозиториями сначала поражает: достаточно написать public interface UserRepository extends Repository { User findByEmail (String email); } и метод сам заработает без единой строчки имплементации! Обращение к первоисточникам показало, что внутренности магии — проксирование, regexp и отражение:

Риторический вопрос читателям: можно ли java считать динамическим языком:)? Если JpaRepository и сгенерированных методов недостаточно, можно писать собственную имплементацию методов или запросы Query. В @Query можно писать JPQL запросы (которые генерятся в @NamedQuery), а можно ссылаться на уже объявленные @NamedQuery в сущностях (почему то в PetClinic @NamedQuery игнорируются, хотя такие запросы строятся и проверяются на этапе деплоя).Например, метод

@Modifying @Transactional @Query (name = User.DELETE) int delete (@Param («id») int id); ссылается на объявленный в User @NamedQuery @NamedQueries ({ … @NamedQuery (name = User.DELETE, query = «DELETE FROM User u WHERE u.id=: id») })

public class User extends NamedEntity { public static final String DELETE = «User.delete»; В отличии от void CrudRepository.delete (ID id) он возвратит количество модифицированных записей.Однако есть проблема: наследование бизнес интерфейса доступа к даным от JpaRepository означает, что уровень сервиса становится зависимым от реализации. Кроме того, например, в методе List findAll (Sort sort) класс Sort также находится в Spring Data и не хочется его задавать в сервисах. Сигнатура методов интерфейса становится привязанным к сигнатурам в JpaService. Неудобно дебажиться и логгироваться. В бизнес интерфейс или попадают все методы JpaRepository, которые нам на уровне сервиса совсем не нужны, или, при наследовании от маркера org.springframework.data.repository.Repository у нас нет проверки @Override… Все эти проблемы решает еще один уровень делегирования:

public interface ProxyUserRepository extends JpaRepository {

@Modifying @Query («DELETE FROM User u WHERE u.id=?1») @Transactional int delete (int id);

@Override @Transactional User save (User user);

@Override User findOne (Integer id);

@Override List findAll (Sort sort); }

@Repository public class DataJpaUserRepository implements UserRepository { private static final Sort SORT_NAME_EMAIL = new Sort («name», «email»);

@Autowired private ProxyUserRepository proxy;

@Override public boolean delete (int id) { return proxy.delete (id) != 0; }

@Override public User save (User user) { return proxy.save (user); }

@Override public User get (int id) { return proxy.findOne (id); }

@Override public List getAll () { return proxy.findAll (SORT_NAME_EMAIL); } }

Spring Maven Логгирование Персистентность Если будет статья понравится, буду готовить часть 2 с неуместившимися Spring MVC, Spring Security, Jackson и пр.Спасибо за внимание, будет интересно услышать ваше мнение по затронутым темам.

© Habrahabr.ru