Skip to content

Latest commit

 

History

History
313 lines (228 loc) · 35.3 KB

lesson06.md

File metadata and controls

313 lines (228 loc) · 35.3 KB

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

correction Правка

Apply 6_0_fix.patch

Поменял зависимости jaxb-runtime для jdk11 и поправил переменную

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

Apply 6_01_HW5_data_jpa.patch

В get получаем и фильтруем Optional<Meal>

Транзакция начинается, когда встречается первый @Transactional. С default propagation REQUIRED остальные @Transactional просто участвуют в первой. Поэтому ставим ее сверху DataJpaMealRepositoryImpl.save(), чтобы все обращения к базе внутри метода были в одной транзакции. Анологично, если из сервиса собирается несколько запросов в репозитории, он ставится над методом сервиса.

Apply 6_02_HW5_profile_test.patch

Для IDEA в spring-db.xml не забудте выставить Spring Profiles: например datajpa, postgres

  • DbTest переименован в AbstractServiceTest и сюда перенес @ActiveProfiles(resolver = ActiveDbProfileResolver.class)
  • Заменил description.getMethodName() на getDisplayName() в выводе результатов тестов. После printResult() буфер сбрасывается в 0, чтобы не накапливать изменения.

Apply 6_03_extract_rules.patch

Вынес измерение времени и сводку в отдельный класс TimingRules

JUnit Rules External Resources

Apply 6_04_HW5_optional_fix_jdbc_profiles.patch

Сделал классы Java8JdbcMealRepositoryImpl и TimestampJdbcMealRepositoryImpl внутренними

Apply 6_05_update_hsqldb.patch

В реальном проекте часто проблему можно решить простым обновлением версии: new HSQLDB version supports Java 8 time API

Apply 6_06_HW5_optional_fetch_join.patch

  • Добавил проверки и тесты на NotFound для MealService.getWithUser и UserService.getWithMeals
  • Убрал CascadeType.REMOVE, в уроке далее будет про Cascade.

Apply 6_07_HW5_graph_batch_size.patch

Сделал @EntityGraph через attributePaths

Когда мы достаем всех юзеров с ролями без @BatchSize делается запрос юзеров (1) и на каждого юзера идет в базу запрос ролей (+N). C @BatchSize(size = 200) делается запрос на юзеров (1) и затем роли достаются пачками для 200 юзеров (+ N/200).

Занятие 6:

Добавил тесты на валидацию

  • к сожалению в JUnit нет ExpectedException.expectRootCause, а expectCause нам не подходит. В 13 JUnit обещают expectThrows(), а пока сделал вручную: AbstractServiceTest.validateRootCause()

Откуда у нас берется ConstraintViolationException в тестах на валидацию? Для каких наших исключений он является рутом?

Прежде всего - пользуйтесь дебагом! Исключение легко увидеть в методе getRootCause(). Если подебажить выполение Hibernate валидации, то можно найти, где обрабатываются аннотации валидации и место в org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(), где бросается ConstraintViolationException.

Apply 6_08_add_test_validation.patch

Тесты валидации для Jdbc не работают, нужно будет починить в HW6 (в реализация Jdbc валидация отсутствует)

Кэш мигрировал на 3.x

Apply 6_09_hibernate_cache.patch

Теперь уже все Jdbc тесты поломались. Требуется починить в HW6

Apply 6_10_cascade_ddl.patch

Cascading

Есть SQL ON .. CASCADE, которая выполняется в базе данных и есть аннотация в Hibernate, исполняемая в приложении

Auto schema generation

Apply 6_11_spring_web.patch

  • Для сборки проекта в окне Maven отключите тесты (Toggele 'Skip Tests' Mode)
  • В web.xml задаются профили запуска по умолчанию: <param-value>postgres,datajpa</param-value>.

Если запускаетесь под HSQLDB, надо поменять на hsqldb,datajpa.

Apply 6_12_jsp_jstl_i18n.patch

Поменял users\meals в ключах локализации на user\meal. Понадобится при локализации ошибок (сделаем позже)

