Initial
This commit is contained in:
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
49
build.gradle
Normal file
49
build.gradle
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '4.0.2'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'ru.ilug'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
description = 'Demo project for Spring Boot'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||||
|
|
||||||
|
implementation 'org.hibernate.orm:hibernate-core:7.2.1.Final'
|
||||||
|
implementation 'org.hibernate.orm:hibernate-hikaricp:7.2.1.Final'
|
||||||
|
implementation 'com.zaxxer:HikariCP:7.0.2'
|
||||||
|
implementation 'org.mapstruct:mapstruct:1.6.3'
|
||||||
|
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0'
|
||||||
|
|
||||||
|
runtimeOnly 'com.h2database:h2:2.4.240'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-validation-test'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'book-service'
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.ilug.book_service;
|
||||||
|
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class HibernateConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SessionFactory sessionFactory() {
|
||||||
|
return new org.hibernate.cfg.Configuration().configure().buildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.ilug.book_service;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class UpdatedBookServiceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(UpdatedBookServiceApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package ru.ilug.book_service.data.repository;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.query.Query;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.ilug.book_service.domain.BookRepository;
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BookRepositoryImpl implements BookRepository {
|
||||||
|
|
||||||
|
private final SessionFactory sessionFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Book> findPageable(int pageSize, int page) {
|
||||||
|
try (Session session = sessionFactory.openSession()) {
|
||||||
|
Query<Book> query = session.createQuery("FROM Book", Book.class);
|
||||||
|
query.setFirstResult(pageSize * page);
|
||||||
|
query.setMaxResults(pageSize);
|
||||||
|
return query.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Book findById(long id) {
|
||||||
|
try (Session session = sessionFactory.openSession()) {
|
||||||
|
return session.find(Book.class, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Book save(Book book) {
|
||||||
|
try (Session session = sessionFactory.openSession()) {
|
||||||
|
Transaction transaction = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction = session.beginTransaction();
|
||||||
|
book = session.merge(book);
|
||||||
|
transaction.commit();
|
||||||
|
|
||||||
|
return book;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (transaction != null) {
|
||||||
|
transaction.rollback();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteById(long id) {
|
||||||
|
try (Session session = sessionFactory.openSession()) {
|
||||||
|
Transaction transaction = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction = session.beginTransaction();
|
||||||
|
Book book = session.find(Book.class, id);
|
||||||
|
session.remove(book);
|
||||||
|
transaction.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (transaction != null) {
|
||||||
|
transaction.rollback();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.ilug.book_service.domain;
|
||||||
|
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface BookRepository {
|
||||||
|
|
||||||
|
List<Book> findPageable(int pageSize, int page);
|
||||||
|
|
||||||
|
Book findById(long id);
|
||||||
|
|
||||||
|
Book save(Book book);
|
||||||
|
|
||||||
|
void deleteById(long id);
|
||||||
|
|
||||||
|
}
|
||||||
51
src/main/java/ru/ilug/book_service/domain/BookService.java
Normal file
51
src/main/java/ru/ilug/book_service/domain/BookService.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package ru.ilug.book_service.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
import ru.ilug.book_service.domain.model.BookUpdateRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BookService {
|
||||||
|
|
||||||
|
private final BookRepository bookRepository;
|
||||||
|
|
||||||
|
public List<Book> findPageable(int page) {
|
||||||
|
return bookRepository.findPageable(5, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Book findById(long bookId) {
|
||||||
|
return bookRepository.findById(bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Book createNew(Book book) {
|
||||||
|
return bookRepository.save(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Book update(long bookId, BookUpdateRequest request) {
|
||||||
|
Book book = bookRepository.findById(bookId);
|
||||||
|
updateBookFromUpdateRequest(book, request);
|
||||||
|
return bookRepository.save(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteById(long bookId) {
|
||||||
|
bookRepository.deleteById(bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBookFromUpdateRequest(Book book, BookUpdateRequest request) {
|
||||||
|
setValueIfNotNull(request.title(), book::setTitle);
|
||||||
|
setValueIfNotNull(request.author(), book::setAuthor);
|
||||||
|
setValueIfNotNull(request.publicationYear(), book::setPublicationYear);
|
||||||
|
setValueIfNotNull(request.genre(), book::setGenre);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void setValueIfNotNull(T value, Consumer<T> valueSetter) {
|
||||||
|
if (value != null) {
|
||||||
|
valueSetter.accept(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/java/ru/ilug/book_service/domain/model/Book.java
Normal file
32
src/main/java/ru/ilug/book_service/domain/model/Book.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package ru.ilug.book_service.domain.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "books")
|
||||||
|
public class Book {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
private String title;
|
||||||
|
private String author;
|
||||||
|
@Column(unique = true)
|
||||||
|
private String isbn;
|
||||||
|
@Column(name = "publication_year")
|
||||||
|
private int publicationYear;
|
||||||
|
private Genre genre;
|
||||||
|
|
||||||
|
public enum Genre {
|
||||||
|
FICTION,
|
||||||
|
NON_FICTION,
|
||||||
|
SCIENCE,
|
||||||
|
HISTORY,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.ilug.book_service.domain.model;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
|
||||||
|
public record BookUpdateRequest(@Nullable @Size(min = 2, max = 100) String title,
|
||||||
|
@Nullable @Size(min = 2, max = 100) String author,
|
||||||
|
@Nullable @Range(min = 2000, max = 2026) Integer publicationYear,
|
||||||
|
@Nullable Book.Genre genre) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package ru.ilug.book_service.infrastructure.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import ru.ilug.book_service.domain.BookService;
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
import ru.ilug.book_service.domain.model.BookUpdateRequest;
|
||||||
|
import ru.ilug.book_service.infrastructure.dto.BookDto;
|
||||||
|
import ru.ilug.book_service.infrastructure.mapper.BookMapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/books")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BookController {
|
||||||
|
|
||||||
|
private final BookService bookService;
|
||||||
|
private final BookMapper bookMapper;
|
||||||
|
|
||||||
|
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public List<Book> books(@RequestParam(required = false, defaultValue = "0") int page) {
|
||||||
|
return bookService.findPageable(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<Book> bookById(@PathVariable long id) {
|
||||||
|
Book book = bookService.findById(id);
|
||||||
|
|
||||||
|
if (book == null) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(book, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public Book newBook(@Valid @RequestBody BookDto bookDto) {
|
||||||
|
Book book = bookMapper.toEntity(bookDto);
|
||||||
|
return bookService.createNew(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping(value = "/{id}")
|
||||||
|
public Book updateBook(@PathVariable long id,
|
||||||
|
@Valid @RequestBody BookUpdateRequest request) {
|
||||||
|
return bookService.update(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(value = "/{id}")
|
||||||
|
public void deleteBook(@PathVariable long id) {
|
||||||
|
bookService.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.ilug.book_service.infrastructure.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import org.hibernate.validator.constraints.Range;
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
|
||||||
|
public record BookDto(@NotNull @Size(min = 2, max = 100) String title,
|
||||||
|
@NotNull @Size(min = 2, max = 50) String author,
|
||||||
|
@NotNull @NotBlank @Pattern(regexp = "\\d{3}-\\d-\\d{5}-\\d{3}-\\d") String isbn,
|
||||||
|
@NotNull @Range(min = 2000, max = 2026) Integer publicationYear,
|
||||||
|
@NotNull Book.Genre genre) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.ilug.book_service.infrastructure.mapper;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.MappingConstants;
|
||||||
|
import org.mapstruct.ReportingPolicy;
|
||||||
|
import ru.ilug.book_service.domain.model.Book;
|
||||||
|
import ru.ilug.book_service.infrastructure.dto.BookDto;
|
||||||
|
|
||||||
|
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING)
|
||||||
|
public interface BookMapper {
|
||||||
|
Book toEntity(BookDto bookDto);
|
||||||
|
|
||||||
|
BookDto toBookDto(Book book);
|
||||||
|
}
|
||||||
1
src/main/resources/application.properties
Normal file
1
src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
spring.application.name=updated-book-service
|
||||||
29
src/main/resources/hibernate.cfg.xml
Normal file
29
src/main/resources/hibernate.cfg.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE hibernate-configuration PUBLIC
|
||||||
|
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||||
|
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
||||||
|
<hibernate-configuration>
|
||||||
|
<session-factory>
|
||||||
|
<property name="hibernate.connection.provider_class">
|
||||||
|
org.hibernate.hikaricp.internal.HikariCPConnectionProvider
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property name="hibernate.connection.driver_class">org.h2.Driver</property>
|
||||||
|
<property name="hibernate.connection.url">jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</property>
|
||||||
|
<property name="hibernate.connection.username">sa</property>
|
||||||
|
<property name="hibernate.connection.password"/>
|
||||||
|
|
||||||
|
<property name="hibernate.hikari.minimumIdle">2</property>
|
||||||
|
<property name="hibernate.hikari.maximumPoolSize">10</property>
|
||||||
|
<property name="hibernate.hikari.idleTimeout">30000</property>
|
||||||
|
<property name="hibernate.hikari.connectionTimeout">20000</property>
|
||||||
|
<property name="hibernate.hikari.maxLifetime">1800000</property>
|
||||||
|
|
||||||
|
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
|
||||||
|
<property name="hibernate.show_sql">true</property>
|
||||||
|
<property name="hibernate.format_sql">true</property>
|
||||||
|
<property name="hibernate.hbm2ddl.auto">create-drop</property>
|
||||||
|
|
||||||
|
<mapping class="ru.ilug.book_service.domain.model.Book"/>
|
||||||
|
</session-factory>
|
||||||
|
</hibernate-configuration>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.ilug.book_service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class UpdatedBookServiceApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user