-
Notifications
You must be signed in to change notification settings - Fork 35
Lesson 3
Онлайн проект Topjava
Материалы занятия (скачать все патчи можно через Download папки patch)
- Обновил зависимость до Servlet 4.0. Приложение нормально работает в Tomcat 9.x
- Поправил грамматику:
exceed(глагол) наexcess. - Преименовал класс
MealWithExceed, принимаю предложения по лучшему названию класса
1. HW2
ВНИМАНИЕ! При удалении класса из исходников, его скомпилированная версия все еще будет находиться в target (и classpath). В этом случае (или в любом другом, когда проект начинает глючить) сделайте
mvn clean.
- В репозиториях по другому инстанциировал компараторы. Оптимизация анонимных классов не требуется! Почитайте комменты от Holger: Java 8 relieves us from the need to think about such things at all.
- Зарефакторил
<T extends Comparable<? super T>> DateTimeUtil.isBetween(T value, T start, T end). Метод теперь не зависит от date/time, перенес его в общийUtilкласс. Дженерики означают, что мы принимаем экземпляры класса, реализующего компаратор, который умеет сравнивать T или суперклассы от T.- Для фильтрации в
InMemoryMealRepositoryImplпередаюPredicateанологично решению вMealsUtil- В
InMemoryMealRepositoryImpl.save()вместо 2-х разнесенных по времени операций
get(meal.getId(), userId)meals.put(meal.getId(), meal), между которыми может вклинится операция удаления этой еды из другого потока, сделал обновление атомарным, используяConcurrentHashMap.computeIfPresent()(см. псевдокод вMap.computeIfPresent.ConcurrentHashMapв отличие отHashMapделает операции атомарно).
- перенес обработку null-дат в
MealRestController.getBetween()- по аналогии с
AbstractUserControllerдобавил проверку id пользователя, пришедшего вMealRestController (assureIdConsistent, checkNew)- поправил интерфейс
MealService.update: контроллер и сервис приupdateничего не возвращает
- Убрал логирование (уже есть в контроллере)
assureIdConsistentпозволяет в контроллере обновлять еду сid=null
- Вместо
MealServlet.resetParam(перемещение параметров фильтрации в атрибуты запроса для отображения вmeals.jsp), достаю их в jsp напрямую из запроса через${param.xxx}- В демо фильтр не хранится в сессии (скидывается по F5). Что такое сессия будем разбирать, когда будем делать реальную авторизацию
- Цвет строк сделал через аттрибут
data-mealExcessи csstr[data-mealExcess=...]
- Should services always return DTOs, or can they also return domain models?
- Mapping Entity->DTO goes in which application layer: Controller or Service?
Что делает
repository.computeIfAbsent / computeIfPresent?
Всегда пробуйте ответить на вопрос сами. Дастоточно просто зайти по Ctrl+мышка в реализацию и посмотреть javadoc и их дефолтную реализацию
Почему выбрана реализация
Map<userId, Map<mealId,Meal>>а неMeal.userId + Map<mealId,Meal>?
В данном случае двойная мапа - самый эффективный способ хранения, который не требует итерирования (перебора всех значений).
ВНИМАНИЕ!! Перед накаткой патча создайте каталог test (из корня проекта путь \src\test), иначе часть файлов попадет в src\main.
- в
maven-surefire-plugin(JUnit) поменял кодировку на UTF-8- добавил метод
InMemoryUserRepositoryImpl.init()для инициализации.
save()больше не работает для отсутствующихid- автогенерацию id начал со 100
- пакет
mockпереименовал вinmemory- переименовал тестовые классы
После патча сделайте clean и обновите зависимости Maven, чтобы IDEA определила сорсы тестов
Вопрос: почему проект упадет при попытке открыть страничку еды (в логе смотреть самый верх самого нижнего исключения)?
5. Spring Test
- поменял
@RunWith:SpringRunneris an alias for theSpringJUnit4ClassRunner
- PostgreSQL.
- PostgreSQL JDBC Driver
- Установка PostgreSQL. ВНИМАНИЕ! с Postgres 11 есть проблемы.
- Чтобы избежать проблем с правами и именами каталогов, рекомендуют установить postgres в простой каталог, например
C:\Postgresql. И при проблемах создать каталог data на другом диске. Если Unix, проверить права доступа к папке (0700).
Создать в pgAdmin новую базу
topjavaи новую рольuser, парольpassword