Переключение автоматического показа ASCII-кодов в IDEA (JSTL локализация кириллицы требует ASCII кодов)

image

-Dspring.profiles.active="datajpa,postgres"

С плагином мы можем сконфигурировать Tomcat приямо в pom.xml и запустить его с задеплоенным туда нашим приложением WAR из командной строки без IDEA и без инсталляции Tomcat. По умолчанию он скачивает его из центрального maven репозитория (можно также указать свой в <container><home>${container.home}</home></container>). При запуске Tomcat из IDEA запускается Tomcat, путь к которому мы прописали в конфигурации запуска (со своими настройками).

Apply 6_13_tomcat_pool_jndi_cargo.patch

  • для запуска в Tomcat 9 поменял tomcat7-maven-plugin на cargo-maven2-plugin.
  • плагин сконфигурирован под postgres. Для HSQLDB нужно скорректировать dependencies и driverClassName в context.xml

Томкат сам управляет пулом коннектов ? На каждый запрос в браузере будет даваться свой коннект?

Да, в томкате есть реализация пула коннектов tomcat-jdbc (мы его подключаем со scope=provided). Если запускаемся с профильем tomcat, приложение на каждую транзакцию (или операцию не в транзакции) берет коннект к базе из пула, сконфигурированного в подкладываемом tomcat context.xml.

Запуск из коммандной строки:

 mvn clean package -DskipTests=true org.codehaus.cargo:cargo-maven2-plugin:1.7.0:run

Приложение деплоится в application context topjava: http://localhost:8080/topjava

Apply 6_14_spring_webmvc.patch

  • Починил путь к корню
  • В Spring 4.3 ввели новые аннотации @Get/Post/...Mapping (сокращенный вариант @RequestMapping)

Настройки Project Structure->Modules->Spring:

image

В web.xml мы инициализируем DispatcherServlet, передавая ему параметром spring-mvc.xml. Получается, что DispatcherServlet парсит spring-mvc.xml и находит в нем context?

Да, можно подебажить родителя FrameworkServlet.initWebApplicationContext(). После инициализации сервлет DispatcherServlet раскидывает все запросы по контроллерам (бинам контекста спринга). См паттерн Front Controller.

Apply 6_15_spring_i18n.patch

  • В локализации поменял fmt:message на spring:message
  • Выбор языка зависит от языка операционной системы и хедера Accept-Language. Добавил в spring-mvc.xml messageSource параметр fallbackToSystemLocale. Он управляет выбором, куда переключаться при выборе en и отсутствии app_en.properties: локаль операционной системы или app.properties (fallbackToSystemLocale=false). Переключение локалей будем реализовывать в конце проекта.

Для тестирования локали можно поменять Accept-Language. Для хрома в chrome://settings/languages перетащить нужную локаль наверх.

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

Кэш hibernate надстраивается над ehcache или он живет самостоятельно?

  • Understanding Hibernate Caching: Hibernate supports following open-source cache implementations out-of-the-box: EHCache (Easy Hibernate Cache), OSCache (Open Symphony Cache), Swarm Cache, and JBoss Tree Cache.

Где конфигурится интернализация для jstl (т.е. файл, где задаются app, app_ru.properties)? Достаточно указать в страницах бандл и путь в ресурсы?

<fmt:setBundle basename="messages.app"/> означает что ресурсы будут искаться в classpath:messages/app(_xx)/properties: Tag setBundle: fully-qualified resource name, which has the same form as a fully-qualified class name. После сборки проекта maven их можно найти в target/classes или target/topjava/WEB-INF/classes.

Отлично, что она все пишет на том языке, который пришел в хидере запроса. А если я хочу выбрать?

Выбор языка зависит от языка операционной системы и хедера Accept-Language. Параметр fallbackToSystemLocale, который управляет выбором, когда с Accept-Language: en,en-US; не находится локализация app_en.properties. Для переключения локали используется JSTL Format Tag fmt:setLocale. Мы будем реализовывать переключение локалей в Spring i18n в конце проекта.

