Онлайн-проект Topjava
- Переименовал переменные в
MealRepository.getBetweenHalfOpen()/реализациях на...DateTime - Вернул в
spring-db.xmlpostgres как база по умолчанию - Переделал
jdbc.initLocation: не "тупит", если путь к скрипту полностью прописывать в пропертях.
- Правки расположения полей и названий методов в тестах
- Можно ставить любую версию JDK: 11, 12 или 13. В проекте оставил 11, т.к. она LTS (long term support) и на 12 еще мало проектов. Для JDK 12/13 нужно поправить 2 места:
pom.xml - <java.version>xx</java.version>.travis.yml - jdk: openjdkxx- Для запуска Tomcat под JDK11/12/13 не из IDEA проверь переменную окружения
JAVA_HOME(версия java в path проверяется просто:java -version) и версию Tomcat 9.x.
- API, ради которых наконец-то стоит обновиться с Java 8 (1)
- API, ради которых наконец-то стоит обновиться с Java 8 (2)
- Добавил javax зависимости
- Сделал создание коллекций через фабричные методы
List.of - Как пример в
InMemoryMealRepositoryиспользовал local variable type inferencevar.
- Validate by RegExp
- Working with JPA Entity Objects
- Java Persistence/Relationships
- Использование ThreadLocal переменных
- Merge vs Persist
- Видео: работа в ZK с OpenJPA (в чем Hibernate хуже)
- Паттерн "открытие транзакции в фильтре" и почему это bad-practice
- Sequence Strategies
- SequenceGenerator/IdentityGenerator in PostgreSql
EntityManager- это по сути прокси-обертка над Hibernate Session, которая создается каждый раз при открытии транзакции.
- Дополнительно (ни разу не сталкивался): еще есть редкий случай ручного управления
@PersistenceContext(type = PersistenceContextType.EXTENDED), когда он используется в нескольких транзакциях (long-running session or session-per-conversation).
- При сравнении еды тесты падают, т.к. Hibernate делает ленивую обертку к
user, и если происходит обращение к любому его полю (кроме id) вне транзакции, бросаетсяLazyInitializationException. По логике приложения полеuserв еде не нужно, и мы не будем его отдавать наружу: в тестах исключаемuserиз сравнения.- Поменял реализацию
JpaMealRepository.get()(вместо@NamedQuery), реализация стали проще- Сделал
JpaMealRepository.saveпроще и понятнее (em.getReferenceне делает запрос в базу и чужая еда- не основной юзкейс)- Вместо BETWEEN (и CAST) в JPA реализации сделал Half Open сравнение. См. также SQL “between” not inclusive
- Из-за Hibernate bug with proxy initialization when using
AccessType.FIELDвJpaMealRepository.get()делался дополнительный запрос в базу для инициализации проксиUser, и мы делали хак: доступ к полюAbstractBaseEntity.idчерезAccessType.PROPERTY. С версии5.2.13.Finalзагрузка прокси при обращении кidуправляется флагомJPA_PROXY_COMPLIANCE(по умолчанию запрос не делается)- Which is better, field or property access?
- Поправил
equals()с учетом Lazy-проксирования
Переопределять
equals()/hashCode()необходимо, если
- использовать entity в
Set(рекомендовано для Many-ассоциаций) либо как ключи вHashMap- использовать reattachment of detached instances (т.е. манипулировать одним Entity в нескольких транзакциях/сессиях).
Оптимально использовать уникальные бизнес-поля, но обычно таких нет, и чаще всего используются PK с ограничением, что он может быть
nullу новых объектов, и нельзя объекты сравнивать черезequals() and hashCode()в бизнес-логике (например, тестах).
Почему над
AbstractBaseEntityстоит@Access(AccessType.FIELD)? Почему при запросеuser.idнам не нужно вытаскивать его из базы?
AccessType.FIELD делает доступ в AbstractBaseEntity и всех классах-наследниках по полям. При загрузке Meal Hibernate на основе поля meal.user_id делает ленивую прокcи к User, у которой нет ничего, кроме id.
- Hibernate 5.2.x already include Java 8 date and time types (JSR-310)
- Stopwatch
- Добавил сводку "имя теста - время выполнения" в конце класса
How do you assert that a certain exception is thrown in JUnit 4 tests?
3. Транзакции
SLF4JBridgeHandlerперенес в профильpostgres(если логировать драйвер не нужно, то и он не нужен)- Галочка в XML-профиле влияет только на отображение в IDEA и никак не влияет на выполнение кода.
Profiles.ACTIVE_DBзадает активный профиль базы (postgres/hsqldb)Profiles.REPOSITORY_IMPLEMENTATIONопределяет реализацию репозитория при запуске приложения (для тестов задаются через@ActiveProfiles).
Для переключения на HSQLDB необходимо:
- поменять в окне Maven Projects профиль (Profiles) - выключить
postgres, включитьhsqldb- и сделатьReimport All Maven Projects(1-я кнопка)- поменять
Profiles.ACTIVE_DB = HSQLDB- почистить проект
mvn clean(фазаcleanне выполняется автоматически, чтобы каждый раз не перекомпилировать весь проект)
Для корректного отображения неактивного профиля в IDEA проверьте флаг Inactive profile highlighting и сделайте проекту clean
Вопрос: почему после этого патча не поднимается Spring при запуске приложения в Tomcat? (будем чинить в ДЗ, п.6)
Автоматический выбор профиля базы: ActiveProfilesResolver
Сделал автоматический выбор профиля базы при запуске приложения (тестов) в зависимости от присутствия драйвера базы в classpath (
Profiles.getActiveDbProfile())
Александр Колесников - JDBC Pools Battle (ссылка на выводы)
- Выбор реализации пула коннектов: Commons Database Connection Pooling, HikariCP
- Самый быстрый пул соединений на java (читаем комменты)
- Tomcat pool
- Переименовал классы Proxy на более адекватные Crud, убрал Impl
- В
spring-framework-bomмы уже задали версию Spring. Убрал из остальных зависимостей.- В spring-data-jpa 2.x поменялся интерфейс:
T CrudRepository.findOne(ID id)->Optional<T> CrudRepository findById(ID id)- Не стал переопределять в
CrudUserRepositoryметодыJpaRepository(для явного указания всех используемых методов). Обычно этого не делают.
- Spring Data JPA
- Замена AbstractDAO: JPA Repositories
- Разрешение зависимостей: Maven BOM [Bill Of Materials] Dependency
- Делегирование (в конце статьи)
- Getting started with Spring Data JPA
- Query methods
- Spring Data – новый взгляд на persistence (JeeConf)
- Евгений Борисов — Spring Data? Да, та!
- Ресурсы:
Какой паттерн проектирования применён в классе DataJpaUserRepository (декоратор/адаптер/другой)?:
Вопрос интересный:) Ближе всего к адаптеру, но скорее композиция с делегированием. Мы просто используем для нашей реализации возможности data-jpa: CrudUserRepository.
Делегат интерфейсов не меняет, а прокси похож на делегата, но служит для неявной подмены (часто прямо в рантайм). См. ПАТТЕРНЫ
ПРОЕКТИРОВАНИЯ
7. Spring кэш
- Сделал миграцию на Ehcache 3.x, compatibile with javax.cache API (JSR-107)
- В
UserServiceTest.setUpвместо вызова методаUserService.evictCacheсделал очистку программно черезCacheManager
- Кэширование в Spring Framework
- Дополнительно:
В spring-petclinic
DataJpaреализована без дополнительных классов. В таком виде как у них, spring data смотрится, конечно, намного лаконичней других реализаций, но у нас получилось вдвое больше кода, чем с тем же jpa или jdbc. Плюс только пожалуй в том, что query находятся прямо в репозитории, а не где-то там в другом пакете. Так что получается, spring data лучше подходит для простейших crud без всяких "фишек"? или в чем его достоинство для больших и сложных проектов?
Достоинства DATA-JPA по сравнению, например, с JPA: есть типизация, готовые реализации типовых методов CRUD, а также paging, data-common. Мы можем переключить реализацию JPA, например, на mongoDb (PagingAndSortingRepository, от которого наследуется JpaRepository, находится в spring-data-common).
Соответственно, его методы будут поддерживаться всеми реализациями spring-data-common (JPA - одна из них) и пр. Подробнее о них есть в видео Spring Data – новый взгляд на persistence.
Дополнительное проксирование в DATA-JPA - моя "фишка" для устранения минусов этого фреймворка: невозможность дебага, привязка к интерфейсу JpaRepository, перенос логики Repository в слой сервисов.
Для большого приложения выигрыш этого стоит. Для небольших (тестовых) приложений (например выпускного) дополнительных классов лучше не делать.
Почему мы для InMemory не сделали отдельного профиля? Почему их не удалить вообще?
Реализация InMemory является примером, как в test делать подмену контекста. Для них сделали отдельный inmemory.xml, и запускаемый проект ничего не должен о них знать. У нас учебный проект, в котором 4 реализации репозиториев, в реальном такого не будет.
А как делать транзакционность для реализации jdbc?
Будем делать на следующем уроке.
- 1: Имплементировать
DataJpaMealRepository. - 2: Разделить реализации Repository по профилям Spring:
jdbc,jpa,datajpa(общее в профилях можно объединять, например,<beans profile="datajpa,jpa">).- 2.1: Профили выбора DB (
postgres/hsqldb) и реализации репозитория (jdbc/datajpa/jpa) независимы друг от друга, и при запуске приложения (тестов) нужно задать тот, и другой. - 2.2: Для интеграции с IDEA не забудьте выставить в
spring-db.xmlсправа вверху вChange Profiles...профили, например,datajpa, postgres. - 2.3: Общие части для всех в
spring-db.xmlможно оставить как есть без профилей вверху файла (до первого<beans profile=!!!).
- 2.1: Профили выбора DB (
- 3: Сделать тесты всех реализаций (
jdbc, jpa, datajpa) через наследование (без дублирования).- 3.1 сделать один базовый класс для
MealServiceTestиUserServiceTest. - 3.2 сводку по времени выполнения тестов также сделать для
user
- 3.1 сделать один базовый класс для
- 4: Проверить запуск всех тестов:
mvn test(в IDEA Maven Lifecycle -test, кнопкуskipTestотжать).
- 5: Разделить
JdbcMealRepositoryдля HSQLDB (она не умеет работать с Java8 Time API) и Postgres через@Profile(для Postgres оставитьLocalDateTime).- Цель задания - потренироваться с паттерном "шаблонный метод" и профилями Spring.
Какие бины Spring попадут в контекст зависит от выставления активных профилей при запуске (
@ActiveProfilesв тестах) и конфигурации, где задаются бины для каждого профиля. Абстрактные классы не создаются и в контекст не попадают. - После выполнения разделения на основе профилей, можно предложить решение проще.
- Цель задания - потренироваться с паттерном "шаблонный метод" и профилями Spring.
Какие бины Spring попадут в контекст зависит от выставления активных профилей при запуске (
- 6: Починить
MealServletи использовать вSpringMainреализацию DB: добавить профили. Попробуйте поднять Spring контекст без использованияspring.profiles.active. - 7: Сделать и протестировать в сервисах методы (тесты и реализация - только для
DataJpa):- 7.1: достать по
idпользователя вместе с его едой - 7.2: достать по
idиuserIdеду вместе с пользователем - 7.3: обращения к DB сделать в одной транзакции (можно сделать разные варианты). Java Persistence/OneToMany
- 7.1: достать по
- 1: Для того, чтобы не запускались родительские классы тестов, нужно сделать их
abstract. - 2: В реализациях
JdbcMealRepositoryкод не должен дублироваться. Если вы возвращаете типObject, посмотрите в сторону дженериков. - 3: В
MealServlet/SpringMainв моментsetActiveProfilesконтекст спринга еще не должен быть инициализирован, иначе выставление профиля уже ни на что не повлияет. - 4: Если у метода нет реализации, то стандартно бросается
UnsupportedOperationException. Для уменьшения количества кода при реализации Optional (п. 7, толькоDataJpa) попробуйте сделатьdefaultметод в интерфейсе. - 5: В Data-Jpa метод для ссылки на entity (аналог
em.getReference) -T getOne(ID id) - 6: Проверьте, что в
DataJpaMealRepositoryвсе обращения к DB выполняются в одной транзакции. - 7: Для 7.1
достать по id пользователя вместе с его едойя вUserдобавилList<Meal> meals. Учесть, что у юзера может отсутствовать еда. Ordering a join fetched collection in JPA using JPQL/HQL - 8: Проверьте, что все тесты запускаются из Maven (имена классов тестов удовлетворяют соглашению) и итоги тестов класса выводятся корректно (не копятся). По умолчанию maven-surefire-plugin включает в тесты классы, заканчивающиеся на Test.
- 9:
@ActiveProfilesпринимает в качестве параметра строку либо массив строк. В тестах можно задавать несколько@ActiveProfilesв разных классах, они суммируются - 10:
<beans profile=в конфигурации контекста должны находиться после всех остальных объявлений.