Проверьте, что у user в Privileges есть возможность авторизации (особенно для pgAdmin4)
или в UNIX командной строке:
sudo -u postgres psql
CREATE DATABASE topjava;
CREATE USER "user" WITH password 'password';
GRANT ALL PRIVILEGES ON DATABASE topjava TO "user";
- NoSQL or RDBMS. Обзор NoSQL систем. CAP
- DB-Engines Ranking
- JDBC
- Обзор Java persistence solution без ORM: commons-dbutils, Spring JdbcTemplate, MyBatis, JDBI, jOOQ
- Основы:
- Дополнительно:
- Настройка Database в IDEA и запуск SQL.
в
JdbcUserRepositoryImpl.getByEmail()заменилqueryForObject()наquery(). Загляните в код:queryForObjectбросаетEmptyResultDataAccessExceptionвместо нужного намnull.
- в
JdbcUserRepositoryImpl.save()добавил проверку на несуществующей в базеUser.id- в классе
JdbcTemplateесть настройки (queryTimeout/ skipResultsProcessing/ skipUndeclaredResults) уровня приложения (если они будут менятся, то, скорее всего, везде в приложении). Мы можем дополнительно сконфигурировать его вspring-db.xmlи использовать в конструкторахNamedParameterJdbcTemplateи вSimpleJdbcInsertвместоdataSource.
- Подключение Spring Jdbc.
- Конфигурирование DataSource. Property Placeholder
Проверьте, что в контекст Spring проекта включены оба файла конфигурации