Мы создаем бин, где получаем dataSource по имени <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/topjava"/>. Но там не указан класс, как в других dataSource? Получается по имени jdbc/topjava нам уже отдает готовый обьект dataSource и мы как бы помещаем его в бин?

Здесь используется namespace jee:jndi-lookup, который прячет под собой классы реализации. JNDI объект DataSource конфигурируется в src/main/resources/tomcat/context.xml

В плагине прописан профиль <spring.profiles.active>tomcat,datajpa</spring.profiles.active>, а в web.xml <param-value>postgres,datajpa</param-value>. Какой же реально отрабатывает?

См. видео урока "Динамическое изменение профиля при запуске". В плагине мы задаем параметры JVM запуска Tomcat

Почему мы не используем элемент <context:annotation-config/> в spring-db.xml?

В проекте у нас сейчас 2 Spring контекста: spring-mvc.xml (см. web.xml, DispatcherServlet) и родительский spring-app.xml + spring-db.xml (web.xml, contextConfigLocation). Грубо: 2 мапы, причем для mvc доступно все что есть в родителе. Те spring-db.xml не является отдельным самостоятельным контекстом и достаточно того, что <context:annotation-config/> у нас есть в spring-app.xml.

A @NamedQuery или @Query подвержены кешу запросов? Т.е. если мы поставим USE_QUERY_CACHE_value="true" будет Hibernate их кешировать?

