Урок по Spring Boot с использованием Hibernate JPA и MinIO для удобного доступа к базе данных и хранению файлов без написания SQL.
Ask about this video. Answers come from its transcript only — with the timestamp, so you can check them.
Generated from the transcript and can be wrong — check the timestamp.
Key Takeaways
- Hibernate JPA значительно упрощает работу с базой данных, устраняя необходимость писать SQL вручную.
- MinIO является удобным бесплатным аналогом Amazon S3 для локального хранения файлов.
- Spring Data JPA предоставляет готовые методы для CRUD операций, что ускоряет разработку.
- Удаление лишних зависимостей и кода повышает качество и поддерживаемость проекта.
- Кастомные запросы можно легко реализовать с помощью аннотаций, сохраняя гибкость.
What the video covers
- Реализация работы с базой данных через Hibernate JPA без необходимости писать SQL-код вручную.
- Подключение и настройка S3-совместимого хранилища MinIO для хранения изображений.
- Удаление устаревших зависимостей и конфигураций, таких как MyBatis и Liquibase.
- Создание и запуск SQL-скриптов для инициализации схемы базы данных.
- Использование Spring Data JPA репозиториев с расширением JpaRepository для упрощения работы с сущностями.
- Автоматизация CRUD операций с помощью Hibernate, исключая ручное написание запросов.
- Реализация кастомных запросов с использованием аннотаций @Query и nativeQuery.
- Обработка связей между сущностями, например, между Task и User, с помощью Hibernate.
- Конфигурация и запуск локального сервера MinIO для имитации облачного хранилища S3.
- Реализация методов контроллера для загрузки и взаимодействия с файлами в MinIO.
Chapters
- 00:00Введение: Hibernate JPA и MinIO для хранения файлов
- 04:38Удаление MyBatis и настройка репозиториев Spring Data JPA
- 08:53Определение сущностей и параметров ID в репозиториях
- 12:52Реализация методов поиска и кастомных запросов в репозиториях
- 16:28Работа с аннотациями и оптимизация кода репозиториев
- 21:00Настройка и запуск MinIO для локального хранения файлов
- 27:15Реализация контроллеров для загрузки и обработки изображений
- 33:15Завершение: тестирование и проверка работы приложения
Full Transcript — Download SRT & Markdown
Speaker A
Сегодня мы реализуем GP, которая позволит очень удобно, без написания SQL-кода, обращаться к базе данных, не думать о том, как аккуратно сма объекты и как не допустить там ошибки. А также мы подключим S3-совместимое хранилище, где будем хранить картинки для тасо.
Speaker A
совместимое хранилище - это такое хранилище которое используется в в Amazon Web Services и мы будем использовать не его а бесплатный аналог Min он достаточно хорош его можно запустить у себя на локальной машине и у вас будет точно такой же хранилище как и в Облаке
Speaker A
S3-совместимое хранилище — это такое хранилище, которое используется в Amazon Web Services, и мы будем использовать не его, а бесплатный аналог Min. Он достаточно хорош, его можно запустить у себя на локальной машине, и у вас будет точно такое же хранилище, как и в облаке Амазона.
Speaker A
создать схемы и загрузить туда Демо данные мы раньше подключали liqu Base поэтому мы теперь можем это удалить У нас есть вче сетах всё также Мы удалим папку myb так как нам она не понадобится и мы не будем мапить ничего в xml
Speaker A
Для начала мы удалим всё то, что нам не нужно будет сегодня. Это всё, что связано с марами, с вот этим SQL-кодом. Изначально у нас было два файла с демо-датой и схемой, которые запускались для того, чтобы создать схемы и загрузить туда демо-данные.
Speaker A
зависимость из этой папки этот файл будет запускаться Перед каждым стартом перед йм и таким образом мы сможем создавать вот эту схему которая нам необходима для работы приложения зайдём на и поищем один стартер который нам нужен и мы воспользуемся им скопирую последнюю
Speaker A
Мы раньше подключали Liquibase, поэтому мы теперь можем это удалить. У нас есть вче сетах всё также. Мы удалим папку myb, так как нам она не понадобится, и мы не будем мапить ничего в XML руками.
Speaker A
Version и перенесём сюда в [музыка] Base обновим ма чтобы эта зависимость скачалась Теперь же Давайте перейдём к удалению имплементации в репозитории и мапперов мапе нам не понадобятся зачем мы писали мапе раньше скорее для того чтобы увидеть как код работает и
Speaker A
Мы хотим, чтобы у нас создавалась схема Task list. Поэтому нам необходимо выполнить такой скрипт. Он был раньше создан, но не запускался, поэтому мы перемену эту папку в Base, а сам файл здесь перемену в SQL. Теперь же, если мы добавим зависимость из этой папки, этот файл будет запускаться перед каждым стартом, перед йм, и таким образом мы сможем создавать вот эту схему, которая нам необходима для работы приложения.
Speaker A
комментариях писали и некоторым я помогал кто ко мне обращался в Telegram с тем были моменты что в марах были допущены ошибки то есть не так АС назван не такое поле пытается достать человек из лта и здесь легко ошибиться но в хи
Speaker A
Зайдём на и поищем один стартер, который нам нужен, и мы воспользуемся им. Скопирую последнюю версию. Она вышла [музыка] недавно. Вернёмся в код и вставим в pom.xml скопированную информацию. Мы её вставим как раз после Liquibase.
Speaker A
татори здесь Мы удалим аннотацию M она нам не понадобится и напишем extens от репозиторий репозиторий - это как раз таки тот репозиторий который позволит нам с помощью хирта обращаться к базе данных здесь необходимо передать два параметра Первый параметр - это с
Speaker A
Вот эта зависимость, удаляем лишние комментарии и перенесём версию ко всем остальным версиям. Назовём её как PR qu Base Version и перенесём сюда в [музыка] Base. Обновим ма, чтобы эта зависимость скачалась.
Speaker A
ID здесь же мы Удаляем ненужные методы здесь в jp repository мы можем увидеть что есть много уже присутствующих методов Get by ID Get reference by ID есть также некоторые методы которые ВК репозитории определены find find by можем увидеть здесь в крут
Speaker A
Теперь же давайте перейдём к удалению имплементации в репозитории и мапперов. Мапе нам не понадобятся. Зачем мы писали мапе раньше? Скорее для того, чтобы увидеть, как код работает и что делает ORM — это object relation mapping, то есть Hibernate.
Speaker A
сами потому что у нас в тас нету никаких ссылок на US ID то есть hibernate видит такую таку и он не понимает как она связана с User ID поэтому здесь мы опишем этот код самостоятельно assign to User by ID Мы тоже удалим так как это
Speaker A
Что они делают под капотом? То, что мы делали руками, они делают автоматически. Это достаточно удобно. То есть на многие вещи очень простые не нужно писать SQL-запросы, потому что там можно ошибиться. Многие в комментариях писали, и некоторым я помогал, кто ко мне обращался в Telegram, с тем были моменты, что в марах были допущены ошибки.
Speaker A
by ID в итоге у нас остаётся лишь один такой метод с помощью Ctrl Alt L либо comm можно убрать лишние зависимости и теперь здесь напишем запрос с помощью которого мы сможем доставать таски по ID пользователя Для этого необходимо
Speaker A
То есть не так АС названо, не такое поле пытается достать человек из ЛТА, и здесь легко ошибиться. Но в Hibernate такого не будет, потому что он сам задаёт, сам сохраняет. Он не идеальный инструмент, конечно же, но он намного лучше, чем писать своими руками, особенно для простых вещей.
Speaker A
все назовём это [музыка] Join users tasks это все те таблицы которые у нас были on User tasks Task ID раня ID и после этого нужно указать ещё условия где User ID равняется [музыка] ID здесь Мы повесим аннотацию пара аннотация па Ram из or Spring
Speaker A
Тоже удам. У нас осталась реализация репозиториев, и репозитории перейдём в татори. Здесь мы удалим аннотацию M, она нам не понадобится, и напишем extends от репозитория. Репозиторий — это как раз таки тот репозиторий, который позволит нам с помощью Hibernate обращаться к базе данных.
Speaker A
обращению к нему к этому методу выполняется такой сли запрос и преобразовывать результат автоматически в список таск То есть то что мы делали руками где мы могли ошибиться во многих местах это всё на себя берёт hibernate и не ошибается там Теперь же перейдём в
Speaker A
Здесь необходимо передать два параметра. Первый параметр — это сущность, с которой будем работать, то есть это Task. Второй параметр — это тип ID. У нас у таски тип ID Long.
Speaker A
Long find мы можем удалить потому что он уже есть find by username не реализован так - это наше поле имы его оставляем здесь очень легко писать запросы зная поля я покажу это чуть подробнее дальше но нам не нужно никак
Speaker A
То есть если мы перейдём в таску, то мы увидим, что у нас ID Long, и сюда передаём этот [музыка] ID. Здесь же мы удаляем ненужные методы. Здесь в JPA repository мы можем увидеть, что есть много уже присутствующих методов: get by ID, get reference by ID.
Speaker A
будет иметь другой Scale запрос а можно сделать вот так вот всё просто также update Create мы удаляем заменяем их на Save insert User Roll Мы тоже удалим так как это будет изменено в самом юзеры без отдельного метода и Task
Speaker A
Есть также некоторые методы, которые в VK репозитории определены: find, find by. Можем увидеть здесь в крут репозитории find by ID, delete, delete all, delete by ID. То есть такие вещи, которые мы писали сами, они уже здесь реализованы, и нам не нужно их описывать.
Speaker A
сразу добавим native query True Ctrl Alt L удаляем всё лишнее и здесь можем писать запрос пишем Select в целом у нас же это уже реализовано в US repository переходим сюда У нас есть запрос из Task owner и мы можем его
Speaker A
Поэтому find by ID мы удалим. Find all by user ID мы опишем сами, потому что у нас в таске нету никаких ссылок на user ID. То есть Hibernate видит такую таску и он не понимает, как она связана с user ID, поэтому здесь мы опишем этот код самостоятельно.
Speaker A
Апа то есть мы пишем пара и выбираем такое пара чтобы избавиться от того что у нас есть My и мы его уже не будем использовать мы будем использовать мы можем перейти сюда и удалить зависимость myb таким образом размер нашего
Speaker A
Assign to user by ID мы тоже удалим, так как это будет сохраняться просто в пользователя, и это не нужно выносить в отдельный метод. Update, create заменяется на save, то есть есть один метод save, который сам понимает, когда нужно создать, а когда нужно обновить таску, и нормально с этим справляется.
Speaker A
пам этот пам импортируем из Spring Framework Data видим два параметра User ID и Task ID и по ним мы будем искать значение Существует ли такая запись в связке tasks где ID такой ID такой ID То есть то что мы делали раньше только мы теперь
Speaker A
И del мы заменим на delete by ID. В итоге у нас остаётся лишь один такой метод. С помощью Ctrl Alt L либо Comm можно убрать лишние зависимости, и теперь здесь напишем запрос, с помощью которого мы сможем доставать таски по ID пользователя.
Speaker A
несколько полей которые мы сохраняем и которые мы имеем у него есть ID find by ID реализован так как ID должен быть обязательно У ти и у нас сейчас остался реализованный find by username Давайте попробуем поискать пользователя по name
Speaker A
Для этого необходимо написать аннотацию ри кавычки, чтобы у нас был чный комментарий. Теперь же в этом мутим комментарии, мы будем писать здесь, будем выбирать всё, так как у нас достаточно много полей, и нам не нужно пить с помощью эв то, мы можем рту передать их все.
Speaker A
методы мы можем найти всех по какому-то полю то есть уже будет лист мы можем использовать просто find by или find by выбираем find by и здесь все поля которые есть и все возможные комбинации то есть мы можем найти по ID это уже
Speaker A
Назовём это [музыка] Join users tasks — это все те таблицы, которые у нас были. On user tasks task ID раня ID, и после этого нужно указать ещё условия, где user ID равняется [музыка] ID.
Speaker A
нему обратимся то он найдёт всех пользователей точнее найдёт одного пользователя у которого имя будет name и роль роль но так как таких пользователей может быть много то возникнет ошибка что нужно будет когда-нибудь обрабатывать но нам так функциональность не необходима но я
Speaker A
Здесь мы повесим аннотацию пара, аннотация param из Spring Framework Data. Здесь передадим название параметра user ID. Таким образом мы можем обращаться к user ID внутри этого SQL. Удобно.
Speaker A
можете вылезти за 100-150 символов это будет не очень красиво мы это убираем нам это не пригодится нажимаем ctr форматируем код вс хорошо мы изменили репозиторий теперь можно перейти к самим сущностям юзера и Task в Юзе Нам необходимо использовать
Speaker A
Мы видим здесь ошибки, чтобы их брать, необходимо добавить ещё один параметр quy. Всё, мы написали метод, который будет по обращению к нему, к этому методу выполняться такой SQL-запрос и преобразовывать результат автоматически в список таск.
Speaker A
используется обычна таблица users мы её тут описывали в создании SQ то мы будем использовать те же таблицы чтобы сейчас перезапустить программу всё было хорошо мы используем Table users здесь видно что мы не можем получить данные так как у меня удалена эта база данных И
Speaker A
То есть то, что мы делали руками, где мы могли ошибиться во многих местах, это всё на себя берёт Hibernate и не ошибается там. Теперь же перейдём в репозиторий в юе репозитории. Мы сделаем всё аналогично, уберём аннотацию M, здесь дом extends repository.
Speaker A
стратегия здесь есть поля поля usame которые у нас остались Pass Pass коци е та Теперь же мы здесь можем также изменить параметры нейма юзер нейма Но в данном проекте для того чтобы мы убедились что всё работает как надо мы
Speaker A
Сюда передадим это User, то есть мы будем работать в этом репозитории с юзером и тип его ID Long, так как у юзера ID Long, то здесь мы указываем Long. Find мы можем удалить, потому что он уже есть.
Speaker A
сущности Вы можете доверить создание хибер и тогда вы сможете описывать колонки так как нужно то есть здесь можно добавить аннотацию col куда можно передать и передать название колонки по умолчанию название колонки берётся как название поля либо Как название такого поля в кейсе с
Speaker A
Find by username не реализован так — это наше поле имени, его оставляем. Здесь очень легко писать запросы, зная поля. Я покажу это чуть подробнее дальше, но нам не нужно никак указывать, что такое username, так как Hibernate понимает, что у юзера есть поле username, и по нему он может искать.
Speaker A
то есть мы можем сказать что имя должно быть UN и что длина имени должна быть 1024 максимальная у длины имени по умолчанию 255 тут можно увидеть и у юника по умолчанию fse то есть по умолчанию все поля могут
Speaker A
То есть мы указываем, что мы ищем по этому полю и передаём сюда параметр, и всё. Нам не нужно писать отдельный метод, который будет иметь другой SQL-запрос, а можно сделать вот так. Вот всё просто.
Speaker A
этого скрипта то здесь можно указать длину Ну мы уберём эту аннотацию она нам здесь не пригодится так как у нас поле называется достаточно просто и оно само автоматически подтянется сюда Password confirmation мы не будем хранить в базе данных потому что в этом нет никакого
Speaker A
Также update, create мы удаляем, заменяем их на save. Insert user role мы тоже удалим, так как это будет изменено в самом юзере без отдельного метода. И task owner мы оставим, потому что у нас у юзера есть список таск, а у таски нету ссылки на юзера.
Speaker A
то есть мы не можем просто так хранить сет так как hibernate и ДПА не понимают А что и как мы хотим хранить в сеете для начала Мы повесим аннотацию Element Collection так как это у нас yam инам А
Speaker A
И delete на delete by ID мы тоже заменим. Теперь же необходимо написать query. Здесь пишем query value, делаем многострочный комментарий и сразу добавим native query true. Ctrl Alt L, удаляем всё лишнее, и здесь можем писать запрос.
Speaker A
вот записей то есть юзера будет но его ID вот этого объектами о и можно хранить в базе вместо названия R сно об [музыка] наглядно потому что можно поменять местами просто объекты и у них изменится уже ID название умов не так часто меняется
Speaker A
Пишем select. В целом у нас же это уже реализовано в US repository. Переходим сюда. У нас есть запрос из task owner, и мы можем его просто взять и скопировать отсюда.
Speaker A
как-нибудь ещё то здесь после users после названия таблицы было бы нижнее подчёркивание и название вот этого поля также мы отметим нотации где напишем и ty мова э обв в мы будем использовать то есть будем хранить по названию ссылки как у
Speaker A
Select exists from user tasks, где user ID. Здесь мы заменяем на user ID, и здесь мы заменяем на task ID. Необходимо ещё убедиться в том, что вот эти параметры — это параметры не из My баса.
Speaker A
называется вот это поле нам это не очень нравится Тем более у нас уже в скрипте указана Roll то мы будем тоже обращаться к колонки Roll colum name равняется [музыка] Roll То есть у нас теперь будет колонка выглядеть как здесь User ID и Roll
Speaker A
Апа, то есть мы пишем пара и выбираем такое пара, чтобы избавиться от того, что у нас есть My, и мы его уже не будем использовать, мы будем использовать. Мы можем перейти сюда и удалить зависимость myb.
Speaker A
может иметь много таск То есть у юзера есть лист таск Если бы у нас было One toone то тогда бы мы здесь могли хранить только таку так как ассоциация один к одному у юзера есть TAS у таски есть юзер если бы
Speaker A
Таким образом размер нашего приложения станет меньше, оно будет немного быстрее работать, и мы не будем ошибаться с названиями пакетов там, где это необходимо. Удалив и нажав обновление ма, обновить зависимости, и мы теперь видим, что такого пакета нету.
Speaker A
ассоциацию нам не нужно никакие параметры указывать также используем Jo ассоциацию где мы скажем что мы будем обращаться по Task ID к номеру таски иначе будет TAS ID так как называется TAS и добави дишни то есть идентификатор Мы видим что у нас не может
Speaker A
Мы это можем удалить и здесь нормально записать. Пом, этот пом импортируем из Spring Framework Data. Видим два параметра: user ID и task ID, и по ним мы будем искать значение.
Speaker A
мым будет таблица tasks в целом можно Не вешать и такие аннотации то есть создастся по названию класса в ЛОР кейсе и добавится s в конце но чтобы мы убедились в том что у нас всё работает как надо где мы создаём
Speaker A
Существует ли такая запись в связке tasks, где ID такой, ID такой, ID. То есть то, что мы делали раньше, только мы теперь сделали с помощью такой аннотации query. Это уже намного проще. Нам не нужно ma писать, нам не нужно set использовать.
Speaker A
что такое ID и где главный ключ и также мы здесь повесим gener и будет Generation Type то есть автоматически будет Сета и выбираться способ создания ID здесь делаем отступ в тайле у нас название такое же ВП такое же
Speaker A
Мы можем написать метод в репозитории и получить ответ. Теперь же, что я хотел показать. У нас есть юзер, у юзера есть несколько полей, которые мы сохраняем и которые мы имеем. У него есть ID, find by ID реализован, так как ID должен быть обязатель.
Speaker A
этого Теперь же чтобы хранить статус нам нужно отметить его как enumerated и мы его будем хранить тоже по имени value и тут STR То есть тут статус статус Это тоже он мог храниться как стрин и как О О - это номер то есть мы бы хранили 0 2
Speaker A
номер объекта вна дальше у нас Day нормально хранится и с этим [музыка] закон рии У нас есть имплементация репозиториев она теперь нам не пригодится так как у нас не будет имплементации это всё будет сгенерировано самостоятельно закрываем эти классы и перейдём в
Speaker A
сервисы в которых Возможно что-то не работает в сервисе аутентификации всё нормально В таск сервисе есть четы ошибки давайте рассмотрим их здесь мы видим ругается потому что Delete принимает ти но мы можем написать Delete by ID как мы видим
Speaker A
здесь есть много методов То есть если мы поставим точку тут есть куча методов которые мы можем вызвать и мы их сами не описывали но в таск репозитории они уже есть то есть автоматически их создал И мы намного быстрее можем писать код чем
Speaker A
мы его писали руками здесь заменили на Del дальше поступим в так вместо assign мы будем засовывать таку в список та у юзера то есть вот у юзера есть Лик мы его туда будем засовывать и сохранять юзера и автоматически сохранит
Speaker A
этот список поэтому мы здесь достанем юзера US US Рав US Get и сюда передадим Нам необходимо рсс сюда заплети именно интерфейс чтобы это было удобно для подмены US мы получили юзера у таски сеем статус теперь уже добавляем юзеру эту
Speaker A
таку р getas то есть уе тепер добав это и вызываем рсс РС update User То есть у нас обновится юзер и у него засветится эта таска Теперь же в методе update вместо апдейта в репозитории мы вызовем Save Save автоматически поймёт Нужно ли
Speaker A
создать объект или его обновить и так будет намного проще и меньше ошибок теперь перейдём в US IMP здесь мы видим что есть некоторые такие же ошибки вте мы напишем Del by ID здесь мы уберём insert Roll опустим эту строку с помощью Shift
Speaker A
Alt и стрелки вниз то есть Мы создали Set ролей User юзеру его зали и сохраним его есть ещ и update и поймёт где нужно создать А где нужно обновить существующего юзера здесь мы тоже вызываем Save удалим из алике yla конфигурацию
Speaker A
для матиса она нам тоже кстати не пригодится И запустим приложение проверив работу у меня появилось список ошибок сейчас мы разберёмся в том что же произошло как мы видим посу зашли с йм при старте приложение то есть вероятно не может
Speaker A
Запустить вот этот скрипт pog SQL и обновить базу данных также удалим папку myb отсюда она нам не пригодится я опустил версию лиса до 130 вме 14 и приложение успешно запустило вероятно последнем обновлении в новом что-то кардинально изменилось из этого
Speaker A
происходили ошибки либо оно требует новых зависимостей но опустив версию я смог запустить приложение Давайте перейдём в postman и убедимся в том что у нас работает функциональность та же открыв postman я тут открыл необходимые вкладки Я уже создал запрос для того
Speaker A
чтобы загружать изображение которое покажу позже обратимся к тель в базе данных кстати Давайте посмотрим что у нас всё в базу данных вставило перейдём во вкладку Database обновим список баз данных выберем Task list тали те шесть таблиц Task users и
Speaker A
сюда вставили данные которые были описаны в PR Base попробуем войти по демо пользователю и мы увидели сообщение о том что у нас неудачная аутентификация то есть что-то пошло не так и нам необходимо понять что же пошло не так Для того чтобы понять что пошло не
Speaker A
так мы зайдём в контроллер advice и закомментировать логи и исключения сюда в консоль а не возвращать сразу ответ перехватывать их в целом разработка какого-то приложения на таком взаимодействии строится Что читаешь все логи Постоянно что-то работает не так потом находишь
Speaker A
где оно перестаёт работать чинишь и всё начинает работать пробуем снова обратиться У меня есть догадки что-то связано с кэшем как-то но мы не авторизованы Почему меня авторизованы у нас нету необходимого хедера но давайте мы посмотрим что же не
Speaker A
так Возможно проблема на фильтре каким-то образом возникла можем перейти в токен фильтр и остановиться здесь запустим приложение в отладке у меня подозрение что это в токен фильтре потому что у нас вывелось сообщение АВ которое мы писали вот здесь
Speaker A
вот в хендлера то есть который из фильтра хент а не из контроллеров Ну и мы видели что не вывелось сообщение сюда вероятно ошибка здесь попробуем обратиться по сенду и видим да что мы попадаем сюда токен Nal поэтому мы его пропускаем здесь мы
Speaker A
его пропускаем Окей с фильтром всё стало хорошо Теперь мы перейдём в сервис аутентификации Здесь мы получаем юзера по логин реквест по юзернейм и пассворд перейдём сюда видим что логин request хранит в себе username и Password так как и нужно
Speaker A
Теперь же перейдём в gvt User detail serv и попробуем достать юзера по юзернейм из базы данных мы попали сюда US и пробуем достать пользователя мы получили исключение о том что не может реализовать есть да проблема и посмотрим что же у нас изменено в том
Speaker A
что не может здесь реализовать изменено у нас то что мы здесь поменяли некоторые нотации тут немножко изменили класс и он теперь имеет другой двоичный код Но мы же здесь в р сервисе используем кэш то есть по ID Я получаю изк чтото
Speaker A
Я в своём проекте использую аннотации кэша на контроллерах то есть на самом высоком уровне где я не должен вот так вот получать такие ошибки во время разработки То есть сейчас мы разрабатываем приложение у нас что-то поместилось в кэш потому что мы Однажды
Speaker A
обратились в Get by User и теперь если мы поменяем юзера то мы не сможем дальше из кэша доставать того юзера его нужно очищать для этого мы перейдём в доке и C очистим кэш перейдя в кли Нам необходимо ввести Flash All авторизоваться так как
Speaker A
мы это требуем для безопасности и очистить кэш авторизовать теперь можем ввести Flash All и очистить весь кэш вернувшись обратно мы можем перезапустить приложение и скорее всего всё будет очень [музыка] хорошо приложение поднялось Теперь мы можем зайти обратно в postman отправить
Speaker A
запрос и мы всё также видим здесь уже видим название ошиб причину ошибки Закроем все ненужные классы и перейдём в юзера мы не можем достать роли сет ролей потому что нет сессии и нам необходимо это исправить укажем для этого
Speaker A
здесь Type игр То есть он будет доставать полностью всего юзера Когда мы будем к нему обращаться по умолчанию тут лези кажется да и он не достаёт коллекцию ролей когда мы пытаемся обратиться поэтому у нас в токен провайдере выскочила
Speaker A
ошибка попробуем ещё раз мы получили тоже [музыка] ошибку но нет сообщений о том что же произошло [музыка] очистив кэш повторно мы смогли авторизоваться и получить Access токе в целом каширование советую использовать только на продакшене так как оно помогает уже когда приложение
Speaker A
работает Не изменяясь а при разработке локально може жать мы берём Access toen переходим в список таск указываем здесь токен вправляем S видим У нас есть одна таска То есть hibernate нам автоматически её достал из юзера если мы обратимся в таку
Speaker A
2 у юзера 2 то мы видим что у него есть три таски Почему мы это видим У пользователя с id1 под который мы зашли есть роль админа У пользователя с id2 её нет поэтому мы можем обратиться это аннотация в
Speaker A
контроллере Теперь же Давайте добавим здесь возможность хранить картинки у таски перейдём в таку и добавим здесь новое Поле это будет поле которое будет иметь имя картинки как Image это будет тоже Collection Table имя таблицы будет называется tasks imes элемент
Speaker A
Collection это будет так как это будет лист названий картинок и создаём priv list string images То есть у нас будут храниться поле из стрингов Где будут названия картинок картинки будут храниться по определённому адресу то есть знание зная название картинки мы сможем её получить
Speaker A
по этому адресу мы изменили тас теперь нам нужно изменить и TAS DD здесь необходимо тоже добавить Private list list из string images и так как картинки мы можем только отдавать поэтому здесь необходимо повесить Jon Property Access Jon Property read Only то есть мы
Speaker A
нем картинки из наружи Мы изменяем это с помощью другого метода через контроллер дто и таско мы поменяли теперь нам необходимо реализовать логику дальнейшую для взаимодействия с картинками перейдём в TAS [музыка] контроллер и напишем здесь новый метод этот метод будет позволять нам добавлять
Speaker A
картинки он будет на посте по будем обращаться к ID таски и и то есть будем добавлять и также для будет написано дальше мы весим Для безопасности то есть тот кто может обращаться к тас Task по ID и теперь это
Speaker A
будет даже upload назовём upload Image upload Image здесь мы принимаем variable Long ID и также будем принимать картинку она у нас будет validated Model attribute и будем называть её Task Image dto Image dto сейчас мы создадим класс для этого здесь мы сделаем следую
Speaker A
логику ра и сюда передадим то есть пишем здесь всю логику которая должна быть передам ID передам который необходимо будет загрузить начала Task IM и Task IM в пакете Task jav Task в НМ повесим сразу аннотацию dat и в ней будет mul
Speaker A
Като дополнительные поля то мы здесь можем просто добавить поле и намного проще с этим взаимодействовать для Task создадим D скопие этот класс и вставим его в в папку TAS назм его TAS в TAS будет находиться всё тоже самое
Speaker A
только е будет одно ограничение это и сюда мы передадим mess IM точка то есть мы будем лидировать картинку чтобы мы не загружали Ничего здесь за [музыка] импортируем сразу красивыми с помощью расширяемой в целом хороший показатель программирования - это использование
Speaker A
интерфейсов поэтому мы сделаем интерфейс Map который будет описывать то что мы можем мапить объекты друг другу для маппера назовём его е и D здесь будут такие методы е принимает ти D D То есть тут будут классы который описывают ти и dto
Speaker A
Сначала мы сделаем метод to dto который принимает entity и возвращает dto дальше мы сделаем метод list to dto который понимает list entity E entity и Аналогично мы сделаем с entity to entity D [музыка] dto entity list D dtos написав такой интерфейс mappable мы
Speaker A
меньшим количество кодов в других интерфейсах которые маят нам объекты поэтому перейдём в Task maper посмотрим что сейчас здесь 18 строк и достаточно много кода оставим здесь аннотацию maper которая позволяет понять мату что нужно сгенерировать ма и реализуем этот
Speaker A
интерфейс M TAS и Task D Мы даже не реализовали А наследовать как мы видим эти методы переопределения названия вот эти вот мы это удаляем И вынесли это как то есть мы уже сэкономили три строки Это только в одном
Speaker A
мапе таких Маров у нас больше у нас есть ещё р поэтому мы его также изменим сюда [музыка] передаём теперь можно удалить эти теперь в каждом У нас есть два метода и два метода Теперь же создадим ма для TAS ИЖа
Speaker A
мы просто скопие TAS и сделаем [музыка] Task здесь заменяем на то есть написав отдельный инте мы сэкономили по че строчки в каждом ма и мы теперь можем намного проще интерфейсы использовать добавим здесь этот [музыка] тепер вр перейдём в T и добавим тут
Speaker A
метод IM он принимает Task ID и принимает TK Image необходимо реализовать этот метод спустимся сда же вни с [музыка] и написать здесь логику логика у нас будет следующая мы будем получать таку Task Task Рав Task репозиторий Get мы можем обратиться
Speaker A
но мы обратимся к Get by из этого метода который выкидывает исключение Если таска не найдена придам ID получили таку STR Рав IM upload Image Task Get images Add F и tkit Save Task мы Обращаемся к Image сервису который мы реализуем загружаем картинку
Speaker A
получаем имя этой картинки добавляем е в список названий картинок у таски и сохраняем таку всё очень просто и очень мало кода здесь необходимо навесить ещё transactional так как это изменяет состояние базы данных и cash evict чтобы убирался кэш из Get by ID так как мы
Speaker A
добавили картинку и Кэш уже будет невалидный скопирую предыдущую аннотацию всё работает теперь нужно написать Image Service перейдём в пакет сервисов Ja в сервисе будет единственный метод STR и сюда передаём то есть мы будем возвращать название этого ИЖа в пакете имплементации
Speaker A
се IMP который IMP IM также за имплементации сервис и аннотацию ВМ лас Нео для того чтобы загрузить картинку но для этого мы сделаем сейчас несколько дополнительных изменений которые нам необходимы мы вернёмся [аплодисменты] ВФ и напишем здесь создание таблички в
Speaker A
который будем хранить картинки так как мы создаём их руками то нам необходимо создать Ту самую таблицу tasks IM в кото хранятся нижнее подчеркивание ID и IM мы так назвали картинку нам не нужен и мы делаем внешний ключ который
Speaker A
будет называться так как мы его их называли раньше tasks tasks внешний ключ по Task ID tasks по ID тут запятая Всё мы создадим эту таблицу при старте приложения только Нам необходимо удалить базу данных теперь так как мы поменяли ты и не запустится нормальный
Speaker A
Base поэтому мы удаляем все таблицы выбираем Dr О если такое используется на что вдг надо добавить новое тоновый но так у ещё нет продакшена и возможно он появится Через несколько видео то мы можем изменять ещё здесь так как у нас демо Data у нас никто не
Speaker A
пользуется этим приложением Но в идеале мы делаем новый change Set и он исполняется так как Чен сеты не должны быть мутабельные то есть не должны меняться Теперь же когда мы это всё поменяли мы можем обратиться к ма репозиторию и скачать себе ми напишем
Speaker A
здесь ми и установим зависимость ми копируем переходим в по xml и где-нибудь в конце вставим версию ми Мы тоже вырежем и вставим Её куда нужно в обновим ма и он скачает драйвер для ми отлично драйвер для ми скачен Теперь
Speaker A
мы создадим несколько важных бинов и других полезных вещей перейдём App будем сейчас конфигурировать то есть то как мы будем к нему обращаться И где что хранить Min я напомню Это такое отдельное приложение который мы будем хранить картинки для
Speaker A
таск но которая ведёт себя как S3 compatible storage То есть просто хранилище там можно хранить не только картинки можно хранить всё что угодно там есть бакеты бакеты - это такие условные папки в которых всё храниться и мы будем хранить это тоже в
Speaker A
каком-то баке вынесем это в конфигурацию чтобы это можно было удобно менять вне кода то есть мы запустили Например одно приложение где один Бакет используется Второе приложение где второй Бакет используется то есть по максимальному выносим конфигурацию в наружу из
Speaker A
приложения чтобы не менять код в случае изменения вот этой конфигурации сюда Мы также перенесём ur также нам понадобится Access Ke Min Access и также нам понадобится с помощью Ctrl D sec и он будет sec отлично У нас есть какая-то
Speaker A
конфигурация и мы её засвети в точка файле у меня это уже установлено меня будет для входа из по такому порту и адресу у меня будет находиться сервер и бат называется то есть у меня будет на сервере buet и в НМ
Speaker A
буду хранить картинки это всё установится мы теперь воспользуемся проми которые есть в сервисе это Я скрою уже оно мне не пригодится в сервисе У нас есть просы и в проса мы можем к gvt properties обратиться скопирую назовём их minu
Speaker A
properties отлично в mino properties мы будем указывать здесь prefix mino так как в Application properties здесь в Application yle у нас с Minion начинаются все поля тут есть bucket URL Access key и Secret Key поэтому у нас есть string
Speaker A
bucket У нас есть это Мы удалим есть STR STR и sec отлично Мы создали про который мы сможем потом доставать и вставлять где это необходимо теперь же нам необходимо выполнить конфигурацию какую-то и поть нане ли для м config здесь создадим поле
Speaker A
[музыка] priv то есть мы подтянем Те про из Application и будем настраивать их дальше мы создадим B Min это будет обычный собака Min назовём его и он будет возвращать следующее он будет возвращать Min Client Builder и между Бим мы будем
Speaker A
добавлять endp в endp мы вставляем URL То есть это Min properties Get дальше мы вставляем credentials это ACC key и Secret Key properties Get Access key и Min properties Get Secret key и Build всё на этом Клинт создан И мы
Speaker A
наконец можем что-то в него отправлять Кроме этого мы сразу добавим раз уже находимся недалеко Exception возьмём Exception какой-нибудь Access Don с копируем его и назовём Up Exception или IM upload Exception То есть это исключение мы будем выбрасывать Когда что-то будет не
Speaker A
так во время загрузки картинки оно простое здесь есть конструктор и вс когда мы создали это исключение мы его сразу возьмём и обработаем в контролер всё хожу вокруг до около никак до него не дойду но вот уже скоро и мы
Speaker A
дойдём здесь статус будет [музыка] handle Image upload Image upload Exception E принимаем и возвращаем New Exception Body и с сообщением об ошибке чтобы мы могли понимать когда мы загружаем картинку что же не так происходит всё наконец мы можем перейти
Speaker A
к конфигурации сервиса для картинок возвращаемся в сервисе Кстати нужно [музыка] добавить отлично Здесь всё видно воз Для этого нам нужно иметь два поля Private Final Min Client который мы сделали в Бине и проперти которые мы потянули из Application Y Min properties
Speaker A
Min properties в алоу будет следующая логика пока тут нету ничего но мы сейчас напишем следующую логику мы попробуем создать buet Create buet мы попробуем создать buet и если это не получится то есть cch Exception E то мы выкинем
Speaker A
исключение Exception и сюда мы передадим Ну и добавим что IM сообщение у него нету конструктора со стригой string mess сделаем такой конструктор и тут передадим медж Да поменяли отлично если у нас ошибка произошла в бате в создании то мы
Speaker A
выкинем исключение и тогда мы дальше не пойдём Если же всё хорошо Мы создали Бакет И убедились что он есть то есть нам есть куда отправлять эту картинку то мы работаем дальше получаем из этого же и то есть ту картинку которую мы хотим
Speaker A
получить мы здесь получаем дальше делаем проверку если файл пустой из или файл или фай есть суф [музыка] даже можем написать что IM Отлично Теперь же когда уже вс стало хорошо получаем [музыка] ра это будет функция в которой мы
Speaker A
передадим картинку даже фай и получим из неё имя дальше нам понадобится и мы его сохраняем сох тут тоже используем и здесь inp Ой не [музыка] Аа Вот это ра Отлично мы получили Теперь нам нужно словить исключение Exception е ловим любые исключения которые тут
Speaker A
возникают и мы тоже копируем вот это сообщение вставляем его сюда то есть произошла какая-то ошибка дальше если всё хорошо то мы сохраняем и мы сюда передаём и который мы получили из картинки и фа который мы сгенерирована название исходного файла и возра есть
Speaker A
намну вфа храни в список имени картинок у нас здесь есть три метода нам их нужно реализовать это будут приватные методы это не очень красиво но в целом этот подход не [музыка] масштабируемой класс buet здесь мы будем [музыка] нам сда нужно
Speaker A
передать Армен здесь между мы будем имя передадим бата знание того есть ли такой по названию вли здесь выкидывается исключение поэтому мы можем навесить и не покрывать это ВС в Теперь же если не то есть если такого Бакета нету то мы
Speaker A
его создадим и между билдером Им мы сделаем также и сюда передадим propet То есть если такой Бат есть то всё хорошо если его нету то мы создаём его дальше у нас есть метод кой принимает и в зависимости от него создаёт
Speaker A
имя у нас имя будет это Рандомный U ID То есть это такой идентификатор который достаточно уникальный и мы будем его использовать И также у нас будет extens из первоначального файла то есть расширение если например точка У нас то мы будем хранить
Speaker A
[музыка] возвращаем отсюда idid плюс точка и плю То есть у нас будет какой-то какая-то строка число такое Вот точка и расширение файла Теперь же мы напишем [музыка] Get orig из него мы берм subst Get Original Last Index of сюда передаём
Speaker A
точку и плю о то есть мы ищем точку последнюю в названии файла и это будет индекс и после этой точки мы обрезаем стоку и возвращаем это будет расширение от этой строки отлично имя у нас сгенерить картинку самое важное наверное
Speaker A
то ради чего вы сюда и пришли смотреть это видео пишем Sneakers пишем vo IM inam inam STR тут с большой букво дальше мы Обращаемся к клиенту и помещаем Put object object Builder очень красиво сделан ent он сделан через билдеры То
Speaker A
есть у нас есть object аргументы У нас есть Make buet Аргументы и buet EX аргументы Здесь мы помещаем в нужно поместить дальше размер ист и размер частички Вот этот мину о можно почитать документации что он означает Но мы будем использовать именно
Speaker A
это указываем имя Бакета Get buet и указываем в обк это название нание объекта ctr Alt L и это мы всё нормально от Фактори Теперь же у нас написано всё что нам необходимо Но у нас нету ю то есть мы это зависимость добавили но
Speaker A
а куда мы где-то будем хранить поэтому переходим в do comp делаем сразу в котором храни D Local то есть мы же хотим чтобы картинки после перезагрузки не удалялись поэтому мы делаем Vol Min dat в котором будут храниться эти картинки также Тут нужно
Speaker A
немножко добавить в App то что он зависит от Min и то что он зависит ещё от редиса То есть у нас приложение должно запускаться после того как запустится база данных и дальше после редиса мы добавим сервис ю Image будет
Speaker A
Min Min по тегу latest можно указать любую другую версию Котей name назовём его Min также Нам необходимо указать некоторые параметры Min Root User назовём его Min Access Ke который мы указывали в Application Y Ctrl D здесь Root Password и
Speaker A
Secret то есть они сдаются здесь при старте контейнера при старте Мы хотим чтобы выполнялась команда следующая сервер [музыка] то есть мы переходим в эту папку и запускаем сервер в этой папке и адрес консоли делаем 9090 чтобы мы по ней могли
Speaker A
попасть в графический интерфейс он достаточно красивый ну здесь также порты экзим тут есть порты 9090 и 99.000 и добавляем созданный во Vol называется Min Data то есть мы будем хранить эту папку Min в которой мы запускаем сервер Min у
Speaker A
меня не скачен этот образ и мы нажмём чтобы его скачать вот эти Ключи подтянутся из то файла он должен находиться в одной директории с то есть вот эти ключи и так как я запускаю сечас приложение из локальной машины то у
Speaker A
меня H в дор контейнерах уже здесь будет находиться ю название этого сервиса а H и хост базы данных будет по и то есть название вот этих сервисов Ну и вместо базы данных тепер же скаем нажать сюда либо запустить comp
Speaker A
[музыка] Build Да я тут ошибся надо дописать запускаем ещё раз и видим что оно закачалось можем перейти в доке и увидеть что здесь запущен мен можно увидеть консоль по адресу 9090 перейдя сюда Local H 9090 мы видим админку
Speaker A
Min у меня для входа используется Min Admin в качестве логина и пароля входим и видим такое внутри здесь можно почитать много всего всё что есть Нас интересуют бакеты мы можем сразу его создать Бакет у нас называется Посмотрим сейчас
Speaker A
как в алике yle мы указывали название Бакета в бате и в точка файле minum bucket images Да мы его создаём нажимаем Create bucket сейчас мы видим У нас тут ноль байт ноль объектов перейдя в этот Бакет Нам необходимо добавить поля на и
Speaker A
разрешение на чтение То есть я хочу чтобы человек мог смотреть без авторизации картинки Также вы можете попробовать сами настроить авторизацию с правами чтобы картинки доставались через сервер но сейчас чтобы это всё упростить у нас картинки по урлу будут даваться
Speaker A
абсолютно всем но в самом хорошем случае вам необходимо чтобы запрос Шёл на сервер сервер обращался в ми По пользовательским данным То есть когда пользователь залогинен возвращал картинку а не ходил в сразу сам поэтому мы добавим правила чтения для анонимусов то есть неавторизованных
Speaker A
на все каталоги у нас Only есть Отлично Теперь мы можем запустить приложение и проверить его работоспособность тут выбираем Task list Application и перезапуска [музыка] [музыка] приложение запустило Давайте попробуем снова [музыка] авторизоваться авторизовать получаем Access token копируем его Заходим в
Speaker A
список таск для юзера о вставляем сюда Access token новый нажимаем Send видим что здесь уже появилось поле images которое пустой список То есть у нас нету картинок переходим по урлу который мы писали в контроллере для картинок здесь у меня есть таска 4 я
Speaker A
могу в таку 4 загружать здесь я тоже обновляю токен И перехожу в Боде Я указываю имя файл То есть это имя в TAS и вот это имя р файла фа я его указываю и сюда загружаю картинку я выбрал уже одну картинку у
Speaker A
себя в это достаточно удобно сделать нажимаю S я вижу ответ 200 ой и то что у меня не было исключений никаких переходя в яу отправить е вижу здесь добави Кая список по такому-то ID То есть у меня уже тас имеет у себя
Speaker A
картинку мы не делали ограничение на расширение файлов Ну у нас просто называется поле images но вы можете хранить здесь любые файлы То есть можно прикрепить сюда текст можно голосовое сообщение прикрепить в каком-нибудь формате но я решил хранить картинки и
Speaker A
назвал поэтому так то есть мини позволяет хранить абсолютно любые файлы обновив страницу Мы видим что здесь появился один об 233 КБ можем зайти и посмотреть нажимаем на preview и видим вот эту картинку которую я загрузил то есть мы загрузили картинку и
Speaker A
у неё доступен адрес здесь его даже можно найти по имени и нажав на Share мы можем сгенерировать Линку [музыка] Access мы сделаем его каким-нибудь простым тоже ми Амин Min Амин 7 дней и генерируем ссылку по этой ссылке если мы
Speaker A
сейчас перейдём у нас скачается картинка мы можем её посмотреть если мы её запустим где-нибудь в htm либо Если будем сервер возвращать но перейдя по вот этой ссылке у нас скачивается картинка которую мы е сгенерирована скачалась из сервера то
Speaker A
есть всё работает как надо у нас хранятся картинки у таск здесь в базе данных мы можем посмотреть в таск листе если я обновлю сейчас таблицу то я увижу здесь та ID таски и название файла то есть всё работает как
Speaker A
надо Если у вас остались вопро поработает очень много материала в интернете К сожалению есть очень много индусов Но я рад ответить на все ваши вопросы поэтому Можете писать мне в Telegram либо в комментариях на ютубера про этот код про другой код Я
Speaker A
постараюсь ответить и постараюсь ещё в ближайшее время сделать видео на другие темы надеюсь вам это было полезно вы узнали Как хорошо и интересно работает ю Вы можете хранить любое количество файлов у себя и взаимодействовать с ними достаточно быстро и удобно также мы убрали My
Speaker A
убрали собственно ручный SQL код и перешли на который позволяет нам писать намного меньше кода и который сам взаимодействует между объектами и базами данных Спасибо что досмотрели До этого момента код вот этого видео будет нари в ске вписани об общения если остались вопросы
Speaker A
пока [музыка] [музыка]
Topics:Spring BootHibernate JPAMinIOS3-совместимое хранилищеSpring Data JPAORMLiquibaseMyBatisJavaBackend разработка