- В конструктор
Userвнесregisteredи делаю копиюroles, чтобы роли нельзя было изменить после инициализации.
- Spring Testing Annotations
- The JPA hashCode() / equals() dilemma
- Hibernate: implementing equals() and hashCode()
- Junit Matcher for comparators
- AssertJ custom comparison strategy. AssertJ field by field comparisons
- Новый PostgreSQL JDBC Driver логирует через java.util.logging. Направил логирование в SLF4J
- Поменял формат вывода. См. Logback Layouts
- Ресурсы, которые кладутся в classpath, maven при сборке берет из определенных каталогов
resources(Introduction to the Standard Directory Layout). Их можно настраивать через maven-resources-plugin, меняем в проекте Masterjava.
Приложение перестало работать, тк. для репозитория мы используем заглушку JdbcMealRepositoryImpl
- Что такое REST? 10 Best Practices for Better RESTful API
- Зачем нужна сортировка еды?
- Можно ли обойтись без
MapSqlParameterSource? - Как происходит работа с DB в тестах?
- Как реализовывать RowMapper?
- Мои комментарии: решения проблем разработчиком.
- Нужен ли разработчику JavaScript?
Какая разница между @BeforeClass and @Before?
@BeforeClass выполняется один раз после загрузки класса (поэтому метод может быть только статический), @Before перед каждым тестом.
Также: для чистоты тестов экземпляр тестового класса пересоздается перед каждым тестом: http://stackoverflow.com/questions/6094081/junit-using-constructor-instead-of-before
Тесты в классе в каком-то определенном порядке выполняются ("сверху вниз" например)?
Порядок по умолчанию неопределен, каждый тест должен быть автономен и не зависеть от других. См. также http://stackoverflow.com/questions/3693626/how-to-run-test-methods-in-specific-order-in-junit4
Обязательно ли разворачивать postgreSQL?
Желательно: хорошая и надежная ДБ:) Если совсем не хочется - можно работать со своей любимой RDBMS (поправить initDB.sql) или работать c postgresql в heroku (креденшелы к нему есть сверху в postgres.properties). На следующем уроке добавим HSQLDB, она не требует установки.
Зачем начали индексацию с 100000?
Тут нет "как принято". Так удобно вставлять в базу (если будет потребность) записи вручную не мешая счетчику.
Из 5-го видео - "Логика в базе - большое зло". Можно чуть поподробней об этом?
- Есть успешные проекты с логикой в базе. Те все относительно.
- Логика в базе - это процедуры и триггеры. Нет никакого ООП, переиспользовать код достаточно сложно, никагого рефакторинга, поиска по коду и других плюшек IDE. Нельзя делать всякие вещи типа кэширования, хранения в сесии - это все для логики на стороне java. Например json можно напрямую отдать в процедуру и там парсить и вставлять в таблицы или наоборот - собирать из таблиц и возвращать. А затем потребуется некоторая логика на стороне приложения и все равно придется этот json дополнительно разпарсивать в java. Я на таком проекте делал специальную миграцию, чтобы процедуры мигрировать не как sql скрипты, а каждую процедуру хранить как класс с историей изменений. Если логика: триггеры и простые процедуры записи-чтения, которые не требуют переиспользования кода или проект небольшой это допустимо, иначе проект становится трудно поддерживать. Также иногда используют View для разграничения доступа. Например, для финансовых систем, таблицы проводок доступны только для админ учеток, а View просто не дадут увидеть (тем более изменить) данны обычному оператору на уровне СУБД.
У JUnit есть ассерты и у спринга тоже. Можно ли обойтись без JUnit?
Предусловия и JUnit-тесты совершенно разные вещи. Один другого не заменит, у нас будут предусловия в следующем уроке.
Я так понял VARCHAR быстрее, чем TEXT, когда мы работаем с небольшими записями. Наши записи будут небольшими (255). Почему вы приняли решение перейти на TEXT?
В отличие от MySql в Postgres VARCHAR и TEXT - тоже самое: http://stackoverflow.com/questions/4848964/postgresql-difference-between-text-and-varchar-character-varying
Зачем при создании таблицы мы создаем
CREATE UNIQUE INDEXиCREATE INDEX. При каких запросах он будет использоваться?
UNIQUE индекс нужен для обеcпечения уникальности, DB не даст сделать одинаковый индекс. Индексы используется для скорости выполнения запросов. Обычно они задействуются, когда в запросе есть условия, на которые сделан индекс. Узнать по конкретному запросу можно запросив план запроса: см. Оптимизация запросов. Основы EXPLAIN в PostgreSQL. На измерение производительности с индексами посмотрим в следующем уроке.
А это нормально, что у нас в базе у meals есть userId, а в классе - нет?
Ненормально, когда в приложении есть "лишний" код, который не используется. Для ORM он нам понадобится- мы Meal.user добавим.
Почему мы использует один sequence на разные таблицы?
Мы будем использовать Hibernate, по умолчанию он делает глобальный sequence на все таблицы. В этом подходе есть как плюсы, так и минусы, из плюсов - удобно делать ссылки в коде и в таблицах на при наследовании и мапы в коде. В дополнение: Configure Hibernate to create separate sequence for each table by default.
- 1 Понять, почему перестали работать
SpringMain, InMemoryAdminRestControllerTest, InMemoryAdminRestControllerSpringTest - 2 Дополнить скрипты создания и инициализации базы таблицой MEALS. Запустить скрипты на вашу базу (через Run). Порядок таблиц при DROP и DELETE важен, если они связаны внешними ключами (foreign key, fk). Проверьте, что ваши скрипты работают
- 3 Реализовать через Spring JDBC Template
JdbcMealRepositoryImpl- 3.1. сделать каждый метод за один SQL запрос
- 3.2.
userIdв классMealвставлять НЕ надо (для UI и REST это лишние данные, userId это id залогиненного пользователя) - 3.3.
JbdcTemplateработает через сеттеры. Вместе с конструктором по умолчанию их нужно добавить вMeal - 3.4. Cписок еды должен быть отсортирован (тогда мы его сможем сравнивать с тестовыми данными). Кроме того это требуется для UI и API: последняя еда наверху.
- 4 Проверить работу MealServlet, запустив приложение
- 5 Сделать
MealServiceTestизMealServiceи реализовать тесты дляJdbcMealRepositoryImpl.
По
Ctrl+Shift+T(выбрать JUnit4) можно создать тест для конкретного класса, выбрав для него нужные методы. Тестовый класс создастся в папкеtestв том же пакете, что и тестируемый.
- 5.1 Сделать тестовые данные
MealTestData(точно такие же, как вставляем вpopulateDB.sql). - 5.2 Сделать тесты на чужую еду (delete, get, update) с тем чтобы получить
NotFoundException. - 6 Почнинить
SpringMain, InMemory*Test.InMemory*Testдолжны использовать реализацию в памяти - 7 Сделать индексы к таблице
Meals: запретить создавать у одного и того-же юзера еду с одинаковой dateTime. Индекс на pk (id) postgres создает автоматически: Postgres and Indexes on Foreign Keys and Primary Keys
Как правильно придумать индекс для базы? Указать в нем все поля, комбинация которых создает по смыслу уникальную запись, или какие-то еще есть условия?
Индекс нужно делать по тем полям, по которым будут искаться записи (участвуют в WHERE, ORDER BY). Уникальность - совсем не обязательное условие. Индексы ускоряют поиск по определенным полям таблицы. Они не бесплатные (хранятся в памяти, замедляется вставка), поэтому на всякий случай их делать не надо. Также не строят индексы на колонки с малым процентом уникальности (например поле "М/Ж"). Поля индекса НЕ КОММУТАТИВНЫ и порядок полей в описании индекса НЕОБХОДИМО соблюдать (в силу использования B-деревьев и их производных как поисковый механизм индекса). При построении плана запроса EXPLAIN учитывается количество записей в базе, поэтому вместо индексного поиска (Index Scan) база может выбрать последовательный (Seq Scan). Проверить, работают ли индексы можно отключив Seq Scan. Также см. Queries on the first field of composite index
Из каталога
mainне видятся классы/ресурсы вtest
Все что находится в test используется только для тестов и недоступно в основном коде.
Из
IDEAне видятся ресурсы в каталогеtest
- Сделайте Reimport All в Maven окне