Чтобы запрос кэшировался, кроме true в конфигурации нужно еще явно выставить запросу setCacheable (http://vladmihalcea.com/2015/06/08/how-does-hibernate-query-cache-work/). По поводу кэширования @NamedQuery нашел @QueryHint: https://docs.jboss.org/jbossas/docs/Clustering_Guide/5/html/ch04s02s03.html

Почему messages мы кладем в config и используем system environment? разве так делают в реальном проекте? не будешь же вписывать на сервере эти переменные каждый раз, если проект куда-то будет переезжать. Можно по другому, кроме systemEnvironment['TOPJAVA_ROOT'] задать путь от корня проекта?

  1. messages нам нужны в runtime (при работе приложения). Проект к собранному и задеплоенному в Tomcat war отношения никакого уже не имеет и на этом сервере он обычно не находится. Если ресурсы нужны только при сборке и тестировании, то путь к корню для одномодульного maven проекта можно задать как ${project.basedir}, но для многомодульного проекта (а все реальные проекты многомодульные) это путь к корню своего модуля.

  2. В "реальном приложении" делается совершенно по разному:

    • нести с собой в classpath, но ресурсы нельзя будет динамически (без передеплоя) обновлять
    • класть в war (не в classpath) и обновлять в развернутом TOMCAT_HOME/webapps/[appname]/...
    • класть в зафиксированное определенное место (например в home: ~ или в путь от корня /app/config). Можно задавать фиксированный пусть в пропертях профиля maven и фильтровать ресурсы (maven resources), чтобы они попали в проперти проекта.
    • делать через переменную окружения, как у нас
    • задавать в параметрах запуска JVM как системную переменную через -D..
    • располагать в преференсах (для unix это home, для windows- registry): использование Preferences API
    • держать настройки в DB

    Часто в одном приложении используют несколько способов для разных видов конфигураций.

Не происходит ли дублирования при кэшировании пользователей чрез Hibernate и @Cacheable ?

@Cacheable кэширует результат запроса getAll(), те список юзеров. Hibernate кэширует юзеров по отдельности, те, грубо мапа, id->User. Те можно назвать это дублированием. Нужно ли будет такое в реальном приложении - все смотрится из логики запросов и их частоты, вполне вероятно что нет. Как то мы писали приложение для Дойчебанка (аналог skype на GWT, те на экране небольшое окошко)- там было 5!! уровней кэширования, первый вообще в базе.

У меня стоит томкат 8 версии, в помнике у нас 9 прописан, но всё работает. Почему?

В pom.xml мы подключаем tomcat-servlet-api со scope=provided, что означает что он используется только для компиляции и не идет в war. Тк мы не используем никаких фич Tomcat 9.x, то наш код совместим с Tomcat 8.x. При запуске через cargo-maven2-plugin Tomcat 9 загружается из maven репозитория.

Откуда @Transactional вытягивает класс для работы с транзакцией, в составе какого бина он идет?

  1. Если в контексте Spring есть <tx:annotation-driven/>, то подключается BeanPostProcessors, который проксирует классы (и методы), помеченные @Transactional.
  2. По умолчанию для TransactionManager используется бин с id=transactionManager

hw Домашнее задание HW06

  • 1.1 Починить тесты InMemoryAdminRestControllerSpringTest/InMemoryAdminRestControllerTest (в новой версии Spring классы spring-mvc требуют WebApplicationContext, поэтому поправьте inmemory.xml)
  • 1.2 Починить Jdbc тесты (валидацию исключить)
  • 1.3 Удалить сервлеты и перенести функциональность MealServlet в JspMealController контроллер (по аналогии с RootController). MealRestController у нас останется, с ним будем работать позже.
    • 1.3.1 разнести запросы на update/delete/.. по разным методам (попробуйте вообще без action=). Можно по аналогии с RootController#setUser принимать HttpServletRequest request (аннотации на параметры и адаптеры для LocalDate/Time мы введем позже).
    • 1.3.2 в одном контроллере нельзя использовать другой. Чтобы не дублировать код можно сделать наследование контроллеров от абстрактного класса.
    • 1.3.3 добавить локализацию и jsp:include в mealForm.jsp / meals.jsp

Optional

  • 2.1 Добавить транзакционность (DataSourceTransactionManager) в Jdbc реализации
  • 2.2 Добавить еще одну роль к юзеру Admin (будет 2 роли: ROLE_USER, ROLE_ADMIN)
  • 2.3 Добавить проверку ролей в UserTestData.assertMatch
  • 2.4 Починить тесты в JdbcUserRepositoryImpl (добавить роли). Доставать можно двумя способами: одним запросом с JOIN либо двумя запросами: отдельно users и отдельно roles.
    • 2.4.1 В реализации getAll НЕ делать запрос ролей для каждого юзера (N+1 select)
    • 2.4.2 При save посмотрите на batchUpdate()
  • Объяснение SQL JOIN

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

  • 1: Неверная кодировка UTF-8 с Spring обычно решается фильтром CharacterEncodingFilter:
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • 2: Если не поднимается контекст Spring, смотрим причину в верху самого нижнего эксепшена. Все ошибки на отсутствия бина в контексте или его нескольких реализациях относятся к пониманию основ: Spring application context. Если нет понимания этих основ, двигаться дальше нельзя, нужно вернуться к видео Спринг, где объясняется что это такое. Также пересмотрите видео Тестирование UserService через AssertJ. Начиная с 11.30 как раз разбираются подобные ошибки.
  • 3: Если неправильно формируется url относительно контекста приложения (например /topjava/meals/meals), посмотрите на
  • 4: При проблемах с запуском томкат проверьте запущенные java процессы, нет ли в TOMCAT_HOME\webapps приложения каталога topjava, логи tomcat - нет ли проблем с доступом к каталогам или контекстом Spring.
  • 5: Если создаете List с одним значением или Map с одним ключом-значением, пользуйтесь Collections.singleton..
  • 6: В MealController общую часть @RequestMapping(value = "/meals") лучше вынести на уровень класса
  • 7: Не забывайте при реализации JdbcUserRepositoryImpl про Map.computeIfAbsent и EnumSet
  • 8: Проверьте @Transactional(readOnly = true) сверху Jdbc..RepositoryImpl
  • 9: Проверьте, что config\messages\app_ru.properties у вас в кодировке UTF-8 (в любом редакторе/вьюере или при отключенном Transparent native-to-ascii conversion в IDEA). ASCII коды нужны были только для JSP.