[Программирование, Java] Spring boot: маленькое приложение для самых маленьких
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! Меня зовут Варвара и я Java Developer в компании “Цифровые привычки”. Я прошла их курс по Java-разработке и по окончании получила оффер от компании. Сейчас я хочу поделиться материалом с одного из воркшопов, который нам проводил один из лекторов - Алексей Романов, Software Architect и преподаватель Бауманки.В этой статье мы научимся создавать простые REST приложения. Напишем свое приложение с использованием SpringBoot, создадим свои контроллеры, воспользуемся JPA, подключим PostgreSQL. Мы будем разрабатывать приложение в 3 этапа:
- Создадим и запустим простое REST приложение на SpringBoot
- Напишем приложение с сущностями, создадим контроллеры и подключим JPA
- Создадим сущности и репозиторий
- Добавим контроллеры
- Напишем сервисную часть приложения
- Запустим и протестируем наше приложение, удивимся, что все работает и порадуемся, что провели время с пользой и узнали что-то новое
1. Создадим и запустим простое REST приложение на SpringBootМы пойдем по простому пути, а точнее зайдем на сайт-стартер проектов на SpringBoot: https://start.spring.io/. Выберем сборку gradle + Java. Запустим и соберем проект локально. Для этого через консоль используем команды, и ждем пока погдрузятся все библиотечки и соберется проект. ./gradlew wrapper - загрузка нужной версии wrapper. ./gradlew clean build bootRun - сборка проекта, прогон unit-тестов, запуск приложения.Когда мы используем утилиту gradlew (по сути это оболочка, которая использует gradle), нам не нужно иметь заранее установленный Gradle на своем ПК. Эта оболочка может сама скачать и установить нужную версию, разобрать аргументы и выполнить задачи. По сути, используя gradlew, мы можем распространять/делиться проектом со всеми, чтобы использовать одну и ту же версию и функциональность Gradle. Gradlew читает информацию из файла gradle/wrapper/gradle-wrapper.properties.Мы собрали наше приложение, но пока оно только запускается, но не выполняет никаких других функций. Заходим в файл build.gradle, можно сказать, что это мозг нашего проекта. Здесь хранится вся основная информация для конфигурации и сборки проекта. Сейчас он выглядит так:
plugins {
id 'java'
id 'org.springframework.boot' version '2.5.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
group = 'ru.dhabits.spring_boot_example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
Раздел plugins содержит плагины, которые предоставляют необходимые библиотеки и их версии. Например, id 'java' - предоставляет возможность работы с самой java, без нее наше приложение не заработает вообще. Раздел repositories - отвечает за ресурс с которого будут скачаны недостающие библиотеки, по умолчанию это mavenCentral. dependencies - позволяет подключать необходимые нам зависимости, в том числе и стартеры SpringBoot. test - говорит о том, что на этапе прогона тестов будет использован JUnit. Параметры group, version отвечают за группу и версию проекта, а sourceCompatibility - версию java.Добавим в dependencies следующую зависимости для работы с PEST API:
implementation "org.springframework.boot:spring-boot-starter-web"
Создадим контроллер - класс с аннотацией @RestController, который умеет что-то выводить на экран. Добавим ему поле и метод, который возвращает значение этого поля.
@RestController
public class controller {
@Value("${spring.application.name}")
private String name;
@GetMapping
public String getNameApplication() {
return name;
}
}
@RestController = @Controller + @ResponseBody. Аннотация @Controller умеет слушать, получать и отвечать на запросы. А @ResponseBody дает фреймворку понять, что объект, который вы вернули из метода надо прогнать через HttpMessageConverter, чтобы получить готовое к отправке клиенту представление.@Value("${spring.application.name}") - умеет читать из application.properties файла с помощью конструкции ${}. @GetMapping - сообщает SpringBoot, что это get метод и он умеет что-то возвращать.В файл application.properties добавим строку:
spring.application.name=spring_boot_example
Вуаля! Теперь наше приложение не только запускается, но и выводит сообщение "spring_boot_example" по адресу: http://localhost:8080/. 2. Напишем приложение с сущностями, создадим контроллеры и подключим JPAТеперь расширим возможности нашего проекта. Для этого добавим JPA, пару сущностей и напишем для них контроллеры. 2.1. Создадим сущности и репозиторийДля начала, чтоб все заработало - нам необходимо несколько зависимостей, по этому пропишем несколько строк в build.gradle. Data-jpa и postgresql - предоставляют набор библиотек для работы с БД, а конкретно с Postgre. Lombok и lang3 упрощают работу с POJO (Plain Old Java Object — «старый добрый Java-объект»), генерируя простой код. Аннотируем классы следующим образом:
@Getter
@Setter
@Accessors(chain = true)
@Entity
@Table(name = "address")
Аннотации: @Getter и @Setter автоматически сгенерируют get и set методы для каждого поля. @Accessors(chain = true) - говорит что сеттер возвращает значение засеченного поля. @Entity - говорит о том, что этот объект - это POJO и на основе этого класса JPA создаст табличку с именем из @Table. Для каждого поля стоит прописать имя столбца в таблице с помощью аннотации @Column. Для таблицы User поле login - уникально и не должно быть null, поэтому для него пропишем параметры nullable = false, unique = true. Поле Address в классе User выглядит следующим образом:
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id"))
private Address address;
Аннотация @OneToOne - отвечает за связь таблиц один к одному, а fetch = FetchType.LAZY говорит, что это ленивая инициализация. То есть данные из таблицы address будут загружаться по этому ключу только в том случае, когда к ним обратятся. @JoinColumn - говорит о том, как правильно подключиться к таблице address. name = "address_id" - названия первичного ключа, а foreignKey = @ForeignKey(name = "fk_users_address_id") - внешнего. Писать имена для внешнего ключа таким образом (fk_users_address_id) удобно, так как видно какие конкретно таблицы соединяются и как. Осталось сгенерировать методы hashCode, equals и toString. Для User мы генерируем hashCode и equals только по полю login, этого достаточно, так как мы сделали это поле уникальным и отличным от null. Так же для User при переопределении toString мы не используем поле address. Ранее упоминалось, что инициализация ленивая, и значение для этого поля не подтягивается сразу, а если мы попробуем обратиться к hibernate и попросить достать сущность без @Transactional, то упадем с ошибкой. Полный код сущностей: Сущность User:
@Getter
@Setter
@Accessors(chain = true)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "login",nullable = false, unique = true)
private String login;
@Column(name = "firstName")
private String firstName;
@Column(name = "middleName")
private String middleName;
@Column(name = "lastName")
private String lastName;
@Column(name = "age")
private Integer age;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id"))
private Address address;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return login.equals(user.login);
}
@Override
public int hashCode() {
return Objects.hash(login);
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", firstName='" + firstName + '\'' +
", middleName='" + middleName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}
Сущность Address:
@Getter
@Setter
@Accessors(chain = true)
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "street")
private String street;
@Column(name = "city")
private String city;
@Column(name = "building")
private String building;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return street.equals(address.street) && city.equals(address.city) && building.equals(address.building);
}
@Override
public int hashCode() {
return Objects.hash(street, city, building);
}
@Override
public String toString() {
return "Address{" +
"id=" + id +
", street='" + street + '\'' +
", city='" + city + '\'' +
", building='" + building + '\'' +
'}';
}
}
Добавим к приложению репозиторий – класс, который умеет работать с базой данных. Реализация очень проста, просто создадим свой интерфейс и унаследуем его от JpaRepository. Все. SpringBoot сам сгенерирует класс, имплементрирующий этот интерфейс и подставит там, где это необходимо.
public interface UserRepository extends JpaRepository<User, Integer> {}
JpaRepository – это интерфейс фреймворка Spring Data предоставляющий набор стандартных методов JPA для работы с БД.Когда мы работаем с MVC моделью логика приложения разделяется на 2 части. Контроллер, который умеет обрабатывать входные данные и отправлять результат работы приложения. А также сервис, который отвечает за весь остальной функционал. В нашем приложении есть третий уровень - репозиторий, который, непосредственно, работает с БД. Рассмотрим по очереди каждый из модулей. 2.2. Добавим контроллеры.Для начала создадим 4 модели (UserResponse, AddressResponse, CreateAddressRequest и CreateUserRequest) - классы объектов которые будут получать и отправлять наши контроллеры. Можно обойтись и двумя, но мы делаем все по правилам. Отдавать доменные модели наружу считается плохим тоном, так как мы таким образом откроем информацию о БД, к тому же свои модели проще расширять и передавать через них любой объем информации.
@Data
@Accessors(chain = true)
public class UserResponse {
private Integer id;
private String login;
private String firstName;
private String middleName;
private String lastName;
private Integer age;
private AddressResponse address;
}
@Data
@Accessors(chain = true)
public class AddressResponse {
private String street;
private String city;
private String building;
}
@Data
@Accessors(chain = true)
public class CreateUserRequest {
private Integer id;
private String login;
private String firstName;
private String middleName;
private String lastName;
private Integer age;
private CreateAddressRequest address;
}
@Data
@Accessors(chain = true)
public class CreateAddressRequest {
private String street;
private String city;
private String building;
}
Аннотация @Data добавляет get, set, toString, equals, hashCode, конструктор по всем полям, т.е. практически полностью генерирует POJO класс. Теперь можно создать наш полноценный контроллер. Помечаем класс аннотациями @RestController и @RequestMapping("/api/v1/users").
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private UserService userService;
}
@RequestMapping говорит, по какому URL будут доступны наши контроллеры. @RequiredArgsConstructor - генерирует конструктор со всеми параметрами.У нашего приложения будет 5 контроллеров. Два на получение данных: всех пользователей и по id, на создание, изменение и удаление данных о пользователе.Получаем список пользователей:
@GetMapping(produces = APPLICATION_JSON_VALUE)
public List<UserResponse> findAll() {
return userService.findAll();
}
На аннотацию @GetMapping мы уже смотрели ранее. Свойство produces = APPLICATION_JSON_VALUE говорит о том, что данные возвращаются в формате json. В данном методе мы возвращаем лист с данными UserResponse.Получаем пользователя по id:
@GetMapping(value = "/{userId}", produces = APPLICATION_JSON_VALUE)
public UserResponse findById(@PathVariable Integer userId) {
return userService.findById(userId);
}
Этот метод аналогичен предыдущему, за исключением того, что мы также получаем id пользователя. Аннотация @PathVariable говорит о том что информация извлекается из адреса и передается в переменную указанную в {}. Создаем пользователя:
@PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public UserResponse create(@RequestBody CreateUserRequest request) {
return userService.createUser(request);
}
@PostMapping сообщает, что это post метод и он создает новей записи в БД. consumes = APPLICATION_JSON_VALUE - сообщает о том, что возвращаемое значение будет в формате json.Обновляем пользователя по id:
@PatchMapping(value = "/{userId}", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public UserResponse update(@PathVariable Integer userId, @RequestBody CreateUserRequest request) {
return userService.update(userId, request);
}
@PatchMapping - patch метод вносит изменения в существующие записиУдаляем пользователя по id:
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = "/{userId}", produces = APPLICATION_JSON_VALUE)
public void delete(@PathVariable Integer userId) {
userService.delete(userId);
}
@DeleteMapping - delete метод удаляет записи. @ResponseStatus(HttpStatus.NO_CONTENT) возвращает статус, указанный в скобках вместо объекта. 2.3. Напишем сервисную часть приложения.Теперь посмотрим на логику сервиса. Для начала создадим интерфейс с пятью методами, а затем унаследуемся от него. Наш интерфейс выглядит так:
public interface UserService {
@NotNull
List<UserResponse> findAll();
@NotNull
UserResponse findById(@NotNull Integer userId);
@NotNull
UserResponse createUser(@NotNull CreateUserRequest request);
@NotNull
UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request);
void delete(@NotNull Integer userId);
}
Аннотация @NotNull используется в двух случаях. В первом - над методом, во втором - с получаемой переменной, и обозначает, что возвращаемое и получаемые значения не могут быть null. Создадим новый класс имплементирующий созданный выше интерфейс:
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private static UserRepository userRepository;
@NotNull
@Override
@Transactional(readOnly = true)
public List<UserResponse> findAll() {
return null;
}
@NotNull
@Override
@Transactional(readOnly = true)
public UserResponse findById(@NotNull Integer userId) {
return null;
}
@NotNull
@Override
@Transactional
public UserResponse createUser(@NotNull CreateUserRequest request) {
return null;
}
@NotNull
@Override
@Transactional
public UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request) {
return null;
}
@Override
@Transactional
public void delete(@NotNull Integer userId) {
return null;
}
Сам класс аннотирован @Service. Над методами добавим @Transactional - идентификатор данного метода как критической секции. Для методов, которые только читают данные, поставим флажок readOnly = true. Теперь добавим логику в каждый метод.Получаем список пользователей:
public List<UserResponse> findAll() {
return userRepository.findAll()
.stream()
.map(this::buildUserResponse)
.collect(Collectors.toList());
}
@NotNull
private UserResponse buildUserResponse(@NotNull User user) {
return new UserResponse()
.setId(user.getId())
.setLogin(user.getLogin())
.setAge(user.getAge())
.setFirstName(user.getFirstName())
.setMiddleName(user.getMiddleName())
.setLastName(user.getLastName())
.setAddress(new AddressResponse()
.setCity(user.getAddress().getCity())
.setBuilding(user.getAddress().getBuilding())
.setStreet(user.getAddress().getStreet()));
}
userRepository.findAll - базовый метод jpa, возвращает, список сущностей типа user, описанные нами ранее. Для этого метода мы просто добавим build метод, который будет конвертировать один объект в другой.Хочу обратить внимание, что раньше мы говорили, что если мы обращаемся к сущности address вне @Transactional метода, то упадем с ошибкой. Так вот, тут такое не произойдет, т.к. метод как раз имеет эту аннотацию, hibernate ее видит и поднимает это поле из БД. Получаем пользователя по id:
public UserResponse findById(@NotNull Integer userId) {
return userRepository.findById(userId)
.map(this::buildUserResponse)
.orElseThrow(() -> new EntityNotFoundException("User " + userId + " is not found"));
}
userRepository.findById - аналогичен findAll, но возвращает 1 объект, а не список. Т.к. из БД нам приходит Optional, то сразу обработаем вариант отсутствия объекта и выбросим ошибку EntityNotFoundException. (дальше напишем свой обработчик для этой ошибки) Создаем пользователя:
public UserResponse createUser(@NotNull CreateUserRequest request) {
User user = buildUserRequest(request);
return buildUserResponse(userRepository.save(user));
}
@NotNull
private User buildUserRequest(@NotNull CreateUserRequest request) {
return new User()
.setLogin(request.getLogin())
.setAge(request.getAge())
.setFirstName(request.getFirstName())
.setMiddleName(request.getMiddleName())
.setLastName(request.getLastName())
.setAddress(new Address()
.setCity(request.getAddress().getCity())
.setBuilding(request.getAddress().getBuilding())
.setStreet(request.getAddress().getStreet()));
}
По аналогии с buildUserResponse создадим дополнительный метод buildUserRequest. Тут появляется небольшая проблема, дело в том, что у сущности User есть дополнительная сущность address. В данном случае address не сохраниться, более того вылетит ошибка о том, что операция сохранения не распространяется на подчиненные сущности. Для того, чтоб это исправить в аннотацию @OneToOne, для поля address нужно добавить cascade = CascadeType.ALL. CascadeType.ALL - этот модификатор говорит о том, что все модификации сущности User будут распространяться и на сущность Address.
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id"))
private Address address;
Это не самый лучший подход, так как для каждой сущности должны быть свои контроллеры и репозитории. Но в контексте нашего приложения Address и User не могут существовать отдельно, поэтому мы можем воспользоваться этим приемом.Обновляем пользователя по id:
public UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("User " + userId + " is not found"));
userUpdate(user, request);
return buildUserResponse(userRepository.save(user));
}
В этом методе мы находим пользователя, по аналогии с методом findById, если такой объект нашелся, то сетим ему поля в методе userUpdate.
private void userUpdate(@NotNull User user, @NotNull CreateUserRequest request) {
ofNullable(request.getLogin()).map(user::setLogin);
ofNullable(request.getFirstName()).map(user::setFirstName);
ofNullable(request.getMiddleName()).map(user::setMiddleName);
ofNullable(request.getLastName()).map(user::setLastName);
ofNullable(request.getAge()).map(user::setAge);
CreateAddressRequest addressRequest = request.getAddress();
if (addressRequest != null) {
ofNullable(addressRequest.getBuilding()).map(user.getAddress()::setBuilding);
ofNullable(addressRequest.getStreet()).map(user.getAddress()::setStreet);
ofNullable(addressRequest.getCity()).map(user.getAddress()::setCity);
}
}
Удаляем пользователя по id:
public void delete(@NotNull Integer userId) {
userRepository.deleteById(userId);
}
userRepository.deleteById - просто удалит пользователя из БД.Теперь добавим свой обработчик ошибок. Spring умеет перехватывать ошибки и возвращать вместо них то, что мы захотим. Для этого создадим объект ExceptionResponse, который будет возвращать только сообщение из ошибки.
@Data
public class ExceptionResponse {
private final String massage;
}
А дальше добавим контроллер - обработчик ошибок.
@RestControllerAdvice
public class ExceptionController {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(EntityNotFoundException.class)
private ExceptionResponse notFound(EntityNotFoundException ex) {
return new ExceptionResponse(ex.getMessage());
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
private ExceptionResponse error(RuntimeException ex) {
return new ExceptionResponse(ex.getMessage());
}
}
Аннотация @ExceptionHandler перехватывает исключения, указанные в скобках. @ResponseStatus будет прикладывать к сообщению соответствующий код ответа. Например: HttpStatus.NOT_FOUND - 404, а HttpStatus.INTERNAL_SERVER_ERROR - 500.3. Запустим и протестируем наше приложение, удивимся что все работает и порадуемся, что провели время с пользой и узнали что-то новое.Добавим креды для подключения к БД в application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/spring_demo
spring.datasource.username=program
spring.datasource.password=test
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
Теперь поднимем базу (для этого я использую докер) и добавляем таблички в БД.
CREATE DATABASE spring_demo;
CREATE ROLE program WITH PASSWORD 'test';
GRANT ALL PRIVILEGES ON DATABASE spring_demo TO program;
ALTER ROLE program WITH LOGIN;
Ура! Наше приложение написано и полностью работает, теперь его можно тестировать.Подведем итог. Мы написали простое приложение и затронули несколько важных тем. Разработали контроллеры для разных REST методов, написали сервисную частью, включая свой обработчик ошибок. Подключили JPA и воспользовались методами интерфейса JpaRepository.Репозиторий с кодом
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Интерфейсы, Математика] Конечные автоматы в реальной жизни: где мы их используем и почему
- [Разработка веб-сайтов, Программирование] Верите ли вы в бога надежности?
- [Программирование, Анализ и проектирование систем, ООП] Многоликий принцип единственности ответственности
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS, Поисковая оптимизация] Next js. Куда, откуда и причем здесь google?
- [PHP, Программирование, Тестирование веб-сервисов] Веб-скрейпинг на PHP (перевод)
- [Программирование, Учебный процесс в IT, Логические игры] 10 игр для программистов, которые позволят улучшить свои навыки (перевод)
- [Java, Git, Серверное администрирование, GitHub, Софт] Как я познавал ci/cd, Гитхаб экшены
- [Python, Семантика, Программирование, Машинное обучение, Natural Language Processing] Перефразирование русских текстов: корпуса, модели, метрики
- [Python, Программирование, Проектирование и рефакторинг] Python: неочевидное в очевидном
- [Разработка веб-сайтов, Работа с видео, Программирование, Видеоконференцсвязь] WebRTC screen-sharing with authorization and other benefits
Теги для поиска: #_programmirovanie (Программирование), #_java, #_java, #_springboot, #_programmirovanie (программирование), #_programmirovanie (
Программирование
), #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:15
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Меня зовут Варвара и я Java Developer в компании “Цифровые привычки”. Я прошла их курс по Java-разработке и по окончании получила оффер от компании. Сейчас я хочу поделиться материалом с одного из воркшопов, который нам проводил один из лекторов - Алексей Романов, Software Architect и преподаватель Бауманки.В этой статье мы научимся создавать простые REST приложения. Напишем свое приложение с использованием SpringBoot, создадим свои контроллеры, воспользуемся JPA, подключим PostgreSQL. Мы будем разрабатывать приложение в 3 этапа:
plugins {
id 'java' id 'org.springframework.boot' version '2.5.1' id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group = 'ru.dhabits.spring_boot_example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() } implementation "org.springframework.boot:spring-boot-starter-web"
@RestController
public class controller { @Value("${spring.application.name}") private String name; @GetMapping public String getNameApplication() { return name; } } spring.application.name=spring_boot_example
@Getter
@Setter @Accessors(chain = true) @Entity @Table(name = "address") @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id")) private Address address; @Getter
@Setter @Accessors(chain = true) @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "login",nullable = false, unique = true) private String login; @Column(name = "firstName") private String firstName; @Column(name = "middleName") private String middleName; @Column(name = "lastName") private String lastName; @Column(name = "age") private Integer age; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id")) private Address address; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return login.equals(user.login); } @Override public int hashCode() { return Objects.hash(login); } @Override public String toString() { return "User{" + "id=" + id + ", login='" + login + '\'' + ", firstName='" + firstName + '\'' + ", middleName='" + middleName + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + '}'; } } @Getter
@Setter @Accessors(chain = true) @Entity @Table(name = "address") public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "street") private String street; @Column(name = "city") private String city; @Column(name = "building") private String building; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; return street.equals(address.street) && city.equals(address.city) && building.equals(address.building); } @Override public int hashCode() { return Objects.hash(street, city, building); } @Override public String toString() { return "Address{" + "id=" + id + ", street='" + street + '\'' + ", city='" + city + '\'' + ", building='" + building + '\'' + '}'; } } public interface UserRepository extends JpaRepository<User, Integer> {}
@Data
@Accessors(chain = true) public class UserResponse { private Integer id; private String login; private String firstName; private String middleName; private String lastName; private Integer age; private AddressResponse address; } @Data @Accessors(chain = true) public class AddressResponse { private String street; private String city; private String building; } @Data @Accessors(chain = true) public class CreateUserRequest { private Integer id; private String login; private String firstName; private String middleName; private String lastName; private Integer age; private CreateAddressRequest address; } @Data @Accessors(chain = true) public class CreateAddressRequest { private String street; private String city; private String building; } @RestController
@RequestMapping("/api/v1/users") @RequiredArgsConstructor public class UserController { private UserService userService; } @GetMapping(produces = APPLICATION_JSON_VALUE)
public List<UserResponse> findAll() { return userService.findAll(); } @GetMapping(value = "/{userId}", produces = APPLICATION_JSON_VALUE)
public UserResponse findById(@PathVariable Integer userId) { return userService.findById(userId); } @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public UserResponse create(@RequestBody CreateUserRequest request) { return userService.createUser(request); } @PatchMapping(value = "/{userId}", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public UserResponse update(@PathVariable Integer userId, @RequestBody CreateUserRequest request) { return userService.update(userId, request); } @ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = "/{userId}", produces = APPLICATION_JSON_VALUE) public void delete(@PathVariable Integer userId) { userService.delete(userId); } public interface UserService {
@NotNull List<UserResponse> findAll(); @NotNull UserResponse findById(@NotNull Integer userId); @NotNull UserResponse createUser(@NotNull CreateUserRequest request); @NotNull UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request); void delete(@NotNull Integer userId); } @Service
@RequiredArgsConstructor public class UserServiceImpl implements UserService { private static UserRepository userRepository; @NotNull @Override @Transactional(readOnly = true) public List<UserResponse> findAll() { return null; } @NotNull @Override @Transactional(readOnly = true) public UserResponse findById(@NotNull Integer userId) { return null; } @NotNull @Override @Transactional public UserResponse createUser(@NotNull CreateUserRequest request) { return null; } @NotNull @Override @Transactional public UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request) { return null; } @Override @Transactional public void delete(@NotNull Integer userId) { return null; } public List<UserResponse> findAll() {
return userRepository.findAll() .stream() .map(this::buildUserResponse) .collect(Collectors.toList()); } @NotNull private UserResponse buildUserResponse(@NotNull User user) { return new UserResponse() .setId(user.getId()) .setLogin(user.getLogin()) .setAge(user.getAge()) .setFirstName(user.getFirstName()) .setMiddleName(user.getMiddleName()) .setLastName(user.getLastName()) .setAddress(new AddressResponse() .setCity(user.getAddress().getCity()) .setBuilding(user.getAddress().getBuilding()) .setStreet(user.getAddress().getStreet())); } public UserResponse findById(@NotNull Integer userId) {
return userRepository.findById(userId) .map(this::buildUserResponse) .orElseThrow(() -> new EntityNotFoundException("User " + userId + " is not found")); } public UserResponse createUser(@NotNull CreateUserRequest request) {
User user = buildUserRequest(request); return buildUserResponse(userRepository.save(user)); } @NotNull private User buildUserRequest(@NotNull CreateUserRequest request) { return new User() .setLogin(request.getLogin()) .setAge(request.getAge()) .setFirstName(request.getFirstName()) .setMiddleName(request.getMiddleName()) .setLastName(request.getLastName()) .setAddress(new Address() .setCity(request.getAddress().getCity()) .setBuilding(request.getAddress().getBuilding()) .setStreet(request.getAddress().getStreet())); } @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", foreignKey = @ForeignKey(name = "fk_users_address_id")) private Address address; public UserResponse update(@NotNull Integer userId, @NotNull CreateUserRequest request) {
User user = userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException("User " + userId + " is not found")); userUpdate(user, request); return buildUserResponse(userRepository.save(user)); } private void userUpdate(@NotNull User user, @NotNull CreateUserRequest request) {
ofNullable(request.getLogin()).map(user::setLogin); ofNullable(request.getFirstName()).map(user::setFirstName); ofNullable(request.getMiddleName()).map(user::setMiddleName); ofNullable(request.getLastName()).map(user::setLastName); ofNullable(request.getAge()).map(user::setAge); CreateAddressRequest addressRequest = request.getAddress(); if (addressRequest != null) { ofNullable(addressRequest.getBuilding()).map(user.getAddress()::setBuilding); ofNullable(addressRequest.getStreet()).map(user.getAddress()::setStreet); ofNullable(addressRequest.getCity()).map(user.getAddress()::setCity); } } public void delete(@NotNull Integer userId) {
userRepository.deleteById(userId); } @Data
public class ExceptionResponse { private final String massage; } @RestControllerAdvice
public class ExceptionController { @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(EntityNotFoundException.class) private ExceptionResponse notFound(EntityNotFoundException ex) { return new ExceptionResponse(ex.getMessage()); } @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(RuntimeException.class) private ExceptionResponse error(RuntimeException ex) { return new ExceptionResponse(ex.getMessage()); } } spring.datasource.url=jdbc:postgresql://localhost:5432/spring_demo
spring.datasource.username=program spring.datasource.password=test spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.generate-ddl=true CREATE DATABASE spring_demo;
CREATE ROLE program WITH PASSWORD 'test'; GRANT ALL PRIVILEGES ON DATABASE spring_demo TO program; ALTER ROLE program WITH LOGIN; =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_java |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:15
Часовой пояс: UTC + 5