Онлайн проекта Topjava
Поменял зависимости
jaxb-runtime
для jdk11 и поправил переменную
В
get
получаем и фильтруемOptional<Meal>
Транзакция начинается, когда встречается первый @Transactional
. С default propagation REQUIRED
остальные @Transactional
просто участвуют в первой. Поэтому ставим ее сверху DataJpaMealRepositoryImpl.save()
, чтобы все обращения к базе внутри метода были в одной транзакции. Анологично, если из сервиса собирается несколько запросов в репозитории, он ставится над методом сервиса.
Для IDEA в spring-db.xml
не забудте выставить Spring Profiles: например datajpa, postgres
DbTest
переименован вAbstractServiceTest
и сюда перенес@ActiveProfiles(resolver = ActiveDbProfileResolver.class)
- Заменил
description.getMethodName()
наgetDisplayName()
в выводе результатов тестов. ПослеprintResult()
буфер сбрасывается в 0, чтобы не накапливать изменения.
Вынес измерение времени и сводку в отдельный класс
TimingRules
JUnit Rules External Resources
Сделал классы
Java8JdbcMealRepositoryImpl
иTimestampJdbcMealRepositoryImpl
внутренними
- Spring Profiles. Spring 4 Conditional.
- зайдите в исходники
@Profile
и посмотрите (подебажте) его реализацию через@Conditional(ProfileCondition.class)
. - дополнительно: реализация через Java Config и Profiles на уровне методов
В реальном проекте часто проблему можно решить простым обновлением версии: new HSQLDB version supports Java 8 time API
- Добавил проверки и тесты на
NotFound
дляMealService.getWithUser
иUserService.getWithMeals
- Убрал
CascadeType.REMOVE
, в уроке далее будет про Cascade.
Сделал
@EntityGraph
черезattributePaths
- N+1 selects issue
- Using Named Entity Graphs
- Стратегии загрузки коллекций в JPA
- Стратегии загрузки коллекций в Hibernate
Когда мы достаем всех юзеров с ролями без
@BatchSize
делается запрос юзеров (1) и на каждого юзера идет в базу запрос ролей (+N). C@BatchSize(size = 200)
делается запрос на юзеров (1) и затем роли достаются пачками для 200 юзеров (+ N/200).
- к сожалению в JUnit нет
ExpectedException.expectRootCause
, аexpectCause
нам не подходит. В 13 JUnit обещаютexpectThrows()
, а пока сделал вручную:AbstractServiceTest.validateRootCause()
Откуда у нас берется ConstraintViolationException в тестах на валидацию? Для каких наших исключений он является рутом?
Прежде всего - пользуйтесь дебагом! Исключение легко увидеть в методе getRootCause()
. Если подебажить выполение Hibernate валидации, то можно найти, где обрабатываются аннотации валидации и место в org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate()
, где бросается ConstraintViolationException
.
Тесты валидации для Jdbc не работают, нужно будет починить в HW6 (в реализация Jdbc валидация отсутствует)
Кэш мигрировал на 3.x
Теперь уже все Jdbc тесты поломались. Требуется починить в HW6
- Уровни кэширования Hibernate
- Hibernate Cache. Практика
- Hibernate - Caching
- Починка тестов: инвалидация кэша Hibernate
- Hibernate User Guide: Caching
- Hibernate 5, Ehcache 3.x
- Ресурсы:
Есть SQL ON .. CASCADE, которая выполняется в базе данных и есть аннотация в Hibernate, исполняемая в приложении
- Do not use
CascadeType
for @ManyToOne - CascadeType meaning
- No cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
- Create ON DELETE CASCADE:
@OnDelete
- Hibernate second level cache and ON DELETE CASCADE in database schema
orphanRemoval=true
vsCascadeType.REMOVE
- JPA
cascade/orphanRemoval
doesn't work withNamedQuery
- JPA DATABASE SCHEMA GENERATION
- hbm2ddl.auto and autoincrement
- Hibernate/JPA DB Schema Generation Best Practices
5. Spring Web
- Для сборки проекта в окне Maven отключите тесты (
Toggele 'Skip Tests' Mode
)- В
web.xml
задаются профили запуска по умолчанию:<param-value>postgres,datajpa</param-value>
.
Поменял
users\meals
в ключах локализации наuser\meal
. Понадобится при локализации ошибок (сделаем позже)
Переключение автоматического показа ASCII-кодов в IDEA (JSTL локализация кириллицы требует ASCII кодов)
-Dspring.profiles.active="datajpa,postgres"
С плагином мы можем сконфигурировать Tomcat приямо в pom.xml
и запустить его с задеплоенным туда нашим приложением WAR из командной строки
без IDEA и без инсталляции Tomcat. По умолчанию он скачивает его из центрального maven репозитория (можно также указать свой в <container><home>${container.home}</home></container>
).
При запуске Tomcat из IDEA запускается Tomcat, путь к которому мы прописали в конфигурации запуска (со своими настройками).
- для запуска в 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
- Cargo Maven2 plugin
- Катомизация context.xml в cargo-maven2-plugin
- Tomcat JNDI Resources
- BasicDataSource Configuration
- Починил путь к корню
- В Spring 4.3 ввели новые аннотации
@Get/Post/...Mapping
(сокращенный вариант@RequestMapping
)
- Spring Web MVC
- ContextLoaderListener vs DispatcherServlet
- Паттерн Front Controller
- Иерархия контекстов в Spring Web MVC
- Сценарий обработки запроса. HandlerMappings
- View Resolution: прячем jsp под WEB-INF.
- HandlerMapping: SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping
- Маппинг ресурсов.
- Ресурсы:
Настройки
Project Structure->Modules->Spring
:
В
web.xml
мы инициализируемDispatcherServlet
, передавая ему параметромspring-mvc.xml
. Получается, чтоDispatcherServlet
парситspring-mvc.xml
и находит в нем context?
Да, можно подебажить родителя FrameworkServlet.initWebApplicationContext()
. После инициализации сервлет DispatcherServlet
раскидывает все запросы по контроллерам (бинам контекста спринга). См паттерн Front Controller.
- Spring локализация кириллицы делается в UTF-8 (НЕ требует ASCII кодов)
- Убедитесь что в настройках IDEA кодировка везде UTF-8
- В локализации поменял
fmt:message
наspring:message
- Выбор языка зависит от языка операционной системы и хедера
Accept-Language
. Добавил вspring-mvc.xml
messageSource
параметрfallbackToSystemLocale
. Он управляет выбором, куда переключаться при выборе en и отсутствииapp_en.properties
: локаль операционной системы илиapp.properties
(fallbackToSystemLocale=false
). Переключение локалей будем реализовывать в конце проекта.
Для тестирования локали можно поменять Accept-Language
. Для хрома в chrome://settings/languages
перетащить нужную локаль наверх.
Кэш 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'] задать путь от корня проекта?
-
messages нам нужны в runtime (при работе приложения). Проект к собранному и задеплоенному в Tomcat war отношения никакого уже не имеет и на этом сервере он обычно не находится. Если ресурсы нужны только при сборке и тестировании, то путь к корню для одномодульного maven проекта можно задать как
${project.basedir}
, но для многомодульного проекта (а все реальные проекты многомодульные) это путь к корню своего модуля. -
В "реальном приложении" делается совершенно по разному:
- нести с собой в 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
вытягивает класс для работы с транзакцией, в составе какого бина он идет?
- Если в контексте Spring есть
<tx:annotation-driven/>
, то подключаетсяBeanPostProcessors
, который проксирует классы (и методы), помеченные@Transactional
. - По умолчанию для TransactionManager используется бин с
id=transactionManager
- 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
- 1.3.1 разнести запросы на update/delete/.. по разным методам (попробуйте вообще без
- 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()
- 2.4.1 В реализации
- Объяснение SQL JOIN
- 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.