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

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

Материалы занятия (скачать все патчи можно через Download папки patch)

correction Рефакторинг

Apply 3_0_1_switch_servlet_4.patch

  • Обновил зависимость до Servlet 4.0. Приложение нормально работает в Tomcat 9.x

Apply 3_0_2_correct_meal_exceed.patch

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

video 1. HW2

ВНИМАНИЕ! При удалении класса из исходников, его скомпилированная версия все еще будет находиться в target (и classpath). В этом случае (или в любом другом, когда проект начинает глючить) сделайте mvn clean.

Apply 3_01_HW2_repository.patch

  • В репозиториях по другому инстанциировал компараторы. Оптимизация анонимных классов не требуется! Почитайте комменты от 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 делает операции атомарно).

Apply 3_02_HW2_meal_layers.patch

  • перенес обработку null-дат в MealRestController.getBetween()
  • по аналогии с AbstractUserController добавил проверку id пользователя, пришедшего в MealRestController (assureIdConsistent, checkNew)
  • поправил интерфейс MealService.update: контроллер и сервис при update ничего не возвращает

Apply 3_03_HW2_optional_MealServlet.patch

  • Убрал логирование (уже есть в контроллере)
  • assureIdConsistent позволяет в контроллере обновлять еду с id=null

Apply 3_04_HW2_optional_filter.patch

  • Вместо MealServlet.resetParam (перемещение параметров фильтрации в атрибуты запроса для отображения в meals.jsp), достаю их в jsp напрямую из запроса через ${param.xxx}
  • В демо фильтр не хранится в сессии (скидывается по F5). Что такое сессия будем разбирать, когда будем делать реальную авторизацию
  • Цвет строк сделал через аттрибут data-mealExcess и css tr[data-mealExcess=...]

Apply 3_05_HW2_optional_select_user.patch

question Вопросы по HW2

Что делает repository.computeIfAbsent / computeIfPresent ?

Всегда пробуйте ответить на вопрос сами. Дастоточно просто зайти по Ctrl+мышка в реализацию и посмотреть javadoc и их дефолтную реализацию

Почему выбрана реализация Map<userId, Map<mealId,Meal>> а не Meal.userId + Map<mealId,Meal> ?

В данном случае двойная мапа - самый эффективный способ хранения, который не требует итерирования (перебора всех значений).

Занятие 3:

Apply 3_06_bean_life_cycle.patch

ВНИМАНИЕ!! Перед накаткой патча создайте каталог test (из корня проекта путь \src\test), иначе часть файлов попадет в src\main.

  • в maven-surefire-plugin (JUnit) поменял кодировку на UTF-8
  • добавил метод InMemoryUserRepositoryImpl.init() для инициализации.
    • save() больше не работает для отсутствующих id
    • автогенерацию id начал со 100
  • пакет mock переименовал в inmemory
  • переименовал тестовые классы

Apply 3_07_add_junit.patch

После патча сделайте clean и обновите зависимости Maven, чтобы IDEA определила сорсы тестов

question Вопрос: почему проект упадет при попытке открыть страничку еды (в логе смотреть самый верх самого нижнего исключения)?

  • поменял @RunWith: SpringRunner is an alias for the SpringJUnit4ClassRunner

Apply 3_08_add_spring_test.patch

Создать в pgAdmin новую базу topjava и новую роль user, пароль password

image

Проверьте, что у 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";

Apply 3_09_add_postgresql.patch

в JdbcUserRepositoryImpl.getByEmail() заменил queryForObject() на query(). Загляните в код: queryForObject бросает EmptyResultDataAccessException вместо нужного нам null.

Apply 3_10_db_implementation.patch

  • в JdbcUserRepositoryImpl.save() добавил проверку на несуществующей в базе User.id
  • в классе JdbcTemplate есть настройки (queryTimeout/ skipResultsProcessing/ skipUndeclaredResults) уровня приложения (если они будут менятся, то, скорее всего, везде в приложении). Мы можем дополнительно сконфигурировать его в spring-db.xml и использовать в конструкторах NamedParameterJdbcTemplate и в SimpleJdbcInsert вместо dataSource.

Проверьте, что в контекст Spring проекта включены оба файла конфигурации

image

Apply 3_11_test_UserService.patch

  • В конструктор User внес registered и делаю копию roles, чтобы роли нельзя было изменить после инициализации.

Apply 3_12_test_logging.patch

Apply 3_13_fix_servlet.patch

Приложение перестало работать, тк. для репозитория мы используем заглушку JdbcMealRepositoryImpl

  • Что такое REST? 10 Best Practices for Better RESTful API
  • Зачем нужна сортировка еды?
  • Можно ли обойтись без MapSqlParameterSource?
  • Как происходит работа с DB в тестах?
  • Как реализовывать RowMapper?
  • Мои комментарии: решения проблем разработчиком.
  • Нужен ли разработчику JavaScript?

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

Какая разница между @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.

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

  • 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, запустив приложение

Optional

  • 5 Сделать MealServiceTest из MealService и реализовать тесты для JdbcMealRepositoryImpl.

По Ctrl+Shift+T (выбрать JUnit4) можно создать тест для конкретного класса, выбрав для него нужные методы. Тестовый класс создастся в папке test в том же пакете, что и тестируемый.

question Как правильно придумать индекс для базы? Указать в нем все поля, комбинация которых создает по смыслу уникальную запись, или какие-то еще есть условия?

Индекс нужно делать по тем полям, по которым будут искаться записи (участвуют в WHERE, ORDER BY). Уникальность - совсем не обязательное условие. Индексы ускоряют поиск по определенным полям таблицы. Они не бесплатные (хранятся в памяти, замедляется вставка), поэтому на всякий случай их делать не надо. Также не строят индексы на колонки с малым процентом уникальности (например поле "М/Ж"). Поля индекса НЕ КОММУТАТИВНЫ и порядок полей в описании индекса НЕОБХОДИМО соблюдать (в силу использования B-деревьев и их производных как поисковый механизм индекса). При построении плана запроса EXPLAIN учитывается количество записей в базе, поэтому вместо индексного поиска (Index Scan) база может выбрать последовательный (Seq Scan). Проверить, работают ли индексы можно отключив Seq Scan. Также см. Queries on the first field of composite index

error Решение проблем

Из каталога main не видятся классы/ресурсы в test

Все что находится в test используется только для тестов и недоступно в основном коде.

Из IDEA не видятся ресурсы в каталоге test

  • Сделайте Reimport All в Maven окне

image

В 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.

image

error Проверка по HW03 (сначала сделайте самостоятельно!)

  • 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).
Clone this wiki locally