spring приложение с формой и postgresql

Spring boot: маленькое приложение для самых маленьких

image loader

В этой статье мы научимся создавать простые REST приложения. Напишем свое приложение с использованием SpringBoot, создадим свои контроллеры, воспользуемся JPA, подключим PostgreSQL.

Мы будем разрабатывать приложение в 3 этапа:

Создадим и запустим простое REST приложение на SpringBoot

Напишем приложение с сущностями, создадим контроллеры и подключим JPA

Создадим сущности и репозиторий

Напишем сервисную часть приложения

Запустим и протестируем наше приложение, удивимся, что все работает и порадуемся, что провели время с пользой и узнали что-то новое

1. Создадим и запустим простое REST приложение на SpringBoot

Мы пойдем по простому пути, а точнее зайдем на сайт-стартер проектов на SpringBoot: https://start.spring.io/. Выберем сборку gradle + Java. Запустим и соберем проект локально. Для этого через консоль используем команды, и ждем пока погдрузятся все библиотечки и соберется проект.

./gradlew wrapper — загрузка нужной версии wrapper.

Когда мы используем утилиту gradlew (по сути это оболочка, которая использует gradle), нам не нужно иметь заранее установленный Gradle на своем ПК. Эта оболочка может сама скачать и установить нужную версию, разобрать аргументы и выполнить задачи. По сути, используя gradlew, мы можем распространять/делиться проектом со всеми, чтобы использовать одну и ту же версию и функциональность Gradle. Gradlew читает информацию из файла gradle/wrapper/gradle-wrapper.properties.

Мы собрали наше приложение, но пока оно только запускается, но не выполняет никаких других функций. Заходим в файл build.gradle, можно сказать, что это мозг нашего проекта. Здесь хранится вся основная информация для конфигурации и сборки проекта. Сейчас он выглядит так:

Добавим в dependencies следующую зависимости для работы с PEST API:

@RestController = @Controller + @ResponseBody. Аннотация @Controller умеет слушать, получать и отвечать на запросы. А @ResponseBody дает фреймворку понять, что объект, который вы вернули из метода надо прогнать через HttpMessageConverter, чтобы получить готовое к отправке клиенту представление.

В файл application.properties добавим строку:

Вуаля! Теперь наше приложение не только запускается, но и выводит сообщение «spring_boot_example» по адресу: http://localhost:8080/.

2. Напишем приложение с сущностями, создадим контроллеры и подключим JPA

Теперь расширим возможности нашего проекта. Для этого добавим JPA, пару сущностей и напишем для них контроллеры.

2.1. Создадим сущности и репозиторий

Аннотируем классы следующим образом:

Поле Address в классе User выглядит следующим образом:

Осталось сгенерировать методы hashCode, equals и toString. Для User мы генерируем hashCode и equals только по полю login, этого достаточно, так как мы сделали это поле уникальным и отличным от null. Так же для User при переопределении toString мы не используем поле address. Ранее упоминалось, что инициализация ленивая, и значение для этого поля не подтягивается сразу, а если мы попробуем обратиться к hibernate и попросить достать сущность без @Transactional, то упадем с ошибкой.

Полный код сущностей:

Добавим к приложению репозиторий – класс, который умеет работать с базой данных. Реализация очень проста, просто создадим свой интерфейс и унаследуем его от JpaRepository. Все. SpringBoot сам сгенерирует класс, имплементрирующий этот интерфейс и подставит там, где это необходимо.

JpaRepository – это интерфейс фреймворка Spring Data предоставляющий набор стандартных методов JPA для работы с БД.

2.2. Добавим контроллеры.

Аннотация @Data добавляет get, set, toString, equals, hashCode, конструктор по всем полям, т.е. практически полностью генерирует POJO класс.

Теперь можно создать наш полноценный контроллер. Помечаем класс аннотациями @RestController и @RequestMapping(«/api/v1/users»).

У нашего приложения будет 5 контроллеров. Два на получение данных: всех пользователей и по id, на создание, изменение и удаление данных о пользователе.

