From 572d0459e0c109ad051119757ef65d11b70f6302 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 18 Feb 2020 11:51:18 +0300 Subject: [PATCH 001/220] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 228b708b7094..ab4d97e44213 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ Java Enterprise Online Project - Выполнить задание и залить на GitHub (commit + push) - Переключиться в основную ветку проекта master. +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/123XyBYVeKLC3ZcRr_dUkwyvO9NC6WLkY/view?usp=sharing) +- [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика)) + ## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 ``` Реализовать метод `UserMealsUtil.filteredByCycles` через циклы (`forEach`): From 796d81b362a3e087d016e90e503fe208eb55740b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 5 Mar 2020 23:56:35 +0300 Subject: [PATCH 002/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab4d97e44213..6b840eac3b03 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Java Enterprise Online Project #### Java (базовые вещи) - Интуит. Программирование на Java - 1й урок MasterJava: Многопоточность -- Основы Java garbage collection +- [Основы Java garbage collection](http://web.archive.org/web/20180831013112/https://ggenikus.github.io/blog/2014/05/04/gc) - Размер Java объектов - Введение в Java Reflection API - Структуры данных в картинках From 80a3c742a574cfaf871982aeedb95fcd62722f66 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 26 Mar 2020 16:02:15 +0300 Subject: [PATCH 003/220] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index a24d881fdeba..60860899fb3c 100644 --- a/cv.md +++ b/cv.md @@ -106,6 +106,8 @@ - Выдели самое главное путем опроса босса и важных коллег. Не распыляйся на мелочи. - [**5 вещей, которые разработчик должен сделать прежде чем попросить о помощи**](https://techrocks.ru/2018/07/16/5-things-a-developer-should-do-before-asking-for-help/) - [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) +- [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) +- [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) ## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) From bee6ffcbffc764b82c1abd2fb4f6ff1087faa0a1 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 23 Apr 2020 14:05:13 +0300 Subject: [PATCH 004/220] Update ReleaseNotes.md --- ReleaseNotes.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index f4e7412eb816..f0cf1ed046f5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,20 @@ # TopJava Release Notes +### Topjava 19 +- Изменилась логика для интервалов времени (исключаем `endTime`) +- Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor` +- В ролях убрал префиксы `ROLE_` ([Role and GrantedAuthority](https://stackoverflow.com/a/19542316/548473)) +- Добавился удобный метод `int AbstractBaseEntity.id()` +- Фикс `Location` в `ProfileRestController.register` +- Фикс валидации `UniqueMailValidator` для REST update без `user.id` +- Заменил `jdbc.initLocation` на полный путь - IDEA не ругается +- В конфигурации `cargo-maven2-plugin` сделал [индивидуальный контекст приложения](https://stackoverflow.com/a/60797999/548473) +- Тесты + - Обновил даты еды на 2020г. + - Зарефакторил тесты сервисов на удаление - `NotFoundException` может бросаться при `delete()` + - В тестах контроллеров вернулся к реализации без обертки над `MockMvcRequestBuilders` + - Для `InMemory` тестов подключаю только `inmemory.xml` (добавил туда необходимую конфигурацию из `spring-app.xml`) + + ### Topjava 18 - В `ErrorType` добавил `HttpStatus status` From 2620c6f93e413d51271a5398e8545f3569036638 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 10 May 2020 23:16:00 +0300 Subject: [PATCH 005/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b840eac3b03..f2777b677f7a 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project - без циклов по другим коллекциям - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (его копиям). + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованому). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка From 3449156db3deb9fb8cb0d4c8d9bd5e8809729304 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 10 May 2020 23:26:52 +0300 Subject: [PATCH 006/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2777b677f7a..eece6c31d014 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project - без циклов по другим коллекциям - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованому). + - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка From 052adf67944cf47671f94293014fbcfce7caa270 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:03:35 +0300 Subject: [PATCH 007/220] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 60860899fb3c..f765942cf700 100644 --- a/cv.md +++ b/cv.md @@ -20,6 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: +-[Interviewing: the most profitable skill you can learn](https://www.pramp.com) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests From 7c31a8ee0c7a6f87064a23a6d69f8b053a4b1cbf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:04:00 +0300 Subject: [PATCH 008/220] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index f765942cf700..51f5a7527ad9 100644 --- a/cv.md +++ b/cv.md @@ -20,7 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: --[Interviewing: the most profitable skill you can learn](https://www.pramp.com) +- [Interviewing: the most profitable skill you can learn](https://www.pramp.com) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests From 54f4ec0d6363ef814f3001f831b484dff60f3a7b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 13 May 2020 22:05:03 +0300 Subject: [PATCH 009/220] Update cv.md --- cv.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cv.md b/cv.md index 51f5a7527ad9..a330bc3c71cb 100644 --- a/cv.md +++ b/cv.md @@ -20,7 +20,7 @@ ### Наши истории (делимся опытом и успехом) ### Тесты/задачи онлайн: -- [Interviewing: the most profitable skill you can learn](https://www.pramp.com) +- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/) - [Java Programming Test](https://tests4geeks.com/java) - game: test Java skills - Codility lesson tests @@ -29,7 +29,6 @@ - Sphere online judge - Codility programmers lessons - Hackerrank practice coding -- [Interviewing: the most profitable skill you can learn (pramp.com)](https://www.pramp.com/) - [start.interviewing.io](https://start.interviewing.io/) ## [Тестовое собеседование, самые спрашиваемые темы](http://javaops.ru/interview/test.html) From 5fe982c3fa982a20b6d31aee74dfc15bfff8ab62 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 17 May 2020 23:21:23 +0300 Subject: [PATCH 010/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eece6c31d014..a91e1e814c5a 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Java Enterprise Online Project ### Полезные ресурсы > ВНИМАНИЕ: -> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](http://danielniko.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает. +> - **ДЗ первого урока будет связано с [созданием небольшого CRUD приложения (в памяти, без DB) на JSP и сервлетах](https://danielniko.wordpress.com/2012/04/17/simple-crud-using-jsp-servlet-and-mysql/)**. Введение будет, но предварительное знакомство не помешает. > - основы JavaSсript необходимы для понимания проекта, начиная с 8-го занятия! Все остальное - опционально. From c2180201f5095afdaf632c3e71681acf44d56014 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:43:39 +0300 Subject: [PATCH 011/220] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a91e1e814c5a..040f70a6e44e 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ Java Enterprise Online Project - Шпаргалка Java Stream API - Алексея Владыкин: Элементы функционального программирования в Java - Yakov Fain о новом в Java 8 -- stream.map vs forEach +- stream.map vs forEach Date: Tue, 19 May 2020 11:45:35 +0300 Subject: [PATCH 012/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040f70a6e44e..f7cefcd90fb8 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Java Enterprise Online Project Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка -- [Полное руководство по Java 8 Stream API в картинках и примерах](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor/) +- [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) - [Руководство по Java 8 Stream API: Collector](https://annimon.com/article/2778#collector) ### Замечания по использованию Stream API: From 3b91874e6df53a7d7fcebb7efa8fc0bf40ad2be9 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:46:05 +0300 Subject: [PATCH 013/220] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f7cefcd90fb8..6a58dbce5545 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,6 @@ Java Enterprise Online Project - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка - - [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) - [Руководство по Java 8 Stream API: Collector](https://annimon.com/article/2778#collector) From 18f0374c537cd28f46b4e1db8f98fc5088f8ac9a Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 19 May 2020 11:47:22 +0300 Subject: [PATCH 014/220] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a58dbce5545..d3a4c1e67cec 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,8 @@ Java Enterprise Online Project - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - возможно дополнительные проходы по частям списка + +Ресурсы: - [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) - [Руководство по Java 8 Stream API: Collector](https://annimon.com/article/2778#collector) From ba240e81eca53c90ca40ec4bfa771393bc8ef71e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 20 May 2020 22:16:40 +0300 Subject: [PATCH 015/220] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a4c1e67cec..8755b22281f5 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ Java Enterprise Online Project - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) -**ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +- **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +- **Плагин уже Git Intergation не требуется и вкладку `Version control` в IDEA переименовали в `Git`** ### Патч [prepare_to_HW0.patch](https://drive.google.com/file/d/1LNPpu9OkuCpfpD8ZJHO-o0vwu49p2i5M) (скачать и положить в каталог вашего проекта) > Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: From 7eb4a67c93f47950a420d561b7de6956c1a9eb1b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 23 May 2020 19:10:30 +0300 Subject: [PATCH 016/220] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8755b22281f5..777992140667 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Java Enterprise Online Project - Git Overview - [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8) - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) + - [Руководство по написанию комментариев в коммитах](https://techrocks.ru/2019/12/02/writing-good-commit-messages) ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) - **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** From efd86a06ca26f316d5a38a58de6b887789c7cd36 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 01:00:39 +0300 Subject: [PATCH 017/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 777992140667..754e9d2cdf25 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Java Enterprise Online Project - 10: `System.out.println` нельзя делать нигде, кроме как в `main`. Позже введем логирование. - 11: Результаты, возвращаемые `UserMealsUtil.filteredByStreams` мы будем использовать [в нашем приложении](http://topjava.herokuapp.com/) для фильтрации по времени и отображения еды правильным цветом. - 12: Обращайте внимание на комментарии к вашим коммитам в git. Они должны быть короткие и информативные (лучше на english) -- 13: Не полагайтесь в решении на то, что список будет подаваться отсортированным. Такого условия нет. +- 13: Не полагайтесь в решении на то, что список еды будет подаваться отсортированным. Такого условия нет. ----- ## [Пример 7-го занятия онлайн стажировки, несколько видео открыто](https://github.com/JavaOPs/topjava/blob/master/doc/lesson07.md) From 628eb10dea9635fd23acb08b6477f897d14426d8 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 13:32:50 +0300 Subject: [PATCH 018/220] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 6e4af3e023cc..c1cb45c337b2 100644 --- a/graduation.md +++ b/graduation.md @@ -66,6 +66,7 @@ _Антуан де Сент-Экзюпери_ - 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB - 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! +- 24: Если задание на English, `readme.md` тоже пишите на English. То же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English. ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From c7a9d42c53c720477944de607b83e722ef3fb750 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 24 May 2020 13:37:51 +0300 Subject: [PATCH 019/220] Update graduation.md --- graduation.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index c1cb45c337b2..60fdff4b9d1d 100644 --- a/graduation.md +++ b/graduation.md @@ -66,7 +66,10 @@ _Антуан де Сент-Экзюпери_ - 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB - 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! -- 24: Если задание на English, `readme.md` тоже пишите на English. То же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English. +- 24: `readme.md`: + - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) + - Требуемые примеры `curl` не прячте- пишите здесь! + - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 7d0145036d3e3e1c69fd053cb6195379aaf9c9ff Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 27 May 2020 13:45:01 +0300 Subject: [PATCH 020/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 754e9d2cdf25..379464cf4f6b 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Java Enterprise Online Project - [Сергей Куксенко — Stream API, часть 1](https://www.youtube.com/watch?v=O8oN4KSZEXE) - [Сергей Куксенко — Stream API, часть 2](https://www.youtube.com/watch?v=i0Jr2l3jrDA) -### Optional 2 (+5 бонусов) +### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - без циклов по другим коллекциям From 6464f3cab8aa6fdfd9732d83ee15e972dc9809b7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 27 May 2020 16:02:09 +0300 Subject: [PATCH 021/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 379464cf4f6b..39f4548d8b6f 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Java Enterprise Online Project ### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - - без циклов по другим коллекциям + - без циклов по другим коллекциям (к ним также относим методы коллекций `addAll()/removeAll()`) - решение должно быть рабочим в общем случае (не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). From 8f2c0376054663ce5c73dcbb68a7a2d3cdab6298 Mon Sep 17 00:00:00 2001 From: JavaOPs Date: Thu, 28 May 2020 11:32:08 +0300 Subject: [PATCH 022/220] Update graduate --- graduation.md | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/graduation.md b/graduation.md index 60fdff4b9d1d..af0f4f74078c 100644 --- a/graduation.md +++ b/graduation.md @@ -26,7 +26,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации -- Если ты закончил [стажировку Topjava](http://javaops.ru/reg/topjava/grd), **cделай новый проект и добавляй туда из Topjava только то что нужно!** Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - **не нужны!** +- **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** - **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** @@ -45,27 +45,36 @@ _Антуан де Сент-Экзюпери_ - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. -- 6: базу лучше взять без установки (H2 или HSQLDB). Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** +- 6: База Данных + - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** + - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. + - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок -- 9: далаем REST API в соответствии с концепцией REST - - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) - - 10 Best Practices for Better RESTful API +- 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) + - **[15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)** + - **10 Best Practices for Better RESTful API** - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) - 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга. -- 11: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) -- 12: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) -- 13: **Историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** -- 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! -- 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. +- 11: не размещайте логику приложения и преобразования в TO в слое доступа к DB +- 12: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) +- 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) +- 14: еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! +- 15: название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. - 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository). -- 17: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). -- 18: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. -- 19: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. +- 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). +- 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. +- 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. - 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! -- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). -- 22: Не размещайте логику приложения и преобразования в TO в слое доступа к DB -- 23: Если используете кэширование, **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! +- 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). +- 22: Кэширование + - необязательно, но желательно + - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! + - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). +- 23: Валидация + - желательна + - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` + - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 24: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! From 79efab669a610cb09e16621ff25d9240046911cf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 31 May 2020 23:26:28 +0300 Subject: [PATCH 023/220] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 39f4548d8b6f..c0d69a45fe7e 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,12 @@ Java Enterprise Online Project ### Optional 2 (+5 бонусов, только после выполнения базового и Optional задания!) Сделать реализацию со сложностью O(N) (обратите внимание на п.13 замечаний): - циклом за 1 проход по `List` - - без циклов по другим коллекциям (к ним также относим методы коллекций `addAll()/removeAll()`) - - решение должно быть рабочим в общем случае (не только при запуске main) + - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`) + - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - - нельзя использовать внешние коллекции, не являющиеся частью коллектора или 2 раза проходить по исходному списку (в том числе модифицированному, например отфильтрованному). - Т.е. в решении не должно быть 2 раза `meal.stream()` (в том числе неявно, в составных коллекторах) - - возможно дополнительные проходы по частям списка + - нельзя использовать внешние коллекции, не являющиеся частью коллектора + - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. + Ресурсы: - [Java 8 Stream API, часть шестая: собственный коллектор](https://easyjava.ru/java/language/java-8-stream-api-chast-shestaya-sobstvennyj-kollektor) From 7664bb38066ab8843696b719a8bdc13a3e138e07 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 4 Jun 2020 18:14:35 +0300 Subject: [PATCH 024/220] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c0d69a45fe7e..f2020bbfcd53 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ Java Enterprise Online Project - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора + - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 2cf32713038aae3a29ed2bb3f49ce8ca75ac9068 Mon Sep 17 00:00:00 2001 From: "admin@javaops.ru" Date: Thu, 4 Jun 2020 18:27:09 +0300 Subject: [PATCH 025/220] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2020bbfcd53..638048568128 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Java Enterprise Online Project > - в `UserMeals/UserMealWithExcess` поля изменились на `private` > - обновил данные `UserMealsUtil.meals` и переименовал некоторые пременные, поля и методы > - добавил `UserMealWithExcess.toString()` и метод для выполнения _Optional домашнего задания_ - +> - метод фильтрации в `TimeUtil` переименовали в `isBetweenHalfOpen` (также изменилась логика сравнения - `startTime` включается в интервал) ## Инструкция по шагам (из видео): - Установить ПО (git, JDK8, IntelliJ IDEA, Maven) From 4331b732f57e2bbef144489026f07581d1fa0d6f Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 17 Jun 2020 15:35:54 +0300 Subject: [PATCH 026/220] Update graduation.md --- graduation.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graduation.md b/graduation.md index af0f4f74078c..4580cd250957 100644 --- a/graduation.md +++ b/graduation.md @@ -67,15 +67,16 @@ _Антуан де Сент-Экзюпери_ - 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. - 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! - 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). -- 22: Кэширование +- 22: обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк +- 23: Кэширование - необязательно, но желательно - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). -- 23: Валидация +- 24: Валидация - желательна - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` -- 24: `readme.md`: +- 25: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) From 64590f1ca576fbc18c743227f61ca33eb21c14f4 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 17 Jun 2020 15:44:52 +0300 Subject: [PATCH 027/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 4580cd250957..3c8d83d87b49 100644 --- a/graduation.md +++ b/graduation.md @@ -39,7 +39,7 @@ _Антуан де Сент-Экзюпери_ - 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** - 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** -- 4: **учитывайте, что пользователей может быть ооочень много, а админов- мало** +- 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** - 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: - 5.1 НЕ надо делать разные профили базы и работы с ней. - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. From 3823848bb585a052d04d6f55c4654e8a6c25b0ea Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 15:47:52 +0300 Subject: [PATCH 028/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 3c8d83d87b49..7dd13808b7e1 100644 --- a/graduation.md +++ b/graduation.md @@ -37,7 +37,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte _Антуан де Сент-Экзюпери_ - 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** -- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу** +- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** - 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** - 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: From f2d884f882826c78d00020049f42a5d81cf1c3ae Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 15:49:23 +0300 Subject: [PATCH 029/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 7dd13808b7e1..34809fc454db 100644 --- a/graduation.md +++ b/graduation.md @@ -61,7 +61,7 @@ _Антуан де Сент-Экзюпери_ - 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) - 14: еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! - 15: название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. -- 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository). +- 16: **Используйте DATA-JPA** (сделайте без лишней делегации, напрямую из сервиса/контроллера дергать Repository). - 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). - 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. - 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. From fd2d8522aab499c9aae6d12832b91d8f48bd68cb Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 16:46:23 +0300 Subject: [PATCH 030/220] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 34809fc454db..d947b113c79f 100644 --- a/graduation.md +++ b/graduation.md @@ -49,6 +49,7 @@ _Антуан де Сент-Экзюпери_ - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** + - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок - 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) From b2b9a19dbf33f24e9a1e532c6d74a498328370e1 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 20 Sep 2020 22:20:46 +0300 Subject: [PATCH 031/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 638048568128..38831adc6f29 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Java Enterprise Online Project - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) - через Stream API за 1 проход по исходному списку `meals.streem()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора - - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной копии) + - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной или преобразованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 7c7e37e548ddc01369ff9448a1a9d390be387141 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Sep 2020 17:37:53 +0300 Subject: [PATCH 032/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index d947b113c79f..208ee73c69cb 100644 --- a/graduation.md +++ b/graduation.md @@ -76,7 +76,7 @@ _Антуан де Сент-Экзюпери_ - 24: Валидация - желательна - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - - проверяйте входные данные при `create/update` в контроллерах! В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` + - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячте- пишите здесь! From 9c995f9b94680892e8e805462de14e1797aebc29 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 24 Sep 2020 14:26:51 +0300 Subject: [PATCH 033/220] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 208ee73c69cb..a4468a0f8ec9 100644 --- a/graduation.md +++ b/graduation.md @@ -50,6 +50,7 @@ _Антуан де Сент-Экзюпери_ - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные + - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок - 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) From 00424a0eaa980dcfcacc6bc6a9bef81b4472b983 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 28 Sep 2020 22:45:15 +0300 Subject: [PATCH 034/220] Update ReleaseNotes.md --- ReleaseNotes.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index f0cf1ed046f5..bf225b9b0647 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,15 @@ # TopJava Release Notes + +### Topjava 20 +- мигрировали на JDK 14 +- в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) +- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) +- в js убрал объект контекст, передаю 3-мя параметрами +- в UI контроллерах убрал префикс `ajax` +- из тестов сервисов убрал `repository`. При проверке через `assertThrows` он не требуется +- в `TestMatcher` сценарии сравнения сделал параметризируемыми (паттерн стратегия) +- добавил `UserTestData.USER_WITH_MEALS_MATCHER` (проверки пользователя сразу с едой) и константу id `NOT_FOUND` + ### Topjava 19 - Изменилась логика для интервалов времени (исключаем `endTime`) - Заменил собственный `MessageUtil` велосипед на спринговый `MessageSourceAccessor` From 61cd81364ee3d2247ef9a53958d23d21a8671f6b Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 28 Sep 2020 22:50:02 +0300 Subject: [PATCH 035/220] Update ReleaseNotes.md --- ReleaseNotes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index bf225b9b0647..b102200d7ac1 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -3,11 +3,11 @@ ### Topjava 20 - мигрировали на JDK 14 - в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) -- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) -- в js убрал объект контекст, передаю 3-мя параметрами +- в `topjava.common.js` в `makeEditable()` вместо объекта контекст передаю 3 параметра - в UI контроллерах убрал префикс `ajax` - из тестов сервисов убрал `repository`. При проверке через `assertThrows` он не требуется - в `TestMatcher` сценарии сравнения сделал параметризируемыми (паттерн стратегия) +- в API добавили `/users/{id}/with-meals` (см. [двунаправленные отношения](https://www.codeflow.site/ru/article/jackson-bidirectional-relationships-and-infinite-recursion)) - добавил `UserTestData.USER_WITH_MEALS_MATCHER` (проверки пользователя сразу с едой) и константу id `NOT_FOUND` ### Topjava 19 From 91841cb912cc4ff8711016f34a166cfe94789469 Mon Sep 17 00:00:00 2001 From: "admin@javaops.ru" Date: Thu, 29 Oct 2020 14:10:49 +0300 Subject: [PATCH 036/220] fix grammar --- ReleaseNotes.md | 2 +- description.md | 125 +++++++++++++++++++++++------------------------- graduation.md | 40 ++++++++-------- 3 files changed, 81 insertions(+), 86 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index b102200d7ac1..65a0c209f4c1 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -29,7 +29,7 @@ ### Topjava 18 - В `ErrorType` добавил `HttpStatus status` -- В PostgreSQL обнаружилась бага: граничное значение `0:00` из за ошибок округления попадает в предыдущий интервал. +- В PostgreSQL обнаружилась бага: граничное значение `0:00` из-за ошибок округления попадает в предыдущий интервал. Мораль: всегда в тестах проверяйте граничные значения. Добавил этот случай в тестовые данные. - Изменил `MealRepository.getBetween` (принимаю `@Nullable LocalDate`). Изменились реализации. - Выделил метод `UserService.prepareAndSave` diff --git a/description.md b/description.md index d2448ca99e57..0619c6d9516d 100644 --- a/description.md +++ b/description.md @@ -1,77 +1,72 @@ #### Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и сохранением в базах данных Postgresql и HSQLDB. -- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. -- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. -- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов. -- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой +- Основное внимание будет уделяться способам решения многочисленных проблем разработки в Spring/JPA, а также структурному (красивому и надежному) java кодированию и архитектуре приложения. +- Каждая итерация проекта закрепляется домашним заданием по реализации схожей функциональности. Следующее занятие начинается с разбора домашних заданий. +- Большое внимание уделяется тестированию кода: в проекте более 100 JUnit тестов. +- Несмотря на относительно небольшой размер, приложение разрабатывается с нуля как большой проект (например, мы используем кэш 2-го уровня Hibernate, настраиваем Jackson для работы с ленивой загрузкой Hibernate, делаем конверторы для типов LocalDateTime (Java 8 time API). - Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. - Т.е на выходе получается не учебный проект, а хорошо маштабируемый шаблон для большого проекта на всех пройденных технологиях. -- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST - контроллеров, насторойка EntityManagerFactory, - выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и - Spring Data Jpa. -- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4 - вместе с Spring Security Test, наиболее удобный для работы с базой проект Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery. +- Разбираются архитектурные паттерны: слои приложения и как правильно разбивать логику по слоям, когда нужно применять Data Transfer Object. То есть на выходе получается не учебный проект, а хорошо масштабируемый шаблон для большого проекта на всех пройденных технологиях. +- Большое внимание уделяется деталям: популяция базы, использование транзакционности, тесты сервисов и REST контроллеров, настройка EntityManagerFactory, выбор реализации пула коннектов. Особое внимание уделяется работе с базой: через Spring JDBC, Spring ORM и Spring Data Jpa. +- Используются самые востребованные на сегодняшний момент фреймворки: Maven, Spring Security 4 вместе с Spring Security Test, наиболее удобный для работы с базой проекта Spring Data Jpa, библиотека логирования logback, реализующая SLF4J, повсеместно используемый Bootstrap и jQuery. #### Демо разрабатываемого приложения ## План проекта (ссылки на некоторые темы открыты для просмотра) ### Архитектура проекта. Персистентность. -- Системы управления версиями -- Java 8: Lambda, Stream API -- Обзор используемых в проекте технологий и инструментов. -- Инструмент сборки Maven. -- WAR. Веб-контейнер Tomcat. Сервлеты. -- Логирование. -- Обзор стандартных библиотек. Apache Commons, Guava -- Слои приложения. Создание каркаса приложения. -- Обзор Spring Framework. Spring Context. -- Тестирование через JUnit. -- Spring Test -- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. -- Настройка Database в IDEA. -- Скрипты инициализации базы. Spring Jdbc Template. -- Spring: инициализация и популирование DB -- ORM. Hibernate. JPA. +- Системы управления версиями +- Java 8: Lambda, Stream API +- Обзор используемых в проекте технологий и инструментов. +- Инструмент сборки Maven. +- WAR. Веб-контейнер Tomcat. Сервлеты. +- Логирование. +- Обзор стандартных библиотек. Apache Commons, Guava +- Слои приложения. Создание каркаса приложения. +- Обзор Spring Framework. Spring Context. +- Тестирование через JUnit. +- Spring Test +- Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. +- Настройка Database в IDEA. +- Скрипты инициализации базы. Spring Jdbc Template. +- Spring: инициализация и популирование DB +- ORM. Hibernate. JPA. - [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw) -- Поддержка HSQLDB -- Транзакции -- Профили Maven и Spring -- Пул коннектов -- Spring Data JPA -- Кэш Hibernate +- Поддержка HSQLDB +- Транзакции +- Профили Maven и Spring +- Пул коннектов +- Spring Data JPA +- Кэш Hibernate ### Разработка WEB -- Spring кэш -- Spring Web -- JSP, JSTL, i18n -- Tomcat maven plugin. JNDI -- Spring Web MVC -- Spring Internationalization -- Тестирование Spring MVC -- REST контроллеры -- Тестирование REST контроллеров. Jackson. -- jackson-datatype-hibernate. Тестирование через матчеры. -- Тестирование через SoapUi. UTF-8 -- WebJars. -- Bootstrap. jQuery datatables. -- AJAX. jQuery. Notifications. -- Spring Security -- Spring Binding/Validation -- Работа с datatables через Ajax. -- Spring Security Test +- Spring кэш +- Spring Web +- JSP, JSTL, i18n +- Tomcat maven plugin. JNDI +- Spring Web MVC +- Spring Internationalization +- Тестирование Spring MVC +- REST контроллеры +- Тестирование REST контроллеров. Jackson. +- jackson-datatype-hibernate. Тестирование через матчеры. +- Тестирование через SoapUi. UTF-8 +- WebJars. +- Bootstrap. jQuery datatables. +- AJAX. jQuery. Notifications. +- Spring Security +- Spring Binding/Validation +- Работа с datatables через Ajax. +- Spring Security Test - [Кастомизация JSON (@JsonView) и валидации (groups)](https://drive.google.com/open?id=0B9Ye2auQ_NsFRTFsTjVHR2dXczA) -- Encoding password -- CSRF (добавление в проект защиты от межсайтовой подделки запроса) -- form-login. Spring Security Taglib -- Handler interceptor -- Spring Exception Handling -- Смена локали -- Фильтрация JSON через @JsonView -- Защита от XSS (Cross Site Scripting) -- Деплой в Heroku -- Локализация datatables, ошибок валидации -- Обработка ошибок 404 (NotFound) -- Доступ к AuthorizedUser -- Собеседование. Разработка ПО +- Encoding password +- CSRF (добавление в проект защиты от межсайтовой подделки запроса) +- form-login. Spring Security Taglib +- Handler interceptor +- Spring Exception Handling +- Смена локали +- Фильтрация JSON с помощью @JsonView +- Защита от XSS (Cross Site Scripting) +- Деплой в Heroku +- Локализация datatables, ошибок валидации +- Обработка ошибок 404 (NotFound) +- Доступ к AuthorizedUser +- Собеседование. Разработка ПО \ No newline at end of file diff --git a/graduation.md b/graduation.md index a4468a0f8ec9..442ce82b8c47 100644 --- a/graduation.md +++ b/graduation.md @@ -11,17 +11,17 @@ Build a voting system for deciding where to have lunch. * Users can vote on which restaurant they want to have lunch at * Only one vote counted per user * If user votes again the same day: - - If it is before 11:00 we asume that he changed his mind. + - If it is before 11:00 we assume that he changed his mind. - If it is after 11:00 then it is too late, vote can't be changed -Each restaurant provides new menu each day. +Each restaurant provides a new menu each day. As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it. ----------------------------- P.S.: Make sure everything works with latest version that is on github :) -P.P.S.: Asume that your API will be used by a frontend developer to build frontend on top of that. +P.P.S.: Assume that your API will be used by a frontend developer to build frontend on top of that. ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации @@ -30,7 +30,7 @@ P.P.S.: Asume that your API will be used by a frontend developer to build fronte - **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** -*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас конечно есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если конечно в ТЗ нет оговорок). Все свои вопросы и предложения и хотелки оформляйете отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например справочник еды)- обязательно напишите в read.me. Как и выбор стратегии кэширования.* +*Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если, конечно, в ТЗ нет оговорок). Все свои вопросы, предложения и хотелки оформляйте отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например, справочник еды) - обязательно напишите в read.me. Как и выбор стратегии кэширования.* > Совершенство достигнуто не тогда, когда нечего добавить, а тогда, когда нечего отнять @@ -40,20 +40,20 @@ _Антуан де Сент-Экзюпери_ - 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. - 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** - 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** -- 5: в проекте (и тестовом задании на работу) в отличие от нашего учебного topjava оставляйте только необходимый для работы приложения код, ничего лишнего: - - 5.1 НЕ надо делать разные профили базы и работы с ней. - - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай. - - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования. - - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. Те.- НИЧЕГО ЛИШНЕГО. +- 5: в проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: + - 5.1 НЕ надо делать разные профили базы и работы с ней + - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай + - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования + - 5.4 Из потребностей приложения (которую надо самим придумать) реализовывать только очевидные сценарии. То есть НИЧЕГО ЛИШНЕГО. - 6: База Данных - берите без установки (H2 или HSQLDB). Одну!! Ваше приложение должно сразу запуститься, **без всяких настроек и переменных окружения** - - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе. + - сделайте индексы к таблицам. Попробуйте обеспечит UNIQUE (один голос пользователя в день, один уникальный пункт меню в день). Следите за порядком полей в индексе - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании - 7: по возможности сделать JUnit тесты - 8: уделяйте внимание обработке ошибок -- 9: далаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) +- 9: делаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) - **[15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)** - **10 Best Practices for Better RESTful API** - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) @@ -62,25 +62,25 @@ _Антуан де Сент-Экзюпери_ - 12: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) - 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) - 14: еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! -- 15: название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил. -- 16: **Используйте DATA-JPA** (сделайте без лишней делегации, напрямую из сервиса/контроллера дергать Repository). -- 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`). -- 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются. -- 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое. -- 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverwrflow в коде обязательна! +- 15: название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил +- 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository) +- 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`) +- 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются +- 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое +- 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverflow в коде обязательна! - 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк - 23: Кэширование - необязательно, но желательно - - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редкозапрашиваемые данные)! + - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редко запрашиваемые данные)! - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). - 24: Валидация - желательна - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: - - Если задание на English, описание пишите также на English (то же самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - - Требуемые примеры `curl` не прячте- пишите здесь! + - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) + - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! From bddd4a8dffcca8b081a25862586b6e09e850a8b5 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 23 Nov 2020 12:02:40 +0300 Subject: [PATCH 037/220] Update cv.md --- cv.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cv.md b/cv.md index a330bc3c71cb..04a98a01039d 100644 --- a/cv.md +++ b/cv.md @@ -109,5 +109,7 @@ - [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) - [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) - +- Типичные ошибки начинающих программистов от JavaRush: + - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) + - [Часть 2](https://javarush.ru/groups/posts/3055-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-2) ## [Отзывы по стажировке Topjava](https://vk.com/topic-74381644_30447246) From 325763b91c1a02b99061b39ede1085dbffda9d09 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 7 Dec 2020 23:33:54 +0300 Subject: [PATCH 038/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38831adc6f29..9636181f4588 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Java Enterprise Online Project - Выполнить задание и залить на GitHub (commit + push) - Переключиться в основную ветку проекта master. -## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/123XyBYVeKLC3ZcRr_dUkwyvO9NC6WLkY/view?usp=sharing) +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Тех.задание: библия или допускаются изменения. Полуоткрытый интервал.](https://drive.google.com/file/d/1BpTzjNFjS0TSekCyt_xvt6YoLvuw5KTZ/view) - [Типы промежутков](https://ru.wikipedia.org/wiki/Промежуток_(математика)) ## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW0 From 52f1f76870923b3715f1d9561547de45bcb1fbdf Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Dec 2020 17:32:32 +0300 Subject: [PATCH 039/220] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 04a98a01039d..cc5ff22da638 100644 --- a/cv.md +++ b/cv.md @@ -88,6 +88,7 @@ - Яндекс агрегатор - HH - LinkedIn +- ХабрКарьера - djinni.co (более актуально для Украины) ## Как выжить на испытательном сроке From 1edcd14a9f5d1c780add0a7f2e56069e711c3b3d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 21 Dec 2020 17:40:10 +0300 Subject: [PATCH 040/220] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index cc5ff22da638..46a3442a81f4 100644 --- a/cv.md +++ b/cv.md @@ -89,8 +89,10 @@ - HH - LinkedIn - ХабрКарьера +- [headz.io](https://app.headz.io/candidates/new) - djinni.co (более актуально для Украины) + ## Как выжить на испытательном сроке - Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. From 5f6d0c40136522bfa67e6b856997f402f295660e Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 16 Jan 2021 00:31:31 +0300 Subject: [PATCH 041/220] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9636181f4588..1bf92ee2cdd5 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Java Enterprise Online Project - Java 8: Lambda выражения - Java 8: Потоки - Pуководство по Java 8 Stream -- Java 8 Stream API в картинках и примерах +- [Полное руководство по Java 8 Stream API в картинках и примерах](https://annimon.com/article/2778) - [7 способов использовать groupingBy в Stream API](https://habrahabr.ru/post/348536) - Лямбда-выражения в Java 8 - A Guide to Java 8 @@ -111,7 +111,6 @@ Java Enterprise Online Project - Алексея Владыкин: Элементы функционального программирования в Java - Yakov Fain о новом в Java 8 - stream.map vs forEach Date: Mon, 18 Jan 2021 00:28:34 +0300 Subject: [PATCH 042/220] Update ReleaseNotes.md --- ReleaseNotes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 65a0c209f4c1..7c03206fe59c 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,15 @@ # TopJava Release Notes +### Topjava 21 +- **добавили документирование REST API: Swagger** +- мигрировали на JDK 15 и используем текстовые блоки +- Вынес `produces = MediaType.APPLICATION_JSON_VALUE` на уровень контроллеров +- Правильно используем [глабальные переменные в js](https://stackoverflow.com/a/5064235/548473) +- Зарефакторил `inputField.tag` +- Тестовые переменные переименовал из UPPERCASE в camelCase +- Из тестов сервисов убрал `throws Exception` (в IDEA больше не генерятся по умолчанию) +- **Мигрировали на Spring Boot 2.4.1** + ### Topjava 20 - мигрировали на JDK 14 - в `@SafeHtml` запрещаем весь html (`whitelistType = NONE`) From c2344fec7ec9f27edb507ec78aa05cbf912be732 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 18 Jan 2021 02:08:41 +0300 Subject: [PATCH 043/220] Update cv.md --- cv.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cv.md b/cv.md index 46a3442a81f4..17e0faf1ee83 100644 --- a/cv.md +++ b/cv.md @@ -111,6 +111,7 @@ - [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) - [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) - [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) +- [Путеводитель по синдрому самозванца](https://dou.ua/lenta/articles/impostor-syndrome-guide-part1/) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) - Типичные ошибки начинающих программистов от JavaRush: - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) From 2ec0a12b5ecbbc6191188ae38d2bd42085578b73 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Mon, 18 Jan 2021 02:10:14 +0300 Subject: [PATCH 044/220] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index 17e0faf1ee83..73ca6a62be71 100644 --- a/cv.md +++ b/cv.md @@ -111,7 +111,7 @@ - [**Советы новичкам**](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) - [ТОП-13 ошибок начинающего программиста](https://proglib.io/p/beginners-fails/) - [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) -- [Путеводитель по синдрому самозванца](https://dou.ua/lenta/articles/impostor-syndrome-guide-part1/) +- [Путеводитель по синдрому самозванца](https://vc.ru/hr/167443-eshche-odin-putevoditel-po-sindromu-samozvanca-korni-prichiny-simptomy-i-posledstviya-chast-1) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) - Типичные ошибки начинающих программистов от JavaRush: - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) From 4be539c0796b7cd3028cd2279259e23cb19624aa Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 22 Jan 2021 15:40:25 +0300 Subject: [PATCH 045/220] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1bf92ee2cdd5..7cae831f37ca 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,11 @@ Java Enterprise Online Project ## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Работа с проектом (выполнять инструкции) - **ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** - **Плагин уже Git Intergation не требуется и вкладку `Version control` в IDEA переименовали в `Git`** + +Для переключения режима отображения изменений из вкладки Commit в Git: Local Changes нужно переключить `Settings/Preferences | Version Control | Commit | Use non-modal commit interface` или в контекстном меню вкладки `Commit`: + +![image](https://user-images.githubusercontent.com/13649199/105491518-72d8f300-5cc7-11eb-8b79-c46382562deb.png) ![image](https://user-images.githubusercontent.com/13649199/105488663-05c35e80-5cc3-11eb-962e-30f403d623e8.png) + ### Патч [prepare_to_HW0.patch](https://drive.google.com/file/d/1LNPpu9OkuCpfpD8ZJHO-o0vwu49p2i5M) (скачать и положить в каталог вашего проекта) > Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: From a779519ad62dc8287b7a9296397a3521c453bdf3 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 28 Jan 2021 00:30:29 +0300 Subject: [PATCH 046/220] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7cae831f37ca..cb332ffa033b 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ Java Enterprise Online Project - A Guide to Java 8 ### Туториалы, разное +- [Открытый курс: Spring Boot + HATEOAS](https://javaops.ru/view/bootjava) - [Что нужно знать о бэкенде новичку в веб-разработке](https://tproger.ru/translations/backend-web-development) - [Туториалы: Spring Framework, Hibernate, Java Core, JDBC](http://proselyte.net/tutorials/) From c91877064f1b5b7554849e02d7479cdabac24219 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Thu, 28 Jan 2021 11:41:19 +0300 Subject: [PATCH 047/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb332ffa033b..71288706bc75 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Java Enterprise Online Project - Оцените Time complexity алгоритма. Если она больше O(N), например O(N*N) или N*log(N), сделайте O(N). ``` - Java 8 Date and Time API -- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов - [Головач: сложность алгоритмов в теме коллекций](https://www.youtube.com/watch?v=Ek9ijOiplNE&feature=youtu.be&t=778) - Time complexity - Временная сложность алгоритма From d026d2cf746fca5b071f3b54422ffeb46dcb3868 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 29 Jan 2021 12:25:27 +0300 Subject: [PATCH 048/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71288706bc75..8a58f7f11632 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Java Enterprise Online Project ## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Замечания к HW0 - 1: Код проекта менять можно! Одна из распространенных ошибок как в тестовых заданиях на собеседовании, так и при работе на проекте, что ничего нельзя менять. Конечно при правках в рабочем проекте обязательно нужно проконсультироваться/проревьюироваться у авторов кода (находится по истории VCS) -- 2: Наследовать `UserMealWithExcess` от `UserMeal` я не буду, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. +- 2: Наследовать `UserMealWithExcess` от `UserMeal` нельзя, т.к. это разные сущности: Transfer Object и Entity. Мы будет их проходить на 2м уроке. Это относится и к зависимости. - 3: Правильная реализация должна быть простой и красивой, можно сделать 2-мя способами: через стримы и через циклы. Сложность должна быть O(N), т.е. без вложенных стримов и циклов. - 4: При реализации через циклы посмотрите в `Map` на методы `getOrDefault` или `merge` - 5: **При реализации через `Stream` заменяйте `forEach` оператором `stream.map(..)`** From 86a6795a6e1997d32c972f53b7a7c045ac55f1dc Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 2 Feb 2021 20:06:39 +0300 Subject: [PATCH 049/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a58f7f11632..470cce9ecab8 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Java Enterprise Online Project - Введение в Java Reflection API - Структуры данных в картинках - Обзор java.util.concurrent.* -- Синхронизация потоков +- Синхронизация потоков - String literal pool - Маленькие хитрости Java - A Guide to Java 8 From dc1eed9977324cd4b5f5d066382edc572aa09317 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 2 Feb 2021 20:10:55 +0300 Subject: [PATCH 050/220] Update description.md --- description.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/description.md b/description.md index 0619c6d9516d..58fb5d2cdf74 100644 --- a/description.md +++ b/description.md @@ -16,24 +16,24 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Системы управления версиями - Java 8: Lambda, Stream API - Обзор используемых в проекте технологий и инструментов. -- Инструмент сборки Maven. +- Инструмент сборки Maven - WAR. Веб-контейнер Tomcat. Сервлеты. - Логирование. - Обзор стандартных библиотек. Apache Commons, Guava - Слои приложения. Создание каркаса приложения. - Обзор Spring Framework. Spring Context. - Тестирование через JUnit. -- Spring Test +- Spring Test - Базы данных. PostgreSQL. Обзор NoSQL и Java persistence solution без ORM. - Настройка Database в IDEA. - Скрипты инициализации базы. Spring Jdbc Template. -- Spring: инициализация и популирование DB +- Spring: инициализация и популирование DB - ORM. Hibernate. JPA. - [Тестирование JPA сервиса через AssertJ](https://www.youtube.com/watch?v=BlyaXT6tOaw) - Поддержка HSQLDB - Транзакции - Профили Maven и Spring -- Пул коннектов +- Пул коннектов - Spring Data JPA - Кэш Hibernate @@ -69,4 +69,4 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Локализация datatables, ошибок валидации - Обработка ошибок 404 (NotFound) - Доступ к AuthorizedUser -- Собеседование. Разработка ПО \ No newline at end of file +- Собеседование. Разработка ПО From fada483294ca81e52c50622b864646106a66d5bd Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 27 Feb 2021 21:51:50 +0300 Subject: [PATCH 051/220] Update cv.md --- cv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cv.md b/cv.md index 73ca6a62be71..878437e24bb4 100644 --- a/cv.md +++ b/cv.md @@ -93,7 +93,7 @@ - djinni.co (более актуально для Украины) -## Как выжить на испытательном сроке +