В UserServiceImpl и MealServiceImpl подчеркнуты красным repository, ошибка: Could not autowire. There is more than one bean of 'MealRepository' type.
- Spring test контекст не надо включать в Spring Facets проекта, там должны быть только
spring-app.xmlиspring-db.xml. Для тестовых контекстов поставьте чекбоксCheck test filesв Inspections.

- 1: В
MealTestDataеду делайте константами. Не надоMapконструкций! - 2: SQL case-insensitive, не надо писать в стиле Camel. В POSTGRES возможны case-sensitive значения, их надо в кавычки заключать (обычно не делают).
- 3: ЕЩЕ РАЗ:
InMemoryтесты должны идти наInMemoryрепозитории - 4: Проверьте, что возвращает
JdbcMealRepositoryImplпри обновлении чужой еды - 5: В реализации
JdbcMealRepositoryImplодним SQL запросом используйте возвращаемоеupdateзначениеthe number of rows affected - 6: При тестировании не портите констант из
MealTestData - 7: Проверьте, что все, что относится к тестам, ноходится в каталоге
test(не попадает в сборку проекта) - 8: Еще раз: в тестах проверять через
JUnit Assertили использоватьassertThat().isEqualToнельзя: сравнение будет происходить черезequals, который сравнивает объекты только поid. Мы не можем переопределятьequalsдля объектов модели, тк будем использовать JPA (см. The JPA hashCode() / equals() dilemma) - 9: НЕ делайте склейку SQL запросов вручную из параметров, только через
jdbcTemplateпараметры! См. Внедрение_SQL-кода - 10: Напомню:
BeanPropertyRowMapperработает через отражение. Ему нужны геттеры/сеттеры и имена полей должны "совпадать" с колонкамиResultSet(Column values are mapped based on matching the column name as obtained from result set metadata to public setters for the corresponding properties. The names are matched either directly or by transforming a name separating the parts with underscores to the same name using "camel" case).
Рефакторинг
Разбор домашнего задания HW02