Получаем список пользователей:

На аннотацию @GetMapping мы уже смотрели ранее. Свойство produces = APPLICATION_JSON_VALUE говорит о том, что данные возвращаются в формате json. В данном методе мы возвращаем лист с данными UserResponse.

Получаем пользователя по id:

Этот метод аналогичен предыдущему, за исключением того, что мы также получаем id пользователя. Аннотация @PathVariable говорит о том что информация извлекается из адреса и передается в переменную указанную в <>.

Обновляем пользователя по id:

Удаляем пользователя по id:

2.3. Напишем сервисную часть приложения.

Теперь посмотрим на логику сервиса. Для начала создадим интерфейс с пятью методами, а затем унаследуемся от него. Наш интерфейс выглядит так:

Создадим новый класс имплементирующий созданный выше интерфейс:

Теперь добавим логику в каждый метод.

Получаем список пользователей:

Хочу обратить внимание, что раньше мы говорили, что если мы обращаемся к сущности address вне @Transactional метода, то упадем с ошибкой. Так вот, тут такое не произойдет, т.к. метод как раз имеет эту аннотацию, hibernate ее видит и поднимает это поле из БД.

Получаем пользователя по id:

По аналогии с buildUserResponse создадим дополнительный метод buildUserRequest.

Это не самый лучший подход, так как для каждой сущности должны быть свои контроллеры и репозитории. Но в контексте нашего приложения Address и User не могут существовать отдельно, поэтому мы можем воспользоваться этим приемом.

Обновляем пользователя по id:

В этом методе мы находим пользователя, по аналогии с методом findById, если такой объект нашелся, то сетим ему поля в методе userUpdate.

Удаляем пользователя по id:

Теперь добавим свой обработчик ошибок. Spring умеет перехватывать ошибки и возвращать вместо них то, что мы захотим. Для этого создадим объект ExceptionResponse, который будет возвращать только сообщение из ошибки.

3. Запустим и протестируем наше приложение, удивимся что все работает и порадуемся, что провели время с пользой и узнали что-то новое.

Добавим креды для подключения к БД в application.properties:

Теперь поднимем базу (для этого я использую докер) и добавляем таблички в БД.

Ура! Наше приложение написано и полностью работает, теперь его можно тестировать.

Подведем итог. Мы написали простое приложение и затронули несколько важных тем. Разработали контроллеры для разных REST методов, написали сервисную частью, включая свой обработчик ошибок. Подключили JPA и воспользовались методами интерфейса JpaRepository.

Источник

Spring Boot. Фоновые задачи и не только

Введение

В данном туториале я хочу привести пример приложения для отправки email-ов юзерам, основываясь на дате их рождения(например с поздравлениями), используя аннотацию Scheduled. Я решил привести данный пример, т к по моему мнению он включает в себя довольно многие вещи, такие как работа с базой данных(в нашем случает это PostgreSQL), Spring Data JPA, новый java 8 time api, email-сервис, создание фоновых задач и небольшую бизнес-логику при этом оставаясь компактным. Сегодня интернет пестрит огромным множеством туториалов которые обычно сводятся к тому как наследоваться от CrudRepository, JpaRepository и тд. Туториал расчитан на то, что вы уже смотрели хотя бы некоторые из них и имеете представление о том, что такое Spring Boot. Я же постараюсь показать пример приложения, которое более обширно показывает его возможности и как с ним работать.

Создание проекта

1. PosgreSQL — в качестве базы данных
2. JPA — доступ к базе
3. Lombok — для удобства и избавления от бойлерплейт кода(не придётся писать геттеры, сеттеры и тд самим), подробнее тут
4. Mail — собственно для работы и отправки email-ов, оф. документация

Указываем группу и артефакт, к примеру com.application и task. Скачиваем и распаковываем проект, затем открываем его в среде разработки, у меня это Intellij IDEA.

База данных

Теперь устанавливаем себе PostgreSQL. Далее создаём базу данных с юзером и паролем. Можно сделать это прямо из IDEA, во вкладке database, можно с помощью командной строки если у вас линукс, следующими командами:

Также на windows это можно сделать с помощью pgAdmin или его альтернатив.

Начало

Открываем наш проект и можем приступать к написанию кода.

Сейчас у нас в проекте есть только один java-файл. Он выглядит примерно так:

Название класса может быть другим в зависимости от имени артефакта, которое вы дали при создании проекта.

Данный класс это точка запуска приложения. Аннотация @SpringBootApplication означает, что это Spring Boot приложение и эквивалентна использованию @Configuration, @EnableAutoConfiguration и @ComponentScan.

Создание модели

Первым делом разделим каталог в котором лежит наш класс для запуска всего приложения и разделим его на три директории: model, repository, service.

Далее в папке model создаем класс User:

Итак мы создали класс с минимальным количеством полей, которые нам необходимы: id юзера, его имя, email и дата рождения.

Пройдёмся по аннотациям: Первые 4 над классом это аннотации lombok, которые генерируют геттеры, сеттеры, метод toString, и конструктор без аргументов.

Entity — указывает Hibernate, что данный класс является сущностью.
Table — название соответствует таблице в бд.
Id — указывает на первичный ключ данного класса.
@GeneratedValue — используется вместе с Id и определяет паметры strategy и generator.
@Column — указывает на имя колонки, которая отображается в свойство сущности, также с помощью nullable = false указываем на то, что поле обязательно.
Email — строка должна быть валидным адресом электронной почты(Используется пакет javax.validation.constraints, а не org.hibernate.validator.constraints, т к в последнем данная аннотация является устаревшей).

Репозиторий

Далее в папке repository создаём интерфейс UserRepository:

Наследование от JpaRepository даёт нам возможность использовать его методы для работы с бд такие как delete, save, findAll и многие другие. Кроме этого при желании мы можем создавать свои методы, по принципу «пишем то что нужно». Т е если нам нужно найти всех юзеров с одинаковым именем, то наш метод будет выглядеть так:

Данный метод в итоге создаст SQL запрос подобный этому:

Позволит выбрать всех юзеров родившихся после определенной даты.

Вообще это довольно обширная тема, на которую довольно много статей и видео. Как например вот это.
Также есть возможность писать свои sql запросы используя JPA-аннотацию Query прямо над телом метода.
Есть возможность использовать два типа синтаксиса: JPQL(язык запросов JPA, подобный SQL использующий вместо таблиц и колонок — сущности, атрибуты и тд) либо собственно используемый нами SQL(тогда добавляется свойство nativeQuery = true). Пример с JPQL:

Для указывания имени параметра запроса можно использовать аннотацию JPA @Param:

Если же мы хотим использовать чисто SQL то:

Мы же создадим метод, который будет брать из базы всех юзеров, у которых email не null, и в которых месяц и день дня рождения будут соответствовать тем, которые мы будем туда передавать. Теперь наш репозиторий будет выглядеть следующим образом:

Пара особенностей репозитория

Первый параметр дженерика должен быть сущностью с которой мы будем работать, а второй соответствовать типу его первичного ключа.
Также типы методов должны соответствовать первому параметру(если не использовать собственный мэппинг).
Если вдруг у вас возник вопрос, почему данный каталог называется repository, а не dao, то это правило хорошего тона в Spring Boot, вы не обязаны делать так-же, просто так принято.

Сервисы

Первым делом создадим в каталоге service интерфейс UserRepositoryService:

Далее здесь же создаем ещё один каталог impl и в нём класс-имплементацию для нашего сервиса:

Теперь разберём наш класс:
Аннотация Service показывает спрингу, что это сервис.
Далее объявляем переменную типа UserRepository и инициализируем её в конструкторе, предварительно пометив его аннотаций @Autowired.
(Можно поставить аннотации прямо над полем repository, но предпочтительнее создать конструктор или сеттер)
@Autowired — спринг находит нужный бин и подставляет его значение в свойство помеченное аннотацией.
Есть возможность создания autowired конструктора с помощью аннотации ломбока над классом:

После конструктора реализуем метод нашего интерфейса и в нём возвращаем метод из репозитория.
Идём дальше: в каталоге service создаём EmailService:

И его имплементацию EmailServiceImpl в impl:

Не буду углубляться в описание, вот ОД.

Теперь в service создадим наш последний и основной класс с шедулером и бизнес-логикой, назовём его к примеру SchedulerService.

Сразу определим в нём следующие поля:

Итак мы инициализировали логгер (также аннотаций ломбока @Slf4j), user и email сервисы в конструкторе(@RequiredArgsConstructor(onConstructor = @__(@Autowired))).

Далее создадим void метод sendMailToUsers а над ним укажем аннотацию:

Данная аннотация позволяет указывать то, когда наш метод будет работать. Мы используем параметр cron, позволяющий указывать расписание по конкретным часам и датам. Также есть такие параметры как fixedRate(определяет интервал между вызовами метода), fixedDelay(определяет интервал с момента окончания работы последнего вызова метода и началом работы следующего), initialDelay(количество миллисекунд для задержки перед первым выполнением fixedRate или fixedDelay) и ещё парочка.

Каждая звездочка в строке cron означает секунды, минуты, часы, дни, месяцы, и дни недели. Вот более подробно. Сейчас значение означает, что проверка будет проходить каждые 10 секунд, это сделано для примера, в дальнейшем мы это поменяем.

Значение cron для удобства можно вынести в константу:

В методе создадим переменную с текущей датой(java date and time api), переменные для месяца и дня, которые берутся из даты, лист юзеров, который инициализируется методом из нашего сервиса и проверку на то, не будет ли он пустым:

Теперь пройдёмся по нему и для каждого юзера создаём переменную для сообщения и вызываем метод send из EmailService, и передаём в него email юзера, заголовок и наше сообщение. В конце оборачиваем всё в try/catch во избежание исключений. Всё, наш метод готов.

Смотрим на весь класс:

Теперь, чтобы иметь возможность запускать фоновые задачи добавим в наш TaskApplication аннотацию @EnableScheduling прямо над @SpringBootApplication, чтобы он в итоге выглядел вот так:

На этом работа с java кодом закончена, нам осталось только в файле application.properties в каталоге resources указать конфиги.

Конфигурация

Исользуются для автоматического создания/обновления таблицы в бд, используя нашу сущность.(В продакшне значения лучше менять на false и none)

Здесь указываются название вашей бд, логин и пароль

Ваш, либо тестовый email и пароль от него. Возможны ошибки доступа к gmail, для этого нужно просто в его настройках разрешить ненадёжные приложения во вкладке безопасность и вход.

Запуск

Идём в наш TaskApplication и запускаем приложение. Если всё сделано правильно, то у вас должны будут идти подобные логи каждые 10 секунд:

Означающие, что наш метод как минимум берёт лист юзеров из бд. Теперь если мы откроем нашу базу(я это делаю прямо в IDEA. Во вкладке database, обычно в правом верхнем углу, есть возможность подключиться к нужной нам бд), то увидим, что там появилась таблица users с соответствующими полями. Создадим новую запись и в качестве дня рождения впишем текущую дату, а в качестве email-a свой собственный. После коммита изменений, каждые 10 секунд должен появляться наш лог сообщающий о том, что email-успешно послан. Проверяем email и если всё сделано корректно, то там нас должны ждать одно или несколько поздравлений с днём рождения(В зависимости от того сколько раз отработал метод). Останавливаем наше приложение и меняем значение CRON на «0 0 10 * * *» означающее, что теперь проверка будет проходить не каждые 10 секунд, а ежедневно в 10 утра, что гарантирует нам отправку только одного поздравления.

Заключение

На основе данного примера можно создавать и решать разнообразные задачи, связанные в частности с фоновыми процессами, главное не бояться экспериментировать. Надеюсь сегодня я смог помочь кому-нибудь лучше понять как работать со Spring Boot, базами данных и java. Если кому-то будет интересно, то я могу написать вторую часть статьи, с добавлением контроллера(чтобы например при желании можно было отключать рассылку email-ов) тестирование и безопасность.

Конструктивная критика и замечания по теме приветствуются.
Отдельное спасибо за комментарии: StanislavL, elegorod, APXEOLOG, Singaporian

Источник

Работа с БД в Spring Boot на примере postgresql

Данная статья является продолжением Spring Boot Restful Service, где была бы раскрыта тема работы с БД в Spring Boot. Давайте рассмотрим эту тему подробнее на примере СУБД postgresql, а в качестве основы возьмём проект, который мы делали в той статье.

Напомню, что проект представляет из себя простой restful-service, который принимает GET-запрос по HTTP и возвращает профиль пользователя по его id. Сам профиль содержит кроме id также имя, фамилию и возраст. Поэтому создадим таблицу profiles в базе данных.

Для поля id можно использовать тип serial. Он представляет собой целое число, которое инкрементируется (увеличивается на 1) автоматически при вставке новой записи в таблицу.

При работе с БД нужно использовать пул подключений к БД, чтобы не создавать их заново при каждом новом sql-запросе, иначе выполнение запроса будет занимать продолжительное время. В качестве пула предлагаю использовать один из наиболее производительных в настоящий момент HikariCP. Также нам нужна поддержка работы с БД со стороны Spring Boot и драйвер для работы с СУБД postgresql. Добавим все эти зависимости в наш проект.

При инициализации пула требуется указать параметры подключения к БД, такие как логин, пароль и т.п. Поскольку данные параметры являются изменяемыми и доступ к ним должен быть ограничен, вынесем их в отдельный текстовый файл и назовём его application.config. Пример содержимого такого файла:

Чтобы Spring Boot увидел данные настройки, абсолютный путь к файлу следует указывать через параметр командной строки —spring.config.location=/путь/до/файла/application.config. Если запускаете проект при помощи Idea, указывайте данный параметр в строке Program Arguments.

@Component
@ConfigurationProperties (prefix = «mainPool» )
public class ConnectionSettings <

private static int DEFAULT_MAX_POOL_SIZE = 5 ;

private String jdbcDriver;
private String jdbcString;
private String jdbcUser;
private String jdbcPassword;
private int jdbcMaxPoolSize = DEFAULT_MAX_POOL_SIZE ;
>

Для каждого из этих полей нужно создать геттер и сеттер, но я для краткости не стал их здесь приводить.

Наш пул подключений максимум может хранить до 5 объектов, однако это значение может быть переопределено через файл настроек.

Теперь создадим ещё один компонент, в котором будем инициализировать сам пул.

@Configuration
public class DatabaseConfig <

private final ConnectionSettings connectionSettings;

@Bean
public DataSource dataSource() <
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(connectionSettings.getJdbcDriver());
hikariConfig.setJdbcUrl(connectionSettings.getJdbcString());
hikariConfig.setUsername(connectionSettings.getJdbcUser());
hikariConfig.setPassword(connectionSettings.getJdbcPassword());
hikariConfig.setMaximumPoolSize(connectionSettings.getJdbcMaxPoolSize());
hikariConfig.setPoolName( «main» );
return new HikariDataSource(hikariConfig);
>
>

Аннотация @Bean позволяет нам вручную создавать бины, которые Spring потом сможет подставлять в другие компоненты.

public interface ProfileDao <

getProfileById( int id);
>

Обратите внимание, что при поиске по id здесь мы будем возвращать типизированный Optional. То есть объект может быть в базе, а может и не быть. И в зависимости от кейса это может трактоваться как ошибка, так и нормальное поведение. Решение о том, ошибка это или нет, будет принимать сервисный слой, который мы рассмотрим далее.

public class Profile <

private int id;
private String firstName;
private String lastName;
private int age;

public int getId() <
return id;
>

// далее идут остальные get- и set-методы.

@Repository
public class ProfileDaoImpl implements ProfileDao <

private static final String SQL_GET_PROFILE_BY_ID =
«select id, first_name, last_name, age from profiles where >;

private final ProfileMapper profileMapper;
private final NamedParameterJdbcTemplate jdbcTemplate;

@Override
public Optional

Обратите внимание, что ВСЕ dao-компоненты снабжаются аннотацией @Repository, которая является частным случаем @Component. Она обеспечивает маппинг ошибок, специфичных для СУБД, в стандартные исключения JDBC.

Сам SQL-запрос для выборки профиля пользователя здесь вынесен в качестве константы в начало класса. Для подстановки целевого id используется именованный параметр с двоеточием в начале, а не простая конкатенация строки и числа. Это позволяет нам сделать запрос более устойчивым к хакерским атакам типа sql injection с одной стороны и более производительным с другой, т.к. СУБД сможет закешировать шаблон данного запроса.

Реализация нашего целевого метода getProfileById() предельно проста. Сначала подставляем требуемый id в sql-запрос через именованный параметр благодаря классу MapSqlParameterSource. Затем вызываем метод queryForObject, передавая ему сам sql-запрос, именованные параметры и маппер полей таблицы. В качестве результата получаем объект Profile или исключение EmptyResultDataAccessException если объект не найден. Исходя из того, что id является первичным ключом в таблице и его значение уникально, мы можем здесь использовать метод queryForObject(). Если бы искали не по уникальному значению, то использовали бы метод query(), который возвращает список объектов. Результат оборачиваем в Optional.

Сам ProfileMapper не хранит внутреннего состояния и всего лишь реализует интерфейс RowMapper, типизированный нашим объектом Profile.

@Override
public Profile mapRow(ResultSet rs, int rowNum) throws SQLException <
Profile profile = new Profile();
profile.setId(rs.getInt( «id» ));
profile.setFirstName(rs.getString( «first_name» ));
profile.setLastName(rs.getString( «last_name» ));
profile.setAge(rs.getInt( «age» ));
return profile;
>
>

На вход он получает ResultSet, представляющий собой результат выборки. Из этого ResultSet мы извлекаем значения полей благодаря методам getInt() и getString() по имени колонки в таблице.

Теперь осталось только внедрить наш ProfileDao в сервисный слой. В предыдущей статье мы уже создавали реализацию сервисного слоя ProfileServiceMock, которая является заглушкой и на самом деле ни в какую базу не ходит. Сейчас мы создадим другую реализацию того же сервиса:

@Primary
@Service
public class ProfileServiceImpl implements ProfileService <

private final ProfileDao profileDao;

Обратите внимание на аннотацию @Primary. Если её не указывать, то спринг не сможет заинжектить в ProfileController нужную нам реализацию сервиса, т.к. по факту у нас их две. Чтобы указать, что по умолчанию нам нужна именно эта реализация, мы и используем данную аннотацию.

Как я уже говорил, именно сервисный слой находится в контексте выполнения запроса и может правильно трактовать пустой результат из dao. В данном случае это ошибка и здесь Optional предоставляет очень удобный метод orElseThrow(), в который мы передаём наше исключение через лямбда-выражение.

На этом примере с двумя реализациями одного интерфейса хорошо виден принцип модульности, которого стоит придерживаться при разработке любых приложений на Spring.

Теперь если вы запустите приложение и выполните GET-запрос по адресу http://localhost:8080/profile/1, то получите профиль с >

Если же выполнить запрос с другим id, то наш ErrorController корректно обработает исключение ProfileNotFoundException и выдаст пользователю json с описанием ошибки:

Итоги

В результате мы добавили в наше приложение слой dao, который ходит в БД, а также создали новую реализацию сервисного слоя, который вместо заглушки теперь использует это dao.

Источник

Понравилась статья? Поделить с друзьями:
Добавить комментарий
  • Как сделать успешный бизнес на ритуальных услугах
  • Выездной кейтеринг в России
  • Риски бизнеса: без чего не обойтись на пути к успеху
  • spring приложение как запустить
  • spring какие бины будут использоваться для настройки приложения