Как выжить на испытательном сроке

- Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. - Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки From 1d8f32a0b890d5b7a7c04ed798c10df51841ca9d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 27 Feb 2021 21:54:16 +0300 Subject: [PATCH 052/220] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index 878437e24bb4..d3789a9e796a 100644 --- a/cv.md +++ b/cv.md @@ -94,6 +94,7 @@

Как выжить на испытательном сроке

+ - Учись грамотно формулировать проблему. Проблема "у меня не работает" может иметь тысячи причин. В процессе формулирования очень часто приходит ее решение. - Учись инвестигировать проблему. Внимательное чтение логов и умение дебажить - основные навыки @@ -113,6 +114,7 @@ - [25 ошибок начинающего программиста](https://habr.com/ru/post/413129/) - [Путеводитель по синдрому самозванца](https://vc.ru/hr/167443-eshche-odin-putevoditel-po-sindromu-samozvanca-korni-prichiny-simptomy-i-posledstviya-chast-1) - [Нетехнические навыки](https://tproger.ru/experts/softskills-for-job) +- Видео [Junior и испытательный срок на первой работе](https://www.youtube.com/watch?v=GsGlsCbok-c) - Типичные ошибки начинающих программистов от JavaRush: - [Часть 1](https://javarush.ru/groups/posts/3044-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-1) - [Часть 2](https://javarush.ru/groups/posts/3055-razbor-tipichnihkh-oshibok-nachinajujshikh-programmistov-chastjh-2) From 699c90299d78fcbb30ebb65f7ec2712d4925d29d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sat, 10 Apr 2021 12:12:14 +0300 Subject: [PATCH 053/220] Update cv.md --- cv.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cv.md b/cv.md index d3789a9e796a..f459060b227a 100644 --- a/cv.md +++ b/cv.md @@ -37,6 +37,8 @@ - Михаил Портнов. Собеседование на работу: как продать себя грамотно - Михаил Портнов. Какие вопросы мы задаем на собеседовании? - Михаил Портнов. Собеседование на работу: жизненный путь +- [Лёша Корепанов. Признаки плохих компаний для программиста](https://www.youtube.com/watch?v=Sj-WSWr-n7U) +- [Лёша Корепанов. Как отвечать на вопросы, которые ты не знаешь. Техническое интервью для программиста](https://www.youtube.com/watch?v=Beoh3tfgPEk) - Канал: Резюме, поиск работы, интервью - Яков Файн: Как стать профессиональным Java разработчиком - Ответы на вопросы на собеседовании Junior Java Developer From b7f28e7176958c11c1e49680be14559eb8045ed7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 16 Apr 2021 15:10:41 +0300 Subject: [PATCH 054/220] Update description.md --- description.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/description.md b/description.md index 58fb5d2cdf74..0834140f19a7 100644 --- a/description.md +++ b/description.md @@ -70,3 +70,11 @@ Hibernate, делаем конверторы для типов LocalDateTime (Ja - Обработка ошибок 404 (NotFound) - Доступ к AuthorizedUser - Собеседование. Разработка ПО + +### Миграция на Spring Boot +- Основы Spring Boot. Spring Boot maven plugin +- Lombok, база H2, ApplicationRunner +- Spring Data REST + HATEOAS +- Swagger/ OpenAPI 3.0 +- Тестирование и кэширование в Spring Boot +- Миграция приложения TopJava на Spring Boot From bc6880c6100e3abcc932958ae72717fcf369af90 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 May 2021 13:42:29 +0300 Subject: [PATCH 055/220] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 442ce82b8c47..50494a26ab8f 100644 --- a/graduation.md +++ b/graduation.md @@ -82,6 +82,7 @@ _Антуан де Сент-Экзюпери_ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) +- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 8c4236cdbaa50c5d397be711be33065c4ee388f7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 26 May 2021 17:55:43 +0300 Subject: [PATCH 056/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 50494a26ab8f..e04d13c35885 100644 --- a/graduation.md +++ b/graduation.md @@ -82,7 +82,7 @@ _Антуан де Сент-Экзюпери_ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) -- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ сваливать в одну кучу! +- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 657ed5fcd836df938c6d540cd7cc919c5e50ece9 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 28 May 2021 15:52:56 +0300 Subject: [PATCH 057/220] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 470cce9ecab8..2941a385669b 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Java Enterprise Online Project - циклом за 1 проход по `List` - без циклов по другим коллекциям/массивам (к ним также относим методы коллекций `addAll()/removeAll()`) - решение должно быть рабочим в общем случае (работать в приложении с многими пользователями, не только при запуске main) -- через Stream API за 1 проход по исходному списку `meals.streem()` +- через Stream API за 1 проход по исходному списку `meals.stream()` - нельзя использовать внешние коллекции, не являющиеся частью коллектора - нельзя 2 раза проходить по исходному списку (в том числе его отфильтрованной или преобразованной копии) - возможно дополнительные проходы по частям списка, при этом превышение должно считаться один раз для всего подсписка. Те например нельзя разбить список на на 2 подсписка с четными и нечетными датами и затем их объединить, с подсчетом превышения для каждого элемента. From 1dcc68b58d81d385c867835fa28561f72396fbea Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Fri, 28 May 2021 15:54:28 +0300 Subject: [PATCH 058/220] Create README.md From ad6225f2f50a3edbc5eb46f6af969e2995027df4 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Sun, 30 May 2021 10:48:37 +0300 Subject: [PATCH 059/220] Update graduation.md --- graduation.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/graduation.md b/graduation.md index e04d13c35885..4aa2c1b00dae 100644 --- a/graduation.md +++ b/graduation.md @@ -16,7 +16,7 @@ Build a voting system for deciding where to have lunch. Each restaurant provides a new menu each day. -As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it. +As a result, provide a link to github repository. It should contain the code, README.md with API documentation and couple curl commands to test it (better - Swagger). ----------------------------- P.S.: Make sure everything works with latest version that is on github :) @@ -27,8 +27,9 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации - **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** -- **API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend)** +- **Рекомендую переписать проект современно: Spring Boot + Swagger/OpenAPI 3.0. Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки.** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** +- API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend) *Представьте себе, что ПМ (лид, архитектор) дал вам ТЗ и некоторое время недоступен. У вас, конечно, есть много мыслей, для чего нужно приложение, как исправить ТЗ, дополнить его и сделать правильно. НО НЕ НАДО ИХ РЕАЛИЗОВЫВАТЬ В КОДЕ. Нужно сделать все максимально просто, удобно для доработок и для использования со стороны клиента (если, конечно, в ТЗ нет оговорок). Все свои вопросы, предложения и хотелки оформляйте отдельно (в `read.me` например). Если делаете что-то сложнее простейшего случая (например, справочник еды) - обязательно напишите в read.me. Как и выбор стратегии кэширования.* @@ -36,11 +37,11 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front _Антуан де Сент-Экзюпери_ -- 1: **читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** -- 2: **тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. -- 3: **тщательно считайте количество запросов в вашем API для отображения нужной информации** -- 4: учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** -- 5: в проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: +- 1: **Читаем ТЗ ОЧЕНЬ внимательно, НЕ надо ничего своего туда домысливать и творчески изменять** +- 2: **Тщательно считайте количество обращений в базу на каждый запрос. Особенно при запросах от юзеров, которых очень много! Также на сложность запросов от них, чтобы не положить базу**. Самое худшее в коде - обращение в базу в цикле. +- 3: **Тщательно считайте количество запросов в вашем API для отображения нужной информации** +- 4: Учитывайте, что **пользователей может быть ОООЧЕНЬ много, а админов - МАЛО** +- 5: В проекте (и тестовом задании на работу), в отличие от нашего учебного topjava, оставляйте только необходимый для работы приложения код, ничего лишнего: - 5.1 НЕ надо делать разные профили базы и работы с ней - 5.2 НЕ надо делать абстрактных контроллеров на всякий случай - 5.3 НЕ надо делать **классов репозиториев и сервисов**, если там нет ничего, кроме делегирования @@ -51,27 +52,27 @@ _Антуан де Сент-Экзюпери_ - **историю еды и голосований сделать НУЖНО! Есть базовые вещи, которые закладываются в архитектуру приложения и неочевидные доработки к ТЗ, которых лучше не делать.** - при популировании добавте записи за сегодняшний день - now(), чтобы всегда были актуальные исходные данные - таблицы обычно именуются в единственном числе. Исключение - users, т. к. user - зарезервированное слово. `date`/`timestamp` - зарезервированное слово, лучше избегать их при именовании -- 7: по возможности сделать JUnit тесты -- 8: уделяйте внимание обработке ошибок -- 9: делаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) +- 7: По возможности сделать JUnit тесты +- 8: Уделяйте внимание обработке ошибок +- 9: Делаем REST API в соответствии с концепцией REST (url в общем имеют вид`{ресурс}/{id_ресурсa}[/{подресурс}/{id_подресурсa}][параметры]`) - **[15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)** - **10 Best Practices for Better RESTful API** - [REST resource hierarchy](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) -- 10: не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга. -- 11: не размещайте логику приложения и преобразования в TO в слое доступа к DB -- 12: если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) +- 10: Не смешивайте TO и Entity вместе. Лучше всего, если они будут независимыми друг от друга. +- 11: Не размещайте логику приложения и преобразования в TO в слое доступа к DB +- 12: Если приложению в объекте требуется только его id, используйте reference (как мы при сохранении еды вставляем туда юзера) - 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) -- 14: еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! -- 15: название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил +- 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! +- 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил - 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository) -- 17: в DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`) -- 18: на topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются -- 19: проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое +- 17: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`) +- 18: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются +- 19: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое - 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverflow в коде обязательна! -- 21: проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). -- 22: обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк +- 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). +- 22: Обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк - 23: Кэширование - - необязательно, но желательно + - необязательно, но желательно. Чем проще реализация - тем лучше. - **тщательно продумайте, что надо кэшировать (самые частые запросы)**, а что нет (большие или редко запрашиваемые данные)! - проверьте соответствие ключей к кэшу (параметры кэшируемого метода) с конфигурацией (например в singleNonExpiryCache, heap=1 в кэше может содержаться только ОДНО значение). - 24: Валидация @@ -80,13 +81,13 @@ _Антуан де Сент-Экзюпери_ - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - - Требуемые примеры `curl` не прячьте, а пишите здесь! + - Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально - ссылка на Swagger. - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) -- 26: на управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! +- 26: На управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. -- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой)? +- Сколько раз пришлось его вызвать API для типичного сценария (нарпимер посмотреть рестораны с едой на сегодня)? - Сколько запросов к базе было сделано? Можно ли сократить (например с FETCH/Graph или через кэширование)? - **API ДОЛЖНО соответствовать принципам REST (см. ссылки выше)** - **ОБЯЗАТЕЛЬНО: запустите `mvn test`- ошибок быть не должно** From 04a5ef7b35a72c30fdc68805081308a7ac60d3e7 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Wed, 28 Jul 2021 12:55:54 +0300 Subject: [PATCH 060/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 4aa2c1b00dae..070424ded69d 100644 --- a/graduation.md +++ b/graduation.md @@ -83,7 +83,7 @@ _Антуан де Сент-Экзюпери_ - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально - ссылка на Swagger. - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) -- 26: На управление (CRUD) рестаранами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! +- 26: На управление (CRUD) ресторанами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From 0471c423de6f18ab565c7aba2337ec78993b25a4 Mon Sep 17 00:00:00 2001 From: "admin@javaops.ru" Date: Fri, 30 Jul 2021 02:12:18 +0300 Subject: [PATCH 061/220] Add text to lesson07 --- doc/lesson07.md | 588 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 481 insertions(+), 107 deletions(-) diff --git a/doc/lesson07.md b/doc/lesson07.md index c4f4992a61df..2b46394101bf 100644 --- a/doc/lesson07.md +++ b/doc/lesson07.md @@ -13,59 +13,171 @@ ### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. HW6 #### Apply 7_01_HW6_fix_tests.patch -> - Добавил `AbstractServiceTest.isJpaBased()` и `Assume.assumeTrue(isJpaBased())` в `AbstractMealServiceTest.testValidation()`. -> - Как вариант можно было вместо наследования от `AbstractJpaUserServiceTest` сделать `@Autowired(required = false) JpaUtil` и чистить кэш по условию `isJpaBased()`. -> - В новой версии Spring классы `spring-mvc` требуют `WebApplicationContext`, поэтому поправил `inmemory.xml` #### Apply 7_02_HW6_meals.patch -При переходе на AJAX `JspMealController` удалим за ненадобностью, возвращение всей еды `meals()` останется в `RootController`. + +> сделал фильтрацию еды через `get`: операция идемпотентная, можно делать в браузере обновление по F5 + +### Внимание: чиним пути в следующем патче #### Apply 7_03_HW6_fix_relative_url_utf8.patch -- Relative paths in JSP -- Spring redirect: prefix + +- + Relative paths in JSP +- + Spring redirect: prefix ### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. HW6 Optional + #### Apply 7_04_HW6_optional_add_role.patch -`JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_optional_jdbc.patch` + +#### `JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_jdbc_transaction_roles.patch` #### Apply 7_05_fix_hint_graph.patch -- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate не дублировать данные. -Для оптимизации можно указать Hibernate делать запрос без distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct) - - Бага [HINT_PASS_DISTINCT_THROUGH does not work if 'hibernate.use_sql_comments=true'](https://hibernate.atlassian.net/browse/HHH-13280). При `hibernate.use_sql_comments=false` все работает- в SELECT нет DISTINCT. -- Тест `DataJpaUserServiceTest.testGetWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). `DISTINCT` при нескольких JOIN не помогает. -Оставил в графе только `meals`. Корректно поставить тип `LOAD`, чтобы остальные ассоциации доставались по стратегии модели. Однако [с типом по умолчанию `FETCH` роли также достаются](https://stackoverflow.com/a/46013654/548473) (похоже, что бага). +- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate + не дублировать данные. Для оптимизации можно указать Hibernate делать запрос без + distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct) +- Бага [HINT_PASS_DISTINCT_THROUGH does not work if 'hibernate.use_sql_comments=true'](https://hibernate.atlassian.net/browse/HHH-13280). При `hibernate.use_sql_comments=false` все работает - в SELECT нет DISTINCT. +- Тест `DataJpaUserServiceTest.getWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). ... -#### Apply 7_06_HW6_optional_jdbc.patch -> - реализовал в `JdbcUserRepositoryImpl.getAll()` доставание ролей через лямбду -> - в `insertRoles` поменял метод `batchUpdate` и сделал проверку на empty -> - в `setRoles` достаю роли через `queryForList` - -Еще интересные JDBC реализации: - - в `getAll()/ get()/ getByEmail()` делать запросы с `LEFT JOIN` и сделать реализацию `ResultSetExtractor` - - подключить зависимость `spring-data-jdbc-core`. Там есть готовый `OneToManyResultSetExtractor`. Можно посмотреть, как он реализован. - - реализация, зависимая от БД (для postgres): доставать агрегированные роли и делать им `split(",")`: -``` -SELECT u.*, string_agg(r.role, ',') AS roles -FROM users u - JOIN user_roles r ON u.id=r.user_id -GROUP BY u.id -``` +#### Apply 7_06_HW6_jdbc_transaction_roles.patch + +Еще интересные JDBC реализации: ... + +### Валидация для `JdbcUserRepository` через Bean Validation API + +#### Apply 7_07_HW6_optional_jdbc_validation.patch + +- [Валидация данных при помощи Bean Validation API](https://alexkosarev.name/2018/07/30/bean-validation-api/). + +На данный момент у нас реализована валидация сущностей только для jpa- и dataJpa-репозиториев. При работе +через JDBC-репозиторий может произойти попытка записи в БД некорректных данных, что приведет к `SQLException` из-за нарушения +ограничений, наложенных на столбцы базы данных. Для того, чтобы перехватить невалидные данные еще до +обращения в базу, воспользуемся API *javax.validation* (ее реализация `hibernate-validator` используется для проверки данных в Hibernate и будет использоваться в Spring Validation, которую подключим позже). +В `ValidationUtil` создадим один потокобезопасный валидатор, который можно переиспользовать (см. *javadoc*). +С его помощью в методах сохранения и обновления сущности в jdbc-репозиториях мы можем производить валидацию этой сущности: `ValidationUtil.validate(object);` +Чтобы проверка не падала, `@NotNull Meal.user` пришлось пока закомментировать. Починим в 10-м занятии через `@JsonView`. + +### Отключение кэша в тестах: + +Вместо наших приседаний с `JpaUtil` и проверкой профилей мы можем ... + +#### Apply 7_08_HW06_optional2_disable_tests_cache.patch + +- [Example of PropertyOverrideConfigurer](https://www.concretepage.com/spring/example_propertyoverrideconfigurer_spring) +- [Spring util schema](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#xsd-schemas-util) ## Занятие 7: -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Тестирование Spring MVC -#### Apply 7_07_controller_test.patch + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. Тестирование Spring MVC + +
+ Краткое содержание + +#### Тестирование Spring MVC + +Для более удобного сравнения объектов в тестах мы будем использовать библиотеку *Harmcrest* с Matcher'ами, которая +позволяет делать сложные проверки. С *Junit* по умолчанию подтягивается *Harmcrest core*, но нам потребуется расширенная версия: +в `pom.xml` из зависимости Junit исключим дочернюю `hamcrest-core` и добавим `hamcrest-all`. + +Для тестирования web создадим вспомогательный класс `AbstractControllerTest`, от которого будут наследоваться все +тесты контроллеров. Его особенностью будет наличие `MockMvc` - эмуляции Spring MVC для тестирования web-компонентов. +Инициализируем ее в методе, отмеченном `@PostConstruct`: + + ``` +mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(CHARACTER_ENCODING_FILTER).build(); + ``` + +Для того, чтобы в тестах контроллеров не популировать базу перед каждым тестом, пометим этот базовый тестовый класс аннотацией `@Transactional`. +Теперь каждый тестовый метод будет выполняться в транзакции, которая будет откатываться после окончания метода и возвращать базу данных в исходное +состояние. Однако теперь в работе тестов могут возникнуть нюансы, связанные с пропагацией транзакций: все +транзакции репозиториев станут вложенными во внешнюю транзакцию теста. При этом, например, кэш первого уровня станет работать не +так, как ожидается. Т.е при таком подходе нужно быть готовыми к ошибкам: мы их увидим и поборем в тестах на обработку ошибок на последних занятиях TopJava. + +#### UserControllerTest + +Создадим тестовый класс для контроллера юзеров, он должен наследоваться от `AbstractControllerTest`. +В `MockMvc` используется [паттерн проектирования Builder](https://refactoring.guru/ru/design-patterns/builder). + + ``` + mockMvc.perform(get("/users")) // выполнить HTTP метод GET к "/users" + .andDo(print()) // распечатать содержимое ответа + .andExpect(status().isOk()) // от контроллера ожидается ответ со статусом HTTP 200(ok) + .andExpect(view().name("users")) // контроллер должен вернуть view с именем "users" + .andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) // ожидается, что клиент должен быть перенаправлен на "/WEB-INF/jsp/users.jsp" + .andExpect(model().attribute("users", hasSize(2))) // в модели должен быть атрибут "users" размером = 2 + .andExpect(model().attribute("users", hasItem( // внутри которого есть элемент ... + allOf( + hasProperty("id", is(START_SEQ)), // ... с аттрибутом id = START_SEQ + hasProperty("name", is(USER.getName())) //... и name = user + ) + ))); +} + ``` + +В параметры метода `andExpect()` передается реализация `ResultMatcher`, в которой мы определяем как должен быть обработан ответ контроллера. + +
+ +#### Apply 7_09_controller_test.patch + > - в `MockMvc` добавился `CharacterEncodingFilter` -> - добавил `AllActiveProfileResolver` -> - реализация Ehcache нетранзакционная, после отката транзакции в тестах по `@Transactional` Hibernate-кеш не восстанавливал роль для USER. Добавил очистку кэша Hibernate в `AbstractControllerTest.setUp` (с учетом того, что `Profiles.REPOSITORY_IMPLEMENTATION` можно переключить на `JDBC`). +> - добавил [`AllActiveProfileResolver`](//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver) для возвращения массива профилей +> - сделал вспомогательный метод `AbstractControllerTest.perform()` + +- Hamcrest +- Unit Testing of Spring MVC Controllers + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Миграция на JUnit 5](https://drive.google.com/open?id=16wi0AJLelso-dPuDj6xaGL7yJPmiO71e) + +
+ Краткое содержание + +Для миграции на 5-ю версию JUnit в файле `pom.xml` поменяем зависимость `junit` на `junit-jupiter-engine` ([No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven)). +Актуальную версию всегда можно посмотреть [в центральном maven репозитории](https://search.maven.org/search?q=junit-jupiter-engine), берем только релизы (..-Mx означают предварительные milestone версии) +Изменять конфигурацию плагина `maven-sureface-plugin` в новых версиях JUnit уже не требуется. +Junit5 не содержит в себе зависимости от *Harmcrest* (которую нам приходилось вручную +отключать для JUnit4 в предыдущих шагах), поэтому исключение `hamcrest-core` просто удаляем. +В итоге у нас останутся зависимости JUnit5 и расширенный Harmcrest. +Теперь мы можем применить все нововведения пятой версии в наших тестах: + 1. Для всех тестов теперь мы можем удалить `public`. + 2. Аннотацию `@Before` исправим на `@BeforeEach` - теперь метод, который будет выполняться перед +каждым тестом, помечается именно так. + 3. В Junit5 работа с исключениями похожа на Junit4 версии 4.13: вместо ожидаемых исключений в параметрах аннотации `@Test(expected = Exception.class)` используется метод `assertThrows()`, +в который первым аргументом мы передаем ожидаемое исключение, а вторым аргументом — реализацию функционального интерфейса `Executable` (кода теста, +в котором ожидается возникновение исключения). + 4. Метод `assertThrows()` возвращает исключение, которое было выброшено в переданном ему коде. Теперь мы можем получить это исключение, извлечь из него сообщение с помощью + `e.getMessage()` и сравнить с ожидаемым. + 5. Для теста на валидацию при проверке предусловия, только при выполнении которого +будет выполняться следующий участок кода (например, в нашем случае тесты на валидацию выполнялись +только в jpa профиле), - теперь нужно пользоваться утильным методом `Assumptions` (нам уже не требуется). + 6. Проверку Root Cause - причины, из-за которой было выброшено пойманное исключение, мы будем делать позднее, при тестах на ошибки. + 7. Из JUnit5 исключена функциональность `@Rule`, вместо них теперь нужно использовать `Extensions`, которые +могут встраиваться в любую фазу тестов. Чтобы добавить их в тесты, пометим базовый тестовый класс аннотацией `@ExtendWith`. + +JUnit предоставляет нам набор коллбэков — интерфейсов, которые будут исполняться в определенный момент тестирования. +Создадим класс `TimingExtension`, который будет засекать время выполнения тестовых методов. +Этот класс будет имплементировать маркерные интерфейсы — коллбэки JUnit: + - `BeforeTestExecutionCallback` - коллбэк, который будет вызывать методы этого интерфейса перед каждым тестовым методом. + - `AfterTestExecutionCallback` - методы этого интерфейса будут вызываться после каждого тестового метода; + - `BeforeAllCallback` - методы перед выполнением тестового класса; + - `AfterAllCallback` - методы после выполнения тестового класса; + +Осталось реализовать соответствующие методы, которые описываются в каждом из этих интерфейсов, они и будут вызываться JUnit в нужный момент: + - в методе `beforeAll` (который будет вызван перед запуском тестового класса) создадим спринговый утильный секундомер `StopWatch` для текущего тестового класса; + - в методе `beforeTestExecution` (будет вызван перед тестовым методом) - запустим секундомер; + - в методе `afterTestExecution` (будет вызван после тестового метода) - остановим секундомер. + - в методе `afterAll` (который будет вызван по окончанию работы тестового класса) - выведем результат работы этого секундомера в консоль; -- Hamcrest -- Unit Testing of Spring MVC Controllers + 8. Аннотации `@ContextConfiguration` и `@ExtendWith(SpringExtension.class)` (замена `@RunWith`) мы можем заменить одной `@SpringJUnitConfiguration` (в старых версиях IDEA ее не понимает) + +
+ +#### Apply 7_10_JUnit5.patch -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Миграция на JUnit 5](https://www.youtube.com/watch?v=YmLzT-j1hU4) -#### Apply 7_08_JUnit5.patch > - [No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven) -> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549). Можно обновить IDEA до 2019.2 (обновите, только если достаточно памяти: минимум 3Gb. `Help menu -> Edit Custom VM Options`) +> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549) - [JUnit 5 homepage](https://junit.org/junit5) - [Overview](https://junit.org/junit5/docs/snapshot/user-guide/#overview) @@ -79,114 +191,376 @@ GROUP BY u.id - [Third party Extensions](https://github.com/junit-team/junit5/wiki/Third-party-Extensions) - [Реализация assertThat](https://stackoverflow.com/questions/43280250) +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. [Принципы REST. REST контроллеры](https://drive.google.com/open?id=1e4ySjV15ZbswqzL29UkRSdGb4lcxXFm1) + +
+ Краткое содержание + +#### Принципы REST, REST-контроллеры + +> [REST](http://spring-projects.ru/understanding/rest/) - архитектурный стиль проектирования распределенных систем (типа клиент-сервер). + +Чаще всего в REST сервер и клиент общаются посредством обмена JSON-объектами через HTTP-методы GET/POST/PUT/DELETE/PATCH. +Особенностью REST является отсутствие состояния (контекста) взаимодействий клиента и сервера. + +В нашем приложении есть контроллеры для Admin и для User. Чтобы сделать их REST-контроллерами, +заменим аннотацию `@Controller` на `@RestController` + +> Не поленитесь зайти чз Ctrl+Click в `@RestController`: к аннотации `@Controller` добавлена `@ResponseBody`. Т.е. ответ от нашего приложения будет не имя View, а данные в теле ответа. + +В `@RequestMapping`, кроме пути для методов контроллера (`value`) добавляем параметр `produces = MediaType.APPLICATION_JSON_VALUE`. +Это означает, что в заголовки ответа будет добавлен тип `ContentType="application/json"` - в ответе от контроллера будет приходить JSON-объект. + +> Чтобы было удобно использовать путь к этому контроллеру в приложении и в тестах, +> выделим путь к нему в константу REST_URL, к которой можно будет обращаться из других классов + +1. Метод `AdminRestController.getAll` пометим аннотацией `@GetMapping` - маршрутизация к методу по HTTP GET. + +2. Метод `AdminRestController.get` пометим аннотацией `@GetMapping("/{id}")`. +В скобках аннотации указано, что к основному URL контроллера будет добавляться `id` пользователя - переменная, которая передается в запросе непосредственно в URL. + Соответствующий параметр метода нужно пометить аннотацией `@PathVariable` (если имя в URL и имя аргумента метода не совпадают, в параметрах аннотации дополнительно нужно будет уточнить + имя в URL. Если они совпадают, [этого не требуется](https://habr.com/ru/post/440214/). + +3. Метод создания пользователя `create` отметим аннотацией `@PostMapping` - маршрутизация к методу по HTTP POST. + В метод мы передаем объект `User` в теле запроса (аннотация `@RequestBody`) и в формате JSON (`consumes = MediaType.APPLICATION_JSON_VALUE`). + При создании нового ресурса правила хорошего тона - вернуть в заголовке ответа URL созданного ресурса. + Для этого возвращем не `User`, а `ResponseEntity`, который мы можем с помощью билдера `ServletUriComponentsBuilder` дополнить заголовком ответа `Location` и вернуть статус `CREATED(201)` + (если пойти в код `ResponseEntity.created` можно докопаться до сути, очень рекомендую смотреть в исходники кода). + +4. Метод `delete` помечаем `@DeleteMapping("/{id}")` - HTTP DELETE. + Он ничего не возвращает, поэтому помечаем его аннотацией `@ResponseStatus(HttpStatus.NO_CONTENT)`. Статус ответа будет HTTP.204; + +5. Над методом обновления ставим `@PutMapping` (HTTP PUT). В аргументах метод принимает `@RequestBody User user` и `@PathVariable int id`. + +6. Метод поиска по `email` также помечаем `@GetMapping`, и, чтобы не было конфликта маршрутизации с методом `get()`, + указываем в URL добавку "/by". В этот метод `email` передается как параметр запроса, аннотация `@RequestParam`. + +> **Все это СТАНДАРТ архитектурного стиля REST. НЕ придумывайте ничего своего в своих выпускных проектах! Это очень большая ошибка - не придерживаться стандартов API.** + +7. `ProfileRestController` выполняем аналогичным способом с учетом того, что пользователь имеет доступ только к своим данным. -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. [Принципы REST. REST контроллеры](https://youtu.be/33_2yOck4ak) -#### Apply 7_09_rest_controller.patch - -- Понимание REST -- JSON (JavaScript Object Notation) +Если на данном этапе попытаться запустить приложение и обратиться к какому-либо методу контроллера, сервер ответит нам ошибкой со статусом 406, +так как Spring не знает, как преобразовать объект User в JSON... + +
+ +#### Apply 7_11_rest_controller.patch + +- Понимание REST +- JSON (JavaScript Object Notation) - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) -- [10 Best Practices for Better RESTful](http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/) +- [10 Best Practices for Better RESTful](https://medium.com/@mwaysolutions/10-best-practices-for-better-restful-api-cbe81b06f291) - [Best practices for rest nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) -- Request mapping -- Дополнительно: - - JAX-RS vs Spring MVC - - RESTful API для сервера – делаем правильно (Часть 1) - - RESTful API для сервера – делаем правильно (Часть 2) - - И. Головач. RestAPI - - [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/) - -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. [Тестирование REST контроллеров. Jackson](https://drive.google.com/file/d/1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO) -#### Apply 7_10_rest_test_jackson.patch -- [Jackson databind github](https://github.com/FasterXML/jackson-databind) -- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations) - -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. [Кастомизация Jackson Object Mapper](https://drive.google.com/file/d/1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks) - -#### Apply 7_11_jackson_object_mapper.patch -- Сериализация hibernate lazy-loading с помощью jackson-datatype-hibernate -- Handle Java 8 dates with Jackson -- Дополнительно: - - Jackson JSON Serializer & Deserializer - -### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. [Тестирование REST контроллеров через JSONassert](https://drive.google.com/file/d/1oa3e0_tG57E71g6PW7_tfb3B61Qldctl) -#### Apply 7_12_json_assert_tests.patch +- + Request mapping +- [Лучшие практики разработки REST API: правила 1-7,15-17](https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/) +- Дополнительно: + - [Подборка практик REST](https://gist.github.com/Londeren/838c8a223b92aa4017d3734d663a0ba3) + - JAX-RS vs Spring MVC + - RESTful API для сервера – делаем правильно (Часть 1) + - RESTful API для сервера – делаем правильно (Часть 2) + - И. Головач. + RestAPI + - [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 6. [Тестирование REST контроллеров. Jackson.](https://drive.google.com/open?id=1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO) + +
+ Краткое содержание + +Для работы с JSON добавляем в `pom.xml` зависимость `jackson-databind`. +Актуальную версию библиотеки можно посмотреть в [центральном maven-репозитории](https://search.maven.org/artifact/com.fasterxml.jackson.core/jackson-databind). +Теперь спринг будет автоматически использовать эту библиотеку для сериализации/десериализации объектов в JSON (найдя ее в *classpath*). +Если сейчас запустить приложение и обратиться к методам REST-контроллера, то оно выбросит `LazyInitializationException`. +Оно возникает из-за того, что у наших сущностей есть лениво загружаемые поля, отмеченные `FetchType.LAZY` - при загрузке сущности из базы, вместо этого поля подставится Proxy, который и должен вернуть +реальный экземпляр этого поля при первом же обращении. Jackson при сериализации в JSON использует все поля сущности, +и при обращении к *Lazy* полям возникает исключение, так как сессия работы с БД в этот момент уже закрыта, и нужный объект +не может быть инициализирован. Чтобы Jackson игнорировал эти поля, пометим их аннотацией `@JsonIgnore`. + +Теперь при запуске приложения REST-контроллер будет работать. Но при получении JSON объектов мы можем увидеть, что Jackson сериализовал объект +через геттеры (например в ответе есть поле `new` от метода `Persistable.isNew()`). +Чтобы учитывались только поля объектов, добавим над `AbstractBaseEntity`: +````java +@JsonAutoDetect(fieldVisibility = ANY, // jackson видит все поля + getterVisibility = NONE, // ... но не видит геттеров + isGetterVisibility = NONE, //... не видит геттеров boolean полей + setterVisibility = NONE) // ... не видит сеттеров +```` +Теперь все сущности, унаследованные от базового класса, будут сериализоваться/десериализоваться через поля. + +
+ +#### Apply 7_12_rest_test_jackson.patch + +- [Jackson databind github](https://github.com/FasterXML/jackson-databind) +- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations) + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 7. [Кастомизация Jackson Object Mapper](https://drive.google.com/open?id=1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks) + +
+ Краткое содержание + +Сейчас, чтобы не сериализовать *Lazy* поля, мы должны пройтись по каждой сущности и +вручную пометить их аннотацией `@JsonIgnore`. Это неудобно, засоряет код и допускает возможные ошибки. К тому же, +при некоторых условиях, нам иногда нужно загрузить и в ответе передать эти *Lazy* поля. +Чтобы запретить сериализацию Lazy полей для всего проекта, подключим в `pom.xml` библиотеку `jackson-datatype-hibernate`. +Также изменим сериализацию/десериализацию полей объектов в JSON: не через аннотацию `@JsonAutoDetect`, а в классе `JacksonObjectMapper`, который +унаследуем от `ObjectMapper` (стандартный Mapper, который использует Jackson) и сделаем в нем другие настройки. +В конструкторе: +- регистрируем `Hibernate5Module` - модуль `jackson-datatype-hibernate`, который не делает сериализацию ленивых полей. +- модуль для корректной сериализации `LocalDateTime` в поля JSON - `JavaTimeModule` модуль библиотеки `jackson-datatype-jsr310` +- запрещаем доступ ко всем полям и методам класса и потом разрешаем доступ только к полям +- не сериализуем null-поля (`setSerializationInclusion(JsonInclude.Include.NON_NULL)`) + +Чтобы подключить наш кастомный `JacksonObjectMapper` в проект, в конфигурации `spring-mvc.xml` к +настройке `` добавим `MappingJackson2HttpMessageConverter`, который будет использовать наш маппер. + +
+ + +#### Apply 7_13_jackson_object_mapper.patch + +- Сериализация hibernate lazy-loading с помощью + jackson-datatype-hibernate +- Handle Java 8 dates with Jackson +- Дополнительно: + - Jackson JSON + Serializer & Deserializer + +### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 8. [Тестирование REST контроллеров через JSONassert и Матчеры](https://drive.google.com/open?id=1oa3e0_tG57E71g6PW7_tfb3B61Qldctl) + +
+ Краткое содержание + +Сейчас в тестах REST-контроллера мы проводим проверку только на статус ответа и тип возвращаемого контента. Добавим проверку содержимого ответа. + +#### 7_14_json_assert_tests + +Чтобы сравнивать содержимое ответа контроллера в виде JSON и сущность, воспользуемся библиотекой +`jsonassert`, которую подключим в `pom.xml` со scope *test*. + +Эта библиотека при сравнении в тестах в качестве ожидаемого значения ожидает от +нас объект в виде JSON-строки. Чтобы вручную не преобразовывать объекты в JSON и не +хардкодить их в виде строк в наши тесты, воспользуемся Jackson. +Для преобразования объектов в JSON и обратно создадим утильный класс `JsonUtil`, в котором +с помощью нашего `JacksonObjectMapper` и будет конвертировать объекты. +И мы сталкиваемся с проблемой: `JsonUtil` - утильный класс и не является +бином спринга, а для его работы требуется наш кастомный маппер, который находится под управлением +спринга и расположен в контейнере зависимостей. Поэтому, чтобы была возможность получить +наш маппер из других классов - сделаем его синглтоном и сделаем в нем статический +метод, который будет возвращать его экземпляр. Теперь `JsonUtil` сможет его получить. +И нам нужно указать спрингу, чтобы он не создавал второй экземпляр этого объекта, а клал в свой контекст существующий. +Для этого в конфигурации `spring-mvc.xml` определим factory-метод, с помощью которого спринг должен +получить экземпляр (instance) этого класса: +```xml + +``` +а в конфигурации `message-converter` вместо создания бина просто сошлемся на сконфигурированный `objectMapper`. + +Метод `ContentResultMatchers.json()` из `spring-test` использует библиотеку `jsonassert` для сравнения 2-х JSON строк: одну из ответа контроллера и вторую - +JSON-сериализация `admin` без поля `registered` (это поле инициализируется в момент создания и отличается). +В методе `JsonUtil.writeIgnoreProps` мы преобразуем объект `admin` в мапу, удаляем из нее игнорируемые поля и снова сериализуем в JSON. + +Также сделаем тесты для утильного класса `JsonUtil`. В тестах мы записываем +объект в JSON-строку, затем конвертируем эту строку обратно в объект и сравниваем с исходным. И то же самое делаем со списком объектов. + +#### 7_15_tests_refactoring + +**`RootControllerTest`** + +Сделаем рефакторинг `RootControllerTest`. Ранее мы в тесте получали модель, доставали из нее сущности и с помощью `hamcrest-all` +производили по одному параметру их сравнение с ожидаемыми значениями. +Метод `ResultActions.andExpect()` позволяет передавать реализацию интерфейса `Matcher`, в котором можно делать любые сравнения. +Функциональность сравнения списка юзеров по ВСЕМ полям у нас уже есть - мы просто делегируем сравнение объектов в `UserTestData.MATCHER`. +При этом нам больше не нужен `harmcrest-all`, нам достаточно только `harmcrest-core`. + +**`MatcherFactory`** + +Теперь вместо `jsonassert` и сравнения JSON-строк в тестах контроллеров сделаем сравнения JSON-объектов через `MatcherFactory`. +Преобразуем ответ контроллера из JSON в объект и сравним с эталоном через уже имеющийся у нас матчер. +Вместо сравнения JSON-строк в метод `andExpect()` мы будем передавать реализации интерфейса `ResultMatcher` из `MATCHER.contentJson(..)`. + +`MATCHER.contentJson(..)` принимают ожидаемый объект и возвращают для него `ResultMatcher` с реализацией единственного метода `match(MvcResult result)`, +в котором делегируем сравнение уже существующим у нас матчерам. +Мы берем JSON-тело ответа (`MatcherFactory.getContent`), десериализуем его в объект (`JsonUtil.readValue/readValues`) и сравниваем через имеющийся `MATCHER.assertMatch` +десериализованный из тела контроллера объект и ожидаемое значение. + +> Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил. + +**`AdminRestControllerTest`** + +- `getByEmail()` - сделан по аналогии с тестом `get()`. Дополнительно нужно дополнить строку URL параметрами запроса. +- `delete()` - выполняем HTTP.DELETE. Проверяем статус ответа 204. Проверяем, что пользователь удален. + +> Раньше я получал всех users из базы и проверял, что среди них нет удаленного. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении-удалении новых тестовых данных. + +- `update()` - выполняем HTTP.PUT. В тело запроса подаем сериализованный `JsonUtil.writeValue(updated)`. После выполнения проверяем, что объект в базе обновился. +- `create()` - выполняем HTTP.POST аналогично `update()`. Но сравнить результат мы сразу не можем, т.к. при создании объекта ему присваивается `id`. + Поэтому мы извлекаем созданного пользователя из ответа (`MATCHER.readFromJson(action)`), получаем его `id`, и уже с этим `id` эталонный объект мы можем сравнить с объектом в ответе контроллера и со + значением в базе. +- `getAll()` - аналогично get(). Список пользователей из ответа в формате JSON сравниваем с эталонным списком (`MATCHER.contentJson(admin, user)`). + +Тесты для `ProfileRestController` выполнены аналогично. + +
+ +#### Apply 7_14_json_assert_tests.patch + +> - В `JsonUtil.writeIgnoreProps` вместо цикла по мапе сделал `map.keySet().removeAll` + - [JSONassert](https://github.com/skyscreamer/JSONassert) - [Java Code Examples for ObjectMapper](https://www.programcreek.com/java-api-examples/index.php?api=com.fasterxml.jackson.databind.ObjectMapper) -#### Apply 7_13_tests_refactoring.patch +#### Apply 7_15_tests_refactoring.patch + +> - Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил. +> - Раньше в тестах я для проверок получал всех users из базы и сравнивал с эталонным списком. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении-удалении новых тестовых данных. + +- [Java @SafeVarargs Annotation](https://www.baeldung.com/java-safevarargs) ### ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 9. Тестирование через SoapUi. UTF-8 -#### Apply 7_14_soapui_utf8_converter.patch + +
+ Краткое содержание + +SOAP UI - это один из инструментов для тестирования API приложений, которые работают по REST и по SOAP. +Он позволяет нам по HTTP протоколу дернуть методы нашего API и увидеть ответ контроллеров. + +Если в контроллер мы добавим метод, который в теле ответа будет возвращать текст на кириллице, то мы увидим кодировка теряться. +Для сохранения кодировки используем `StringHttpMessageConverter`, который конфигурируем в `spring-mvc.xml`. +При этом мы должны явно указать, что конвертор будет работать только с текстом в кодировке *UTF-8*. + +
+ +#### Apply 7_16_soapui_utf8_converter.patch - Инструменты тестирования REST: - - SoapUi - - Написание HTTP-запросов с помощью Curl -(для Windows можно использовать Git Bash). Для работы с UTF-8 в Windows 10 нужны пляски с бубном: ["Язык и региональные стандарты" -> "Сопутствующие параметры" -> "Административные языковые параметры" -> "Изменить язык системы" -> галка "Бета-версия:Использовать Юникод (UTF-8) для поддержки языка во всем мире"](https://drive.google.com/open?id=1J1WquTv9wenJQ9ptMymXPYGnrvFzAV-L), перезагрузка. - - Postman - - IDEA: Tools->HTTP Client->... - - [Insomnia REST client](https://insomnia.rest/) + - SoapUi + - Написание HTTP-запросов с помощью + Curl. + Для Windows 7 можно использовать Git Bash, с Windows 10 v1803 можно прямо из консоли. Возможны проблемы с UTF-8: + - [CURL doesn't encode UTF-8](https://stackoverflow.com/a/41384903/548473) + - [Нстройка кодировки в Windows](https://support.socialkit.ru/ru/knowledge-bases/4/articles/11110-preduprezhdenie-obnaruzhenyi-problemyi-svyazannyie-s-raspoznavaniem-russkih-simvolov) + - **[IDEA: Tools->HTTP Client->...](https://www.jetbrains.com/help/idea/rest-client-tool-window.html)** + - Postman + - [Insomnia REST client](https://insomnia.rest/) -**Импортировать проект в SoapUi из config\Topjava-soapui-project.xml. Response смотреть в формате JSON.** +**Импортировать проект в SoapUi из `config\Topjava-soapui-project.xml`. Response смотреть в формате JSON.** -> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text +> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text -ResponseBody and UTF-8 +[ResponseBody and UTF-8](http://web.archive.org/web/20190102203042/http://forum.spring.io/forum/spring-projects/web/74209-responsebody-and-utf-8) ## ![question](https://cloud.githubusercontent.com/assets/13649199/13672858/9cd58692-e6e7-11e5-905d-c295d2a456f1.png) Ваши вопросы + +> Зачем у нас и UIController'ы, и RestController'ы? То есть в общем случае backend-разработчику недостаточно предоставить REST-api и RestController? + +В общем случае нужны и те и другие. REST обычно используют для отдельного UI например на React или Angular или для +интеграции / мобильного приложения. У нас REST контроллеры используются только для тестирования. UI мы используем для +нашего приложения на JSP шаблонах. Таких сайтов без богатой UI логики тоже немало. Например https://javaops.ru/ :) +Разница в запросах: + +- для UI используются только GET и POST +- при создании-обновлении в UI мы принимаем данные из формы `application/x-www-form-urlencoded` (посмотрите + вкладку `Network`, не в формате JSON) +- для REST запросы GET, POST, PUT, DELETE, PATCH и возвращают только данные (обычно JSON) + +и в способе авторизации: + +- для RESТ у нас будет базовая авторизация +- для UI - через cookies + +Также часто бывают смешанные сайты - где есть и отдельное JS приложение и шаблоны. + > При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет? -`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции, и после каждого теста JUnit делает rollback базы. +`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции, и после каждого +теста JUnit делает rollback базы. -> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`. +> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`. -Запросы SQL можно посмотреть в логах. Т.е. `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующиеся записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт. -По поводу `SQL DISTINCT` не стесняйтесь пользоваться google, например, [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/) +Запросы SQL можно посмотреть в логах. Т.е. `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующиеся +записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт. По +поводу `SQL DISTINCT` не стесняйтесь пользоваться google, +например, [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/) -> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать? +> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать? hamcrest-all используется в проверках `RootControllerTest`: `org.hamcrest.Matchers.*` -> Jackson мы просто подключаем в помнике, и Спринг будет с ним работать без любых других настроек? +> Jackson мы просто подключаем в помнике, и Spring будет с ним работать без любых других настроек? Да, Spring смотрит в classpath и если видит там Jackson, то подключает интеграцию с ним. -> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ? +> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ? + +Так делают, только при +отношении +агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож +на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}` (не рекомендуется, см ссылку ниже). В случае критериев +поиска или страничных данных они передаются как параметр. Смотри также: -Так делают, только при отношении агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}`. В случае критериев поиска или страничных данных они передаются как параметр. Смотри также: - [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/) -- 10 Best Practices for Better RESTful +- 10 Best Practices + for Better RESTful - [REST resource hierarchy (если кратко: не рекомендуется)](https://stackoverflow.com/questions/15259843/how-to-structure-rest-resource-hierarchy) > Что означает конструкция в `JsonUtil`: `reader.readValues(json)`; -См. Generic Methods. Когда компилятор не может вывести тип, можно его уточнить при вызове generic метода. Неважно, static или нет. +См. Generic Methods. Когда компилятор +не может вывести тип, можно его уточнить при вызове generic метода. Неважно, static или нет. ## ![hw](https://cloud.githubusercontent.com/assets/13649199/13672719/09593080-e6e7-11e5-81d1-5cb629c438ca.png) Домашнее задание HW07 - 1: Добавить тесты контроллеров: - - 1.1 `RootControllerTest.testMeals` для `meals.jsp` - - 1.2 `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`) + - 1.1 `RootControllerTest.getMeals` для `meals.jsp` + - 1.2 Сделать `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`) - 2: Реализовать `MealRestController` и протестировать его через `MealRestControllerTest` - - 2.1 следите, чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить логирование `` для проверки маршрутизации. - - 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать через @DATETIMEFORMAT WITH JAVA 8 DATE-TIME API), а передавать в тестах в формате `ISO_LOCAL_DATE_TIME` (например `'2011-12-03T10:15:30'`). Вызывать `super.getBetween()` пока без проверки на `null`, используя `toLocalDate()/toLocalTime()` (см. Optional п.3) - -#### Optional -- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`). Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры. - - Spring Type Conversion - - Spring Field Formatting - - Difference between Spring MVC formatters and converters -- 4: Протестировать `MealRestController` (SoapUi, curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести в отдельный `md` файл (либо `README.md`) - -**На следующем занятии используется JavaScript/jQuery. Если у вас там пробелы, пройдите его основы** - + - 2.1 следите, чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить + логирование `` для проверки маршрутизации. + - 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать + через @DateTimeFormat with Java + 8 Date-Time API), пока без проверки на `null` (используя `toLocalDate()/toLocalTime()`, см. Optional п.3). В + тестах передавать в формате `ISO_LOCAL_DATE_TIME` ( + например `'2011-12-03T10:15:30'`). + +### Optional + +- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по + времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`) + . Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры. + - Spring Type + Conversion + - Spring Field + Formatting + - + Difference between Spring MVC formatters and converters +- 4: Протестировать `MealRestController` (SoapUi, curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести + в отдельный `md` файл (или `README.md`) +- 5: Добавить в `AdminRestController` и `ProfileRestController` методы получения пользователя вместе с + едой (`getWithMeals`, `/with-meals`). + - [Jackson – Bidirectional Relationships](https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion) + +### Optional 2 + +- 6: Сделать тесты на методы контроллеров `getWithMeals()` (п.5) + +**На следующем занятии используется JavaScript/jQuery. Если у вас там +пробелы, пройдите его основы** --------------------- + ## ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Типичные ошибки и подсказки по реализации + - 1: Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно. -- 2: Jackson и неизменяемые объекты -- 3: Jackson JSON Tutorial -- 4: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в формате JSON) -- 5: При проблемах с собственным форматтером убедитесь, что в конфигурации `Jackson и неизменяемые объекты (для + сериализации MealTo) +- 3: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в + формате JSON) +- 4: При проблемах с собственным форматтером убедитесь, что в конфигурации ` Date: Mon, 23 Aug 2021 11:26:05 +0300 Subject: [PATCH 062/220] Update ReleaseNotes.md --- ReleaseNotes.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 7c03206fe59c..68ebc2ef69d7 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,22 @@ # TopJava Release Notes +### Topjava 23 +- migrate to JDK 16 +- в новой spring-data-jpa `getOne` заменили на `getById` +- в UserUtil#prepareToSave убрал проверку пароля на `hasText`. На UI поле проверяется на `@NotBlank` +- `ProfileRestController#register` делаю по правилам REST (POST без "/register") +- css стили `data-...` сделал [low-case через дефисы](https://stackoverflow.com/questions/36176474/548473) +- `TestMatcher` переименовал в `MatcherFactory` +- Для Swagger UI пометил `AuthorizedUser` аннотацией `@ApiIgnore` + +### Topjava 22 + - очистка пароля `AuthorizedUser#userTo` + - заменил `@SafeHtml`, который удалили из `hibernate.validator` на [Jsoup.clean](https://stackoverflow.com/a/68888601/548473) + - перенес запрет на обновление admin/user в `UserService` + - проверку email на уникальность для update с `id=null` в теле запроса сделал на основе анализа `HttpServletRequest.getRequestURI()` + - проверку класса в `classpath` в `Profiles#getActiveDbProfile` делаю на `org.springframework.util.ClassUtils#isPresent` + - удалил `type="text/javascript"` + ### Topjava 21 - **добавили документирование REST API: Swagger** - мигрировали на JDK 15 и используем текстовые блоки From 86178d56da8d171f8afb5e6b185c5709e9eba26f Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 7 Sep 2021 13:04:44 +0300 Subject: [PATCH 063/220] Update graduation.md --- graduation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graduation.md b/graduation.md index 070424ded69d..f1f2fe3cbf5a 100644 --- a/graduation.md +++ b/graduation.md @@ -26,8 +26,7 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации -- **Сделай новый проект и добавляй туда из Topjava только то что нужно! Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** -- **Рекомендую переписать проект современно: Spring Boot + Swagger/OpenAPI 3.0. Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки.** +- **Рекомендую писать проект современно: Spring Boot + Swagger/OpenAPI 3.0. Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки. Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** - API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend) From 2e65460975c07b60da4c9f3549f21862cd90a62d Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 7 Sep 2021 13:10:51 +0300 Subject: [PATCH 064/220] Update graduation.md --- graduation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graduation.md b/graduation.md index f1f2fe3cbf5a..5457a21193e0 100644 --- a/graduation.md +++ b/graduation.md @@ -63,11 +63,11 @@ _Антуан де Сент-Экзюпери_ - 13: [Use for money in java app](http://stackoverflow.com/a/43051227/548473) - 14: Еще раз про [hashCode/equals в Entity](https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma): не делайте сравнение по полям! - 15: Название пакетов, имен классов для `model/to/web` достаточно стандартные (например `model/domain`). НЕ надо придумывать своих собственных правил -- 16: **Используйте DATA-JPA** (можно без лишней делегации, напрямую из сервиса/контроллера дергать Repository) +- 16: **Используйте DATA-JPA** (без лишней делегации, напрямую из сервиса/контроллера дергать Repository) - 17: В DATA-JPA 2.x используются `Optional`. Попробуйте работать с ними, это безопасный способ работать с null значениями (используйте `orElseThrow`) -- 18: На topjava мы смотрели разные варианты использования, тут делаем максимально просто. С TO многие вещи упрощаются +- 18: На topjava мы смотрели разные варианты c использованием TO и без, критерий - делаем максимально просто. - 19: Проверьте, не торчат ли из кода учебные уши topjava, типа `ProfileRestController.testUTF()`, `AbstractServiceTest.printResult()` или закомментированные `JdbcTemplate`. Назначение этого проекта совсем другое -- 20: ORM работает с объектами. [В простейших случаях fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities), но при этом JPA их уже никак не обрабатывает и не используйте их вместе с объектами. Ссылка на stackoverflow в коде обязательна! +- 20: ORM работает с объектами. [Иногда, для упрощения логики, fk_id как поля допустимы](https://stackoverflow.com/questions/6311776/hibernate-foreign-keys-instead-of-entities) - 21: Проверьте, станет ли код проще с `@AuthenticationPrincipal` (урок 11, Доступ к AuthorizedUser). - 22: Обновление в базе делается через `update`, даже если `delete/insert` сократит java код на несколько строк - 23: Кэширование From d34fee895acb76b633c233f9e8d022c3c19bb844 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 7 Sep 2021 13:15:14 +0300 Subject: [PATCH 065/220] Update graduation.md --- graduation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graduation.md b/graduation.md index 5457a21193e0..02fb145d3b8a 100644 --- a/graduation.md +++ b/graduation.md @@ -25,7 +25,7 @@ P.P.S.: Assume that your API will be used by a frontend developer to build front ----------------------------- ### ![error](https://cloud.githubusercontent.com/assets/13649199/13672935/ef09ec1e-e6e7-11e5-9f79-d1641c05cbe6.png) Рекомендации - +- **Не изобретай велосипедов! Грубая ошибка - пытаться сделать стандартные вещи по-своему, чаще всего криво. На проекте все должно быть единообразно! Ваш проект TopJava - сделай все МАКСИМАЛЬНО в этом стиле. Если тебе кажется, что у тебя есть лучшее решение, чем в TopJava - [пиши мне в личку](https://javaops.ru/#contacts), я всегда открыт для улучшений.** - **Рекомендую писать проект современно: Spring Boot + Swagger/OpenAPI 3.0. Оптимально подойдет код миграции TopJava на Spring Boot в конце стажировки. Локализация, типы ошибок, BeanMatcher, Json View, излишние делегирования и наследования - не нужны!** - **Сначала сделай основной сценарий по ТЗ. Все остальное (если очень хочется, 3 раза подумай) - потом.** - API продумывай с точки зрения не программиста и объектов, а с точки зрения того, кто им будет пользоваться (frontend) From 525c685f6f1a8963d31460d709c390efc40561a0 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 7 Sep 2021 13:22:40 +0300 Subject: [PATCH 066/220] Update graduation.md --- graduation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/graduation.md b/graduation.md index 02fb145d3b8a..eab47ab2e652 100644 --- a/graduation.md +++ b/graduation.md @@ -79,6 +79,7 @@ _Антуан де Сент-Экзюпери_ - одних аннотаций недостаточно. Должны быть `@Valid/@Validation` - проверяйте входные данные при `create/update` **в контроллерах!** В TopJava это `ValidationUtil.checkNew()/assureIdConsistent()` - 25: `readme.md`: + - Будет хорошо поместить сюда ТЗ проекта - Если задание на English, описание пишите также на English (тоже самое относится к языку резюме: вакансия на English предполагает ваше резюме на English) - Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально - ссылка на Swagger. - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) From 516b1a2329dcb4c772d7bb3da6a5c3f306da66a1 Mon Sep 17 00:00:00 2001 From: Java Online Projects Date: Tue, 7 Sep 2021 13:35:14 +0300 Subject: [PATCH 067/220] Update graduation.md --- graduation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graduation.md b/graduation.md index eab47ab2e652..61024535f5de 100644 --- a/graduation.md +++ b/graduation.md @@ -84,6 +84,9 @@ _Антуан де Сент-Экзюпери_ - Требуемые примеры `curl` не прячьте, а пишите здесь! Оптимально - ссылка на Swagger. - Проверяют люди с опытом в Java: не надо писать инструкций, как устанавливать Java и Maven:) - 26: На управление (CRUD) ресторанами и едой должны быть ОТДЕЛЬНЫЕ контроллеры. Не надо все, что может админ, сваливать в одну кучу! +- 27: Git + - Должна быть история ваших комитов с внятными коментариями. Это смотрят. + - Не комить служенбые файлы: логи, DB, настройки IDEA и пр., это сразу тебя зачислит в Junior. Все это должно быть в `.gitignore` ## Попробуйте подергать свое API по всем типичным сценариям ТЗ! - Удобно использовать? Можно сделать проще? Например чтобы проголосовать за ресторан залогиненному юзеру достаточно `restorauntId`. From dc9cd21d141cb5809d9b27814b669dc9733f08c1 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 16 Sep 2021 16:47:56 +0400 Subject: [PATCH 068/220] prepare to HW0 patch --- .../javawebinar/topjava/model/UserMeal.java | 29 ++++++++++++++ .../topjava/model/UserMealWithExcess.java | 30 ++++++++++++++ .../ru/javawebinar/topjava/util/TimeUtil.java | 9 +++++ .../topjava/util/UserMealsUtil.java | 39 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMeal.java create mode 100644 src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java new file mode 100644 index 000000000000..d8f91b127f6a --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMeal.java @@ -0,0 +1,29 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMeal { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + public UserMeal(LocalDateTime dateTime, String description, int calories) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java new file mode 100644 index 000000000000..d0aa431a35d9 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java @@ -0,0 +1,30 @@ +package ru.javawebinar.topjava.model; + +import java.time.LocalDateTime; + +public class UserMealWithExcess { + private final LocalDateTime dateTime; + + private final String description; + + private final int calories; + + private final boolean excess; + + public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { + this.dateTime = dateTime; + this.description = description; + this.calories = calories; + this.excess = excess; + } + + @Override + public String toString() { + return "UserMealWithExcess{" + + "dateTime=" + dateTime + + ", description='" + description + '\'' + + ", calories=" + calories + + ", excess=" + excess + + '}'; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java new file mode 100644 index 000000000000..0ebfdb5fcdcb --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -0,0 +1,9 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalTime; + +public class TimeUtil { + public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java new file mode 100644 index 000000000000..3c171b4a5972 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -0,0 +1,39 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.UserMeal; +import ru.javawebinar.topjava.model.UserMealWithExcess; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; + +public class UserMealsUtil { + public static void main(String[] args) { + List meals = Arrays.asList( + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsTo.forEach(System.out::println); + +// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + } + + public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO return filtered list with excess. Implement by cycles + return null; + } + + public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + // TODO Implement by streams + return null; + } +} From 82ba8ebee04469000321c0b6d95028b67d3e34b9 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 Sep 2021 13:19:06 +0400 Subject: [PATCH 069/220] prepare to HW0 patch --- .../ru/javawebinar/topjava/util/TimeUtil.java | 2 +- .../topjava/util/UserMealsUtil.java | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 0ebfdb5fcdcb..3a91711325ad 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -4,6 +4,6 @@ public class TimeUtil { public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0 && startTime.isBefore(endTime); } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 3c171b4a5972..0cf851ed1af0 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -3,33 +3,55 @@ import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExcess; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.Arrays; -import java.util.List; +import java.util.*; public class UserMealsUtil { public static void main(String[] args) { + List meals = Arrays.asList( new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) ); - List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsTo.forEach(System.out::println); -// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + //List meals = new ArrayList<>(); + List mealsTo = filteredByCycles(meals, LocalTime.of(23, 0), LocalTime.of(8, 0), 400); + + mealsTo.forEach(System.out::println); + //System.out.println(TimeUtil.isBetweenHalfOpen(meals.get(0).getDateTime().toLocalTime(), LocalTime.of(11, 0), LocalTime.of(12, 0))); + //System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { // TODO return filtered list with excess. Implement by cycles - return null; + if(!meals.isEmpty()){ + Map caloriesSumInDay = new HashMap<>(); + for (UserMeal meal : meals) + caloriesSumInDay.put(meal.getDateTime().toLocalDate(), caloriesSumInDay.getOrDefault(meal.getDateTime().toLocalDate(), 0 ) + meal.getCalories()); + + + List userMealWithExcessList = new ArrayList<>(); + for (UserMeal meal : meals) { + if(TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime) && meal.getCalories() > caloriesPerDay) + userMealWithExcessList.add( + new UserMealWithExcess(meal.getDateTime(), meal.getDescription(), meal.getCalories(), caloriesSumInDay.get(meal.getDateTime().toLocalDate()) > caloriesPerDay) + ); + } + return userMealWithExcessList; + } + else + throw new IllegalArgumentException(); + } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { From 5ab6746b951ebcc92558da546f4f8ad82493a714 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 Sep 2021 19:12:06 +0400 Subject: [PATCH 070/220] prepare to HW0 patch --- .../topjava/util/UserMealsUtil.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 0cf851ed1af0..aa45e2050f99 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -8,6 +8,7 @@ import java.time.LocalTime; import java.time.Month; import java.util.*; +import java.util.stream.Collectors; public class UserMealsUtil { public static void main(String[] args) { @@ -23,17 +24,28 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) ); - + //Map caloriesSumInDay = meals.stream().collect(Collectors.groupingBy(UserMeal::getDateTime, Collectors.counting())); + //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); //List meals = new ArrayList<>(); - List mealsTo = filteredByCycles(meals, LocalTime.of(23, 0), LocalTime.of(8, 0), 400); + List mealsFilteredByCycle = filteredByCycles(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 400); + List mealsFilteredByStreams = filteredByStreams(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 600); + + System.out.println("Cycle Filter"); + mealsFilteredByCycle.forEach(System.out::println); + System.out.println("-------------"); + System.out.println("Stream Filter"); + mealsFilteredByStreams.forEach(System.out::println); - mealsTo.forEach(System.out::println); //System.out.println(TimeUtil.isBetweenHalfOpen(meals.get(0).getDateTime().toLocalTime(), LocalTime.of(11, 0), LocalTime.of(12, 0))); //System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); + //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //Map caloriesSumInDay = meals.stream().collect( + //Collectors.groupingBy(UserMeal::getDateTime)).entrySet(Collectors.g Collectors.summingInt(UserMeal::getCalories)); + + } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with excess. Implement by cycles if(!meals.isEmpty()){ Map caloriesSumInDay = new HashMap<>(); for (UserMeal meal : meals) @@ -51,11 +63,16 @@ public static List filteredByCycles(List meals, Lo } else throw new IllegalArgumentException(); - } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { // TODO Implement by streams - return null; + Map caloriesSumInDay = meals.stream(). + collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); + //List userMealWithExcessList = new ArrayList<>(); + return meals.stream().filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)). + map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)). + collect(Collectors.toList()); } + } From 26961be37c69b40f587c3255ad94f783235892bc Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 Sep 2021 19:18:39 +0400 Subject: [PATCH 071/220] prepare to HW0 patch --- .../javawebinar/topjava/util/UserMealsUtil.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index aa45e2050f99..7115d77905ff 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -67,12 +67,16 @@ public static List filteredByCycles(List meals, Lo public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { // TODO Implement by streams - Map caloriesSumInDay = meals.stream(). - collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); - //List userMealWithExcessList = new ArrayList<>(); - return meals.stream().filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)). - map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)). - collect(Collectors.toList()); + if(!meals.isEmpty()){ + Map caloriesSumInDay = meals.stream(). + collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); + //List userMealWithExcessList = new ArrayList<>(); + return meals.stream().filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)). + map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)). + collect(Collectors.toList()); + } + else + throw new IllegalArgumentException(); } } From 69d184bab140a17ab19b20c1489914b416ea4924 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 14:35:18 +0400 Subject: [PATCH 072/220] prepare to HW0 patch --- .../topjava/util/UserMealsUtil.java | 108 ++++++++++++++---- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 7115d77905ff..bf3f9f78247d 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -3,16 +3,18 @@ import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExcess; +import java.sql.Time; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class UserMealsUtil { public static void main(String[] args) { - + System.out.println("--Begin main method--"); List meals = Arrays.asList( new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), @@ -21,36 +23,87 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410), - //Map caloriesSumInDay = meals.stream().collect(Collectors.groupingBy(UserMeal::getDateTime, Collectors.counting())); - //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - //List meals = new ArrayList<>(); - List mealsFilteredByCycle = filteredByCycles(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 400); - List mealsFilteredByStreams = filteredByStreams(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 600); + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 10, 0), "Ужин", 410), + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 13, 0), "Завтрак", 710), + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 20, 0), "Ужин", 333), + + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 0, 0), "Еда на граничное значение", 50), + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 10, 0), "Завтрак", 1111), + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 13, 0), "Обед", 422), + new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) + + + ); + List mealsFilteredByCycle = filteredByCycles(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 400); + List mealsFilteredByStreams = filteredByStreams(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 600); System.out.println("Cycle Filter"); mealsFilteredByCycle.forEach(System.out::println); System.out.println("-------------"); System.out.println("Stream Filter"); mealsFilteredByStreams.forEach(System.out::println); - - //System.out.println(TimeUtil.isBetweenHalfOpen(meals.get(0).getDateTime().toLocalTime(), LocalTime.of(11, 0), LocalTime.of(12, 0))); - //System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - //Map caloriesSumInDay = meals.stream().collect( - //Collectors.groupingBy(UserMeal::getDateTime)).entrySet(Collectors.g Collectors.summingInt(UserMeal::getCalories)); + System.out.println("-------------"); + System.out.println("--End main method--"); } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + System.out.println("--Begin filteredByCycles method--"); if(!meals.isEmpty()){ Map caloriesSumInDay = new HashMap<>(); - for (UserMeal meal : meals) + int calories = 0; + //First variant + //----------------------------------------------------- + for (int n = 0; n < meals.size() - 1; ++n){ + if(meals.get(n).getDateTime().toLocalDate().equals(meals.get(n + 1).getDateTime().toLocalDate())){ + calories += meals.get(n).getCalories(); + if(n == meals.size() - 2) { + caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories += meals.get(n + 1).getCalories()); + calories = 0; + } + } + else{ + calories += meals.get(n).getCalories(); + caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories); + if(n == meals.size() - 2){ + caloriesSumInDay.put(meals.get(n + 1).getDateTime().toLocalDate(), meals.get(n + 1).getCalories()); + } + calories = 0; + } + } + System.out.println("---------First variant--------"); + caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + caloriesSumInDay.clear(); + //----------------------------------------------------- + //Second variant + for (UserMeal meal : meals){ + caloriesSumInDay.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + } + System.out.println("---------Second variant--------"); + caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + caloriesSumInDay.clear(); + //----------------------------------------------------- + //Third variant + for (UserMeal meal : meals){ caloriesSumInDay.put(meal.getDateTime().toLocalDate(), caloriesSumInDay.getOrDefault(meal.getDateTime().toLocalDate(), 0 ) + meal.getCalories()); + } + System.out.println("---------Third variant--------"); + caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //----------------------------------------------------- +/* //Fourth variant + for (UserMeal meal : meals){ + calories = caloriesSumInDay.containsKey(meal.getDateTime().toLocalDate()) ? caloriesSumInDay.get(meal.getDateTime().toLocalDate()) : 0; + calories += meal.getCalories(); + caloriesSumInDay.put(meal.getDateTime().toLocalDate(), calories); + } + System.out.println("---------Fourth variant--------"); + caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //Fourth = Third +*/ List userMealWithExcessList = new ArrayList<>(); for (UserMeal meal : meals) { @@ -59,10 +112,14 @@ public static List filteredByCycles(List meals, Lo new UserMealWithExcess(meal.getDateTime(), meal.getDescription(), meal.getCalories(), caloriesSumInDay.get(meal.getDateTime().toLocalDate()) > caloriesPerDay) ); } + System.out.println("--End filteredByCycles method--"); return userMealWithExcessList; } - else + else { + System.err.println("IllegalArgumentException"); throw new IllegalArgumentException(); + } + //Time complexity: - O(N + N) = O(N) } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -70,13 +127,24 @@ public static List filteredByStreams(List meals, L if(!meals.isEmpty()){ Map caloriesSumInDay = meals.stream(). collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); - //List userMealWithExcessList = new ArrayList<>(); - return meals.stream().filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)). - map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)). - collect(Collectors.toList()); + + return meals.stream() + .filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)) + .map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + + + //Stream filtering uses iteration internally + //Time complexity: - O(N + N) = O(N) } else throw new IllegalArgumentException(); } + public static Map> testFunc(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay){ + return null; + // meals.stream() + // .collect(Collectors.partitioningBy(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime) && userMeal.getCalories() > caloriesPerDay)); + } + } From 55427fec30f40b9b74917ce21d333b76a8b5d95e Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 14:45:52 +0400 Subject: [PATCH 073/220] prepare to HW0 patch --- .../java/ru/javawebinar/topjava/util/UserMealsUtil.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index bf3f9f78247d..1fa81cb687c4 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -33,9 +33,7 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 10, 0), "Завтрак", 1111), new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 13, 0), "Обед", 422), new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) - - - ); + ); List mealsFilteredByCycle = filteredByCycles(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 400); List mealsFilteredByStreams = filteredByStreams(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 600); @@ -46,8 +44,6 @@ public static void main(String[] args) { mealsFilteredByStreams.forEach(System.out::println); System.out.println("-------------"); System.out.println("--End main method--"); - - } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -132,8 +128,6 @@ public static List filteredByStreams(List meals, L .filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)) .map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)) .collect(Collectors.toList()); - - //Stream filtering uses iteration internally //Time complexity: - O(N + N) = O(N) } From b4bb8ddad7b0ebb0e9d38224ea2e9210dd345bdc Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 15:05:32 +0400 Subject: [PATCH 074/220] prepare to HW0 --- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 1fa81cb687c4..e8c4ab55389d 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -119,7 +119,6 @@ public static List filteredByCycles(List meals, Lo } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO Implement by streams if(!meals.isEmpty()){ Map caloriesSumInDay = meals.stream(). collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); From 240854ceadaeecb2ea568f4f01dbf2af85b5f7f3 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 15:07:14 +0400 Subject: [PATCH 075/220] prepare to HW0 --- .../topjava/util/UserMealsUtil.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index e8c4ab55389d..f2f78b579390 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -35,15 +35,6 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) ); - List mealsFilteredByCycle = filteredByCycles(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 400); - List mealsFilteredByStreams = filteredByStreams(meals, LocalTime.of(5, 0), LocalTime.of(15, 0), 600); - System.out.println("Cycle Filter"); - mealsFilteredByCycle.forEach(System.out::println); - System.out.println("-------------"); - System.out.println("Stream Filter"); - mealsFilteredByStreams.forEach(System.out::println); - System.out.println("-------------"); - System.out.println("--End main method--"); } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -88,7 +79,6 @@ public static List filteredByCycles(List meals, Lo } System.out.println("---------Third variant--------"); caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - //----------------------------------------------------- /* //Fourth variant for (UserMeal meal : meals){ @@ -100,7 +90,6 @@ public static List filteredByCycles(List meals, Lo caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); //Fourth = Third */ - List userMealWithExcessList = new ArrayList<>(); for (UserMeal meal : meals) { if(TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime) && meal.getCalories() > caloriesPerDay) @@ -133,11 +122,4 @@ public static List filteredByStreams(List meals, L else throw new IllegalArgumentException(); } - - public static Map> testFunc(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay){ - return null; - // meals.stream() - // .collect(Collectors.partitioningBy(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime) && userMeal.getCalories() > caloriesPerDay)); - } - } From 5b0512140e776f975ab0c1b7621b11fd4a5cde55 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 15:08:26 +0400 Subject: [PATCH 076/220] prepare to HW0 --- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index f2f78b579390..8d048d6bfd88 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -119,7 +119,10 @@ public static List filteredByStreams(List meals, L //Stream filtering uses iteration internally //Time complexity: - O(N + N) = O(N) } - else + else{ + System.err.println("IllegalArgumentException"); throw new IllegalArgumentException(); + } + } } From 971c76e3f3b9cad3d2093ef7cde1a4032283383b Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 18 Sep 2021 15:22:08 +0400 Subject: [PATCH 077/220] prepare to HW0 --- src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index 8d048d6bfd88..c9e26fb251aa 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -14,7 +14,6 @@ public class UserMealsUtil { public static void main(String[] args) { - System.out.println("--Begin main method--"); List meals = Arrays.asList( new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), @@ -34,11 +33,9 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 13, 0), "Обед", 422), new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) ); - } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - System.out.println("--Begin filteredByCycles method--"); if(!meals.isEmpty()){ Map caloriesSumInDay = new HashMap<>(); int calories = 0; @@ -97,7 +94,6 @@ public static List filteredByCycles(List meals, Lo new UserMealWithExcess(meal.getDateTime(), meal.getDescription(), meal.getCalories(), caloriesSumInDay.get(meal.getDateTime().toLocalDate()) > caloriesPerDay) ); } - System.out.println("--End filteredByCycles method--"); return userMealWithExcessList; } else { @@ -123,6 +119,5 @@ public static List filteredByStreams(List meals, L System.err.println("IllegalArgumentException"); throw new IllegalArgumentException(); } - } } From f026314720f7eda5d75cec3e1290ce63524b218b Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 28 Sep 2021 18:29:02 +0400 Subject: [PATCH 078/220] HW0 --- .../topjava/util/UserMealsUtil.java | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index c9e26fb251aa..ad0c415df3c8 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -33,6 +33,16 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 13, 0), "Обед", 422), new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) ); + System.out.println("filteredByCycles method"); + List mealsToCycles = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + assert mealsToCycles != null; + mealsToCycles.forEach(System.out::println); + System.out.println("------------------------"); + System.out.println("filteredByStreams method"); + List mealsToStreams = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + assert mealsToStreams != null; + mealsToStreams.forEach(System.out::println); + } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { @@ -45,7 +55,7 @@ public static List filteredByCycles(List meals, Lo if(meals.get(n).getDateTime().toLocalDate().equals(meals.get(n + 1).getDateTime().toLocalDate())){ calories += meals.get(n).getCalories(); if(n == meals.size() - 2) { - caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories += meals.get(n + 1).getCalories()); + caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories + meals.get(n + 1).getCalories()); calories = 0; } } @@ -58,24 +68,24 @@ public static List filteredByCycles(List meals, Lo calories = 0; } } - System.out.println("---------First variant--------"); - caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //System.out.println("---------First variant--------"); + //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); caloriesSumInDay.clear(); //----------------------------------------------------- //Second variant for (UserMeal meal : meals){ caloriesSumInDay.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); } - System.out.println("---------Second variant--------"); - caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //System.out.println("---------Second variant--------"); + //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); caloriesSumInDay.clear(); //----------------------------------------------------- //Third variant for (UserMeal meal : meals){ caloriesSumInDay.put(meal.getDateTime().toLocalDate(), caloriesSumInDay.getOrDefault(meal.getDateTime().toLocalDate(), 0 ) + meal.getCalories()); } - System.out.println("---------Third variant--------"); - caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); + //System.out.println("---------Third variant--------"); + //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); //----------------------------------------------------- /* //Fourth variant for (UserMeal meal : meals){ @@ -89,7 +99,7 @@ public static List filteredByCycles(List meals, Lo */ List userMealWithExcessList = new ArrayList<>(); for (UserMeal meal : meals) { - if(TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime) && meal.getCalories() > caloriesPerDay) + if(TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) userMealWithExcessList.add( new UserMealWithExcess(meal.getDateTime(), meal.getDescription(), meal.getCalories(), caloriesSumInDay.get(meal.getDateTime().toLocalDate()) > caloriesPerDay) ); @@ -97,8 +107,8 @@ public static List filteredByCycles(List meals, Lo return userMealWithExcessList; } else { - System.err.println("IllegalArgumentException"); - throw new IllegalArgumentException(); + System.err.println("Empty arg input"); + return null; } //Time complexity: - O(N + N) = O(N) } @@ -116,8 +126,8 @@ public static List filteredByStreams(List meals, L //Time complexity: - O(N + N) = O(N) } else{ - System.err.println("IllegalArgumentException"); - throw new IllegalArgumentException(); + System.err.println("Empty arg input"); + return null; } } } From d5531df1391da11cca13f0bbf2906fe9f924b391 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 29 Sep 2021 20:31:21 +0400 Subject: [PATCH 079/220] HW0 --- .../ru/javawebinar/topjava/util/TimeUtil.java | 2 +- .../topjava/util/UserMealsUtil.java | 116 ++---------------- 2 files changed, 12 insertions(+), 106 deletions(-) diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java index 3a91711325ad..0ebfdb5fcdcb 100644 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java @@ -4,6 +4,6 @@ public class TimeUtil { public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0 && startTime.isBefore(endTime); + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; } } diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java index ad0c415df3c8..3c171b4a5972 100644 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java @@ -3,14 +3,11 @@ import ru.javawebinar.topjava.model.UserMeal; import ru.javawebinar.topjava.model.UserMealWithExcess; -import java.sql.Time; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Arrays; +import java.util.List; public class UserMealsUtil { public static void main(String[] args) { @@ -18,116 +15,25 @@ public static void main(String[] args) { new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410), + new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 10, 0), "Ужин", 410), - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 13, 0), "Завтрак", 710), - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 1, 20, 0), "Ужин", 333), + List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsTo.forEach(System.out::println); - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 0, 0), "Еда на граничное значение", 50), - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 10, 0), "Завтрак", 1111), - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 13, 0), "Обед", 422), - new UserMeal(LocalDateTime.of(2020, Month.FEBRUARY, 2, 20, 0), "Ужин", 424) - ); - System.out.println("filteredByCycles method"); - List mealsToCycles = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - assert mealsToCycles != null; - mealsToCycles.forEach(System.out::println); - System.out.println("------------------------"); - System.out.println("filteredByStreams method"); - List mealsToStreams = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - assert mealsToStreams != null; - mealsToStreams.forEach(System.out::println); - +// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); } public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - if(!meals.isEmpty()){ - Map caloriesSumInDay = new HashMap<>(); - int calories = 0; - //First variant - //----------------------------------------------------- - for (int n = 0; n < meals.size() - 1; ++n){ - if(meals.get(n).getDateTime().toLocalDate().equals(meals.get(n + 1).getDateTime().toLocalDate())){ - calories += meals.get(n).getCalories(); - if(n == meals.size() - 2) { - caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories + meals.get(n + 1).getCalories()); - calories = 0; - } - } - else{ - calories += meals.get(n).getCalories(); - caloriesSumInDay.put(meals.get(n).getDateTime().toLocalDate(), calories); - if(n == meals.size() - 2){ - caloriesSumInDay.put(meals.get(n + 1).getDateTime().toLocalDate(), meals.get(n + 1).getCalories()); - } - calories = 0; - } - } - //System.out.println("---------First variant--------"); - //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - caloriesSumInDay.clear(); - //----------------------------------------------------- - //Second variant - for (UserMeal meal : meals){ - caloriesSumInDay.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); - } - //System.out.println("---------Second variant--------"); - //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - caloriesSumInDay.clear(); - //----------------------------------------------------- - //Third variant - for (UserMeal meal : meals){ - caloriesSumInDay.put(meal.getDateTime().toLocalDate(), caloriesSumInDay.getOrDefault(meal.getDateTime().toLocalDate(), 0 ) + meal.getCalories()); - } - //System.out.println("---------Third variant--------"); - //caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - //----------------------------------------------------- -/* //Fourth variant - for (UserMeal meal : meals){ - calories = caloriesSumInDay.containsKey(meal.getDateTime().toLocalDate()) ? caloriesSumInDay.get(meal.getDateTime().toLocalDate()) : 0; - calories += meal.getCalories(); - caloriesSumInDay.put(meal.getDateTime().toLocalDate(), calories); - } - System.out.println("---------Fourth variant--------"); - caloriesSumInDay.forEach((key, value) -> System.out.println(key + "--" + value)); - //Fourth = Third -*/ - List userMealWithExcessList = new ArrayList<>(); - for (UserMeal meal : meals) { - if(TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) - userMealWithExcessList.add( - new UserMealWithExcess(meal.getDateTime(), meal.getDescription(), meal.getCalories(), caloriesSumInDay.get(meal.getDateTime().toLocalDate()) > caloriesPerDay) - ); - } - return userMealWithExcessList; - } - else { - System.err.println("Empty arg input"); - return null; - } - //Time complexity: - O(N + N) = O(N) + // TODO return filtered list with excess. Implement by cycles + return null; } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - if(!meals.isEmpty()){ - Map caloriesSumInDay = meals.stream(). - collect(Collectors.groupingBy(userMeal -> userMeal.getDateTime().toLocalDate(), Collectors.summingInt(UserMeal::getCalories))); - - return meals.stream() - .filter(userMeal -> TimeUtil.isBetweenHalfOpen(userMeal.getDateTime().toLocalTime(), startTime, endTime)) - .map(userMeal -> new UserMealWithExcess(userMeal.getDateTime(),userMeal.getDescription(), userMeal.getCalories(), caloriesSumInDay.get(userMeal.getDateTime().toLocalDate()) > caloriesPerDay)) - .collect(Collectors.toList()); - //Stream filtering uses iteration internally - //Time complexity: - O(N + N) = O(N) - } - else{ - System.err.println("Empty arg input"); - return null; - } + // TODO Implement by streams + return null; } } From a73c00836cadc7bc91d2b00227af75f36baf1637 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 12 Oct 2021 12:21:37 +0400 Subject: [PATCH 080/220] Apply lesson 1 patches. --- .../model/{UserMeal.java => Meal.java} | 14 +++++- .../{UserMealWithExcess.java => MealTo.java} | 6 +-- .../javawebinar/topjava/util/MealsUtil.java | 47 +++++++++++++++++++ .../topjava/util/UserMealsUtil.java | 39 --------------- 4 files changed, 62 insertions(+), 44 deletions(-) rename src/main/java/ru/javawebinar/topjava/model/{UserMeal.java => Meal.java} (62%) rename src/main/java/ru/javawebinar/topjava/model/{UserMealWithExcess.java => MealTo.java} (77%) create mode 100644 src/main/java/ru/javawebinar/topjava/util/MealsUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java b/src/main/java/ru/javawebinar/topjava/model/Meal.java similarity index 62% rename from src/main/java/ru/javawebinar/topjava/model/UserMeal.java rename to src/main/java/ru/javawebinar/topjava/model/Meal.java index d8f91b127f6a..943ff5cd59fa 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMeal.java +++ b/src/main/java/ru/javawebinar/topjava/model/Meal.java @@ -1,15 +1,17 @@ package ru.javawebinar.topjava.model; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; -public class UserMeal { +public class Meal { private final LocalDateTime dateTime; private final String description; private final int calories; - public UserMeal(LocalDateTime dateTime, String description, int calories) { + public Meal(LocalDateTime dateTime, String description, int calories) { this.dateTime = dateTime; this.description = description; this.calories = calories; @@ -26,4 +28,12 @@ public String getDescription() { public int getCalories() { return calories; } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } } diff --git a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java similarity index 77% rename from src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java rename to src/main/java/ru/javawebinar/topjava/model/MealTo.java index d0aa431a35d9..07f04f8dbb9f 100644 --- a/src/main/java/ru/javawebinar/topjava/model/UserMealWithExcess.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -2,7 +2,7 @@ import java.time.LocalDateTime; -public class UserMealWithExcess { +public class MealTo { private final LocalDateTime dateTime; private final String description; @@ -11,7 +11,7 @@ public class UserMealWithExcess { private final boolean excess; - public UserMealWithExcess(LocalDateTime dateTime, String description, int calories, boolean excess) { + public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { this.dateTime = dateTime; this.description = description; this.calories = calories; @@ -20,7 +20,7 @@ public UserMealWithExcess(LocalDateTime dateTime, String description, int calori @Override public String toString() { - return "UserMealWithExcess{" + + return "MealTo{" + "dateTime=" + dateTime + ", description='" + description + '\'' + ", calories=" + calories + diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java new file mode 100644 index 000000000000..c29e1fbbb077 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -0,0 +1,47 @@ +package ru.javawebinar.topjava.util; + +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.model.MealTo; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MealsUtil { + public static void main(String[] args) { + List meals = Arrays.asList( + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + List mealsTo = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + mealsTo.forEach(System.out::println); + } + + public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = meals.stream() + .collect( + Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) +// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + ); + + return meals.stream() + .filter(meal -> TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) + .collect(Collectors.toList()); + } + + private static MealTo createTo(Meal meal, boolean excess) { + return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java deleted file mode 100644 index 3c171b4a5972..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/UserMealsUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.javawebinar.topjava.util; - -import ru.javawebinar.topjava.model.UserMeal; -import ru.javawebinar.topjava.model.UserMealWithExcess; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Month; -import java.util.Arrays; -import java.util.List; - -public class UserMealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new UserMeal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); - - List mealsTo = filteredByCycles(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsTo.forEach(System.out::println); - -// System.out.println(filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000)); - } - - public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO return filtered list with excess. Implement by cycles - return null; - } - - public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { - // TODO Implement by streams - return null; - } -} From dc7aa41912606c694fd328753e4d90c83930d072 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 12 Oct 2021 12:22:32 +0400 Subject: [PATCH 081/220] Apply lesson 1 patches. --- pom.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 19 +++++++++++++++++++ src/main/webapp/index.html | 13 +++++++++++++ src/main/webapp/users.jsp | 11 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/users.jsp diff --git a/pom.xml b/pom.xml index 0b1c2896da5b..b4426c1aa2d6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ru.javawebinar topjava - jar + war 1.0-SNAPSHOT diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..c63810c43593 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + Topjava + + + userServlet + ru.javawebinar.topjava.web.UserServlet + 0 + + + userServlet + /users + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 000000000000..58d8d5ab6aec --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,13 @@ + + + + Java Enterprise (Topjava) + + +

Проект Java Enterprise (Topjava)

+
+ + + diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 000000000000..650c8dda479c --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" %> + + + Users + + +

Home

+
+

Users

+ + \ No newline at end of file From 72e932d141af409dc7e054a61b9d493a08d0d87d Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 12 Oct 2021 12:22:48 +0400 Subject: [PATCH 082/220] Apply lesson 1 patches. --- pom.xml | 28 +++++++++++++++++- .../javawebinar/topjava/web/UserServlet.java | 23 +++++++++++++++ src/main/resources/logback.xml | 29 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/javawebinar/topjava/web/UserServlet.java create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index b4426c1aa2d6..dd7fcafacc65 100644 --- a/pom.xml +++ b/pom.xml @@ -15,11 +15,15 @@ 1.8 UTF-8 UTF-8 + + + 1.2.6 + 1.7.32 topjava - install + package org.apache.maven.plugins @@ -34,6 +38,28 @@ + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + ch.qos.logback + logback-classic + ${logback.version} + runtime + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java new file mode 100644 index 000000000000..ef52d67576c0 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -0,0 +1,23 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.slf4j.LoggerFactory.getLogger; + +public class UserServlet extends HttpServlet { + private static final Logger log = getLogger(UserServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("redirect to users"); + +// request.getRequestDispatcher("/users.jsp").forward(request, response); + response.sendRedirect("users.jsp"); + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 000000000000..e9b900b26669 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + ${TOPJAVA_ROOT}/log/topjava.log + + + UTF-8 + %date %-5level %logger{0} [%file:%line] %msg%n + + + + + + UTF-8 + %-5level %logger{0} [%file:%line] %msg%n + + + + + + + + + + From ceb33cadd0a577d0bbd78ee9c74e876985e791c1 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 14 Oct 2021 11:19:56 +0400 Subject: [PATCH 083/220] Apply lesson 2 patches. --- pom.xml | 6 ++ .../java/ru/javawebinar/topjava/Main.java | 11 --- .../ru/javawebinar/topjava/model/MealTo.java | 20 +++++ .../topjava/util/DateTimeUtil.java | 18 +++++ .../javawebinar/topjava/util/MealsUtil.java | 36 +++++---- .../ru/javawebinar/topjava/util/TimeUtil.java | 9 --- .../javawebinar/topjava/web/MealServlet.java | 77 +++++++++++++++++++ .../javawebinar/topjava/web/UserServlet.java | 6 +- src/main/webapp/WEB-INF/tld/functions.tld | 16 ++++ src/main/webapp/WEB-INF/web.xml | 10 +++ src/main/webapp/index.html | 3 +- src/main/webapp/meals.jsp | 54 +++++++++++++ 12 files changed, 226 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/ru/javawebinar/topjava/Main.java create mode 100644 src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java delete mode 100644 src/main/java/ru/javawebinar/topjava/util/TimeUtil.java create mode 100644 src/main/java/ru/javawebinar/topjava/web/MealServlet.java create mode 100644 src/main/webapp/WEB-INF/tld/functions.tld create mode 100644 src/main/webapp/meals.jsp diff --git a/pom.xml b/pom.xml index dd7fcafacc65..af1aac8af40c 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,12 @@ 4.0.1 provided + + + javax.servlet + jstl + 1.2 + diff --git a/src/main/java/ru/javawebinar/topjava/Main.java b/src/main/java/ru/javawebinar/topjava/Main.java deleted file mode 100644 index c2f9cc618f7c..000000000000 --- a/src/main/java/ru/javawebinar/topjava/Main.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.javawebinar.topjava; - -/** - * @see Demo application - * @see Initial project - */ -public class Main { - public static void main(String[] args) { - System.out.format("Hello TopJava Enterprise!"); - } -} diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 07f04f8dbb9f..117c8bb0b10a 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -18,6 +18,26 @@ public MealTo(LocalDateTime dateTime, String description, int calories, boolean this.excess = excess; } + public Integer getId() { + return id; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public String getDescription() { + return description; + } + + public int getCalories() { + return calories; + } + + public boolean isExcess() { + return excess; + } + @Override public String toString() { return "MealTo{" + diff --git a/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java new file mode 100644 index 000000000000..3f23f83fde65 --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/util/DateTimeUtil.java @@ -0,0 +1,18 @@ +package ru.javawebinar.topjava.util; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { + return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; + } + + public static String toString(LocalDateTime ldt) { + return ldt == null ? "" : ldt.format(DATE_TIME_FORMATTER); + } +} + diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c29e1fbbb077..dddb1cb00ffb 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -10,25 +10,31 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.stream.Collectors; public class MealsUtil { - public static void main(String[] args) { - List meals = Arrays.asList( - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), - new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) - ); - - List mealsTo = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); - mealsTo.forEach(System.out::println); + public static final int DEFAULT_CALORIES_PER_DAY = 2000; + + public static final List meals = Arrays.asList( + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 20, 0), "Ужин", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 0, 0), "Еда на граничное значение", 100), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 10, 0), "Завтрак", 1000), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 13, 0), "Обед", 500), + new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) + ); + + public static List getTos(Collection meals, int caloriesPerDay) { + return filterByPredicate(meals, caloriesPerDay, meal -> true); + } + + public static List getFilteredTos(Collection meals, int caloriesPerDay, LocalTime startTime, LocalTime endTime) { + return filterByPredicate(meals, caloriesPerDay, meal -> DateTimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)); } - public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + public static List filterByPredicate(Collection meals, int caloriesPerDay, Predicate filter) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) @@ -36,7 +42,7 @@ public static List filteredByStreams(List meals, LocalTime startTi ); return meals.stream() - .filter(meal -> TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .filter(filter) .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } diff --git a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java b/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java deleted file mode 100644 index 0ebfdb5fcdcb..000000000000 --- a/src/main/java/ru/javawebinar/topjava/util/TimeUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.javawebinar.topjava.util; - -import java.time.LocalTime; - -public class TimeUtil { - public static boolean isBetweenHalfOpen(LocalTime lt, LocalTime startTime, LocalTime endTime) { - return lt.compareTo(startTime) >= 0 && lt.compareTo(endTime) < 0; - } -} diff --git a/src/main/java/ru/javawebinar/topjava/web/MealServlet.java b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java new file mode 100644 index 000000000000..f3299023724d --- /dev/null +++ b/src/main/java/ru/javawebinar/topjava/web/MealServlet.java @@ -0,0 +1,77 @@ +package ru.javawebinar.topjava.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.javawebinar.topjava.model.Meal; +import ru.javawebinar.topjava.repository.MealRepository; +import ru.javawebinar.topjava.repository.inmemory.InMemoryMealRepository; +import ru.javawebinar.topjava.util.MealsUtil; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +public class MealServlet extends HttpServlet { + private static final Logger log = LoggerFactory.getLogger(MealServlet.class); + + private MealRepository repository; + + @Override + public void init() { + repository = new InMemoryMealRepository(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + String id = request.getParameter("id"); + + Meal meal = new Meal(id.isEmpty() ? null : Integer.valueOf(id), + LocalDateTime.parse(request.getParameter("dateTime")), + request.getParameter("description"), + Integer.parseInt(request.getParameter("calories"))); + + log.info(meal.isNew() ? "Create {}" : "Update {}", meal); + repository.save(meal); + response.sendRedirect("meals"); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String action = request.getParameter("action"); + + switch (action == null ? "all" : action) { + case "delete": + int id = getId(request); + log.info("Delete {}", id); + repository.delete(id); + response.sendRedirect("meals"); + break; + case "create": + case "update": + final Meal meal = "create".equals(action) ? + new Meal(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES), "", 1000) : + repository.get(getId(request)); + request.setAttribute("meal", meal); + request.getRequestDispatcher("/mealForm.jsp").forward(request, response); + break; + case "all": + default: + log.info("getAll"); + request.setAttribute("meals", + MealsUtil.getTos(repository.getAll(), MealsUtil.DEFAULT_CALORIES_PER_DAY)); + request.getRequestDispatcher("/meals.jsp").forward(request, response); + break; + } + } + + private int getId(HttpServletRequest request) { + String paramId = Objects.requireNonNull(request.getParameter("id")); + return Integer.parseInt(paramId); + } +} diff --git a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java index ef52d67576c0..f6cf12e69976 100644 --- a/src/main/java/ru/javawebinar/topjava/web/UserServlet.java +++ b/src/main/java/ru/javawebinar/topjava/web/UserServlet.java @@ -15,9 +15,7 @@ public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.debug("redirect to users"); - -// request.getRequestDispatcher("/users.jsp").forward(request, response); - response.sendRedirect("users.jsp"); + log.debug("forward to users"); + request.getRequestDispatcher("/users.jsp").forward(request, response); } } diff --git a/src/main/webapp/WEB-INF/tld/functions.tld b/src/main/webapp/WEB-INF/tld/functions.tld new file mode 100644 index 000000000000..d138fecdbfb5 --- /dev/null +++ b/src/main/webapp/WEB-INF/tld/functions.tld @@ -0,0 +1,16 @@ + + + + 1.0 + functions + http://topjava.javawebinar.ru/functions + + + formatDateTime + ru.javawebinar.topjava.util.DateTimeUtil + java.lang.String toString(java.time.LocalDateTime) + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index c63810c43593..bd98d3bf3f6a 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,4 +16,14 @@ /users + + mealServlet + ru.javawebinar.topjava.web.MealServlet + 0 + + + mealServlet + /meals + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 58d8d5ab6aec..714683bd3856 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -6,8 +6,9 @@

Проект Java Enterprise (Topjava)


-