Skip to content
Vladimir edited this page Feb 9, 2019 · 1 revision

Онлайн проект Topjava

Для запуска Tomcat под JDK11 проверьте переменную окружения JAVA_HOME

Apply 5_0_jdk_11.patch

hw Разбор домашнего задания HW4

EntityManager это по сути прокси обертка над Hibernate Session, которая создается каждый раз при открытии транзакции.

  • Дополнительно (ни разу не сталкивался): еще есть редкий случай ручного управления @PersistenceContext(type = PersistenceContextType.EXTENDED), когда он используется в нескольних транзакциях (long-running session or session-per-conversation).

Apply 5_1_HW4.patch

  • При сравнении еды тесты падают, тк Hibernate делает ленивую обертку к user и если происходит обращение к любому его полю кроме id вне транзакции бросается LazyInitializationException. По логике приложения поле user в еде не нужно и мы не будем его отдавать наружу: в тестах исключаем user из сравнения.
  • IDEA ругается на BETWEEN, в Meal добавил @SuppressWarnings("JpaQlInspection"). Other warnings
  • Поменял реализацию JpaMealRepositoryImpl.get() (вместо @NamedQuery), реализация стали проще

Apply 5_2_fix_hibernate_issue.patch


Переопределять equals()/hashCode() необходимо, если

  • использовать entity в Set (рекомендовано для many ассоциаций), либо как ключи в HashMap
  • использовать reattachment of detached instances (те манипулировать одним Entity в нескольких транзакциях/сессиях).

Implementing equals() and hashCode()

Оптимально использовать уникальные бизнес поля, но обычно таких нет, и чаще всего, используются PK с ограничением, что он может быть null у новых объектов и нельзя объекты сравнивать через equals() and hashCode() в бизнес-логике (например тестах).

equals() and hashcode() when using JPA and Hibernate


question Почему над AbstractBaseEntity стоит @Access(AccessType.FIELD) ? Почему при запросе user.id нам не нужно вытаскивать его из базы?

AccessType.FIELD делает доступ в AbstractBaseEntity и всех классах-наследниках по полям. При загрузке Meal Hibernate на основе поля meal.user_id делает ленивую прокcи к User, у которой нет ничего, кроме id.

Apply 5_3_HW4_optional.patch

Занятие 5:

Раскрасил лог (в Spring Boot по умолчанию он тоже colored)

Apply 5_4_log_colored.patch

Apply 5_5_profiles_connection_pool.patch

  • Галочка в XML профиле влияет только на отображение в IDEA и никак на выполнение кода.
  • tomcat-jdbc со scope=provided не работает в Tomcat 7.x, поставьте Tomcat 8.x
  • Profiles.ACTIVE_DB задает активный профиль базы (postgres/hsqldb)
  • Profiles.REPOSITORY_IMPLEMENTATION определяет реализацию репозитория при запуске приложения (для тестов задаются через @ActiveProfiles).

Для переключения на HSQLDB необходимо:

  • поменять в окне Maven Projects профиль (Profiles) на hsqldb и сделать Reimport All Maven Projects (1я кнопка)
  • поменять Profiles.ACTIVE_DB = HSQLDB
  • почистить проект mvn clean (фаза clean не выполняется автоматически, чтобы каждый раз не перекомпилировать весь проект)

Для корректного отображения неактивного профиля в IDEA проверьте флаг Inactive profile highlighting и сделайте проекту clean

image

Вопрос: почему после этого патча не поднимется Spring при запуске приложения в Tomcat? (будем чинить в ДЗ п.6)

Автоматический выбор профиля базы: ActiveProfilesResolver

Apply 5_6_profile_resolver.patch

Сделал автоматический выбор профиля базы при запуске приложения (тестов) в зависимости от присутствия драйвера базы в classpath (Profiles.getActiveDbProfile())

Apply 5_7_spring_data_jpa.patch

  • Переименовал классы Proxy на более адекватные Crud
  • В spring-framework-bom мы уже задали версию Spring. Убрал из остальных зависимостей.
  • В spring-data-jpa 2.x поменялся интерфейс: T CrudRepository.findOne(ID id) -> Optional<T> CrudRepository findById(ID id)

question Вопросы:

Зачем мы переопределяем @Override Meal save в CrudUserRepository. Без этого все работает.

Можно не переопределять. Сделал только для явного указания всех используемых методов из наследуемого CrudRepository

Какой паттерн проектирования применён в классе DataJpaUserRepositoryImpl (декоратор/адаптер/другой)?:

Вопрос интересный:) Я бы назвал это композицией с делегированием. Если бы стояла бизнес задача преобразовать UserRepository в CrudUserRepository то это был бы адаптер (у нас нет бизнес задачи, CrudUserRepository - внутреняя фича нашей реализации data-jpa) Делегат интерфейсов не меняет, а прокси похож на делегата, но служит неявной подмены (часто прямо в рантайм). См. ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ

Apply 5_8_spring_cache.patch

Apply 5_9_jdk11_fix.patch

Поправил версию JDK в .travis.yml

question Ваши вопросы

В 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: Имплементировать DataJpaMealRepositoryImpl
  • 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=)!!.
  • 3: Сделать тесты всех реализаций (jdbc, jpa, datajpa) через наследование (без дублирования)
    • 3.1 сделать один базовый класс для MealServiceTest и UserServiceTest.
    • 3.2 сводку по времени выполнения тестов также сделать для user
  • 4: Запустить все тесты: mvn test (в IDEA Maven Lifecycle - test, кнопку skipTest отжать)

Optional

  • 5: Разделить JdbcMealRepositoryImpl для HSQLDB (она не умеет работать с Java8 Time API) и Postgres через @Profile (для Postgres оставить LocalDateTime). Цель задания - потренироваться с паттерном шаблонный метод и профилями Spring. Бины Spring мы разделяем (фильтруем) по разным профилям с помощью beans profile в xml конфигурации и @Profile (те мы конфигурируем, какие бины попадут в контекст Spring в зависимости от активных профилей приложения). Абстрактные классы не создаются и в контекст не попадают. Профили, заданные в @Profile пересекаются с активными профилями приложения: если пересечение есть, то бин включается в контекст (cм. реализацию @Profile и в ней ProfileCondition, можно подебажить). Например при сконфигуренном @Profile({"postgres","jdbc"}) бин попадет в контекст, если в профилях запущенного приложения есть хотя бы один из них (например "jdbc"). После выполнения разделения можно предложить решение проще.
  • 6: Починить MealServlet и использовать в SpringMain реализацию DB: добавить профили. Попробуйте поднять Spring контекст без использования spring.profiles.active.
  • 7: Сделать и протестировать в сервисах методы (тесты и реализация только для DataJpa)
    • 7.1: достать по id пользователя вместе с его едой
    • 7.2: достать по id еду вместе с пользователем
    • 7.3: обращения к DB сделать в одной транзакции (можно сделать разные варианты). Java Persistence/OneToMany

error Подсказки по HW05

  • 1: Для того, чтобы не запускались родительские классы тестов нужно сделать их abstract
  • 2: В реализациях JdbcMealRepository код не должен дублироваться. Если вы возвращаете тип Object, посмотрите в сторону дженериков.
  • 3: В MealServlet/SpringMain в момент setActiveProfiles контекст спринга еще не должен быть инициализирован, иначе выставление профиля уже ничего не будет делать.
  • 4: Если у метода нет реализации, то стандартно бросается UnsupportedOperationException.
  • 5: Для уменьшения количества кода при реализации Optional (п. 7, только DataJpa) попробуйте сделать default метод в интерфейсе
  • 6: В Data-Jpa метод для ссылки на entity (аналог em.getReference) : T getOne(ID id)
  • 7: Проверьте, что в DataJpaMealRepositoryImpl все обращения к DB выполняются в одной транзакции
  • 8: Для достать по id пользователя вместе с его едой я в User добавил List<Meal> meals
  • 9: Проверьте, что все тесты запускаются из Maven (имена классов тестов удовлетворяют соглашению) и итоги тестов класса выводятся корректно (не копятся)
  • 10: @ActiveProfiles принимает в качестве параметра строку, либо массив строк. В тестах можно задавать несколько @ActiveProfiles в разных классах, они суммируются
  • 11: В релизации 7.1 учесть, что у юзера может отсутствовать еда
  • 12: Ordering a join fetched collection in JPA using JPQL/HQL
  • 13: <beans profile= в конфигурации контекста должны находиться после всех остальных объявлений.
  • 14: По умолчанию maven-surefire-plugin включает в тесты классы, заканчивающиеся на Test.

Clone this wiki locally