diff --git a/pom.xml b/pom.xml index ffbd96b..59f6b0b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,49 +2,40 @@ 4.0.0 + org.springframework.boot spring-boot-starter-parent 3.4.1 + org.example SpringBoot-BookShop 0.0.1-SNAPSHOT SpringBoot-BookShop SpringBoot-BookShop - - - - - - - - - - - - - + 17 checkstyle.xml + org.springframework.boot - spring-boot-starter + spring-boot-starter-web org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-data-jpa org.springframework.boot - spring-boot-starter-data-jpa + spring-boot-starter-test + test @@ -64,39 +55,58 @@ - org.springframework.boot - spring-boot-starter-web + org.mapstruct + mapstruct + 1.6.3 + + + + org.liquibase + liquibase-core + 4.23.0 - - org.springframework.boot - spring-boot-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 17 + 17 + + + org.projectlombok + lombok + 1.18.36 + + + org.mapstruct + mapstruct-processor + 1.6.3 + + + - org.apache.maven.plugins - maven-checkstyle-plugin - 3.3.0 + org.liquibase + liquibase-maven-plugin + 4.23.0 - compile + process-resources + + src/main/resources/liquibase.properties + - check + update - - ${maven.checkstyle.plugin.configLocation} - true - true - false - - diff --git a/src/main/java/org/example/SpringBootBookShopApplication.java b/src/main/java/org/example/SpringBootBookShopApplication.java index 48c1e9f..d7dc995 100644 --- a/src/main/java/org/example/SpringBootBookShopApplication.java +++ b/src/main/java/org/example/SpringBootBookShopApplication.java @@ -1,9 +1,7 @@ package org.example; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringBootBookShopApplication { @@ -11,14 +9,4 @@ public class SpringBootBookShopApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBookShopApplication.class, args); } - @Bean - public CommandLineRunner commandLineRunner() { - return new CommandLineRunner() { - @Override - public void run(String... args) throws Exception { - - } - }; - } - } diff --git a/src/main/java/org/example/config/MapperConfig.java b/src/main/java/org/example/config/MapperConfig.java new file mode 100644 index 0000000..0fd0054 --- /dev/null +++ b/src/main/java/org/example/config/MapperConfig.java @@ -0,0 +1,13 @@ +package org.example.config; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.NullValueCheckStrategy; + +@org.mapstruct.MapperConfig( + componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + implementationPackage = ".impl" +) +public class MapperConfig { +} diff --git a/src/main/java/org/example/controller/BookController.java b/src/main/java/org/example/controller/BookController.java new file mode 100644 index 0000000..2f4cf54 --- /dev/null +++ b/src/main/java/org/example/controller/BookController.java @@ -0,0 +1,43 @@ +package org.example.controller; + +import lombok.RequiredArgsConstructor; +import org.example.dto.BookDto; +import org.example.dto.CreateBookRequestDto; +import org.example.service.BookService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping(value = "/books") +public class BookController { + + private final BookService bookService; + + @GetMapping + public List getAll() { + return bookService.findAll(); + } + + @GetMapping("/{id}") + public BookDto getBookById(@PathVariable Long id) { + return bookService.findById(id); + } + + @PostMapping + public BookDto createBook(@RequestBody CreateBookRequestDto createBookRequestDto) { + return bookService.save(createBookRequestDto); + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{id}") + public void deleteBookById(@PathVariable Long id) { + bookService.deleteById(id); + } + + @PutMapping("/{id}") + public void updateBookById(@PathVariable Long id, @RequestBody BookDto bookDto) { + bookService.updateBookById(id, bookDto); + } +} diff --git a/src/main/java/org/example/dto/BookDto.java b/src/main/java/org/example/dto/BookDto.java new file mode 100644 index 0000000..3a2bf19 --- /dev/null +++ b/src/main/java/org/example/dto/BookDto.java @@ -0,0 +1,15 @@ +package org.example.dto; + +import lombok.Data; +import java.math.BigDecimal; + +@Data +public class BookDto { + private Long id; + private String title; + private String author; + private String isbn; + private BigDecimal price; + private String description; + private String coverImage; +} diff --git a/src/main/java/org/example/dto/CreateBookRequestDto.java b/src/main/java/org/example/dto/CreateBookRequestDto.java new file mode 100644 index 0000000..e3da19c --- /dev/null +++ b/src/main/java/org/example/dto/CreateBookRequestDto.java @@ -0,0 +1,14 @@ +package org.example.dto; + +import lombok.Data; +import java.math.BigDecimal; + +@Data +public class CreateBookRequestDto { + private String title; + private String author; + private String isbn; + private BigDecimal price; + private String description; + private String coverImage; +} diff --git a/src/main/java/org/example/exception/BookNotFoundException.java b/src/main/java/org/example/exception/BookNotFoundException.java new file mode 100644 index 0000000..aca5f3e --- /dev/null +++ b/src/main/java/org/example/exception/BookNotFoundException.java @@ -0,0 +1,7 @@ +package org.example.exception; + +public class BookNotFoundException extends RuntimeException { + public BookNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/org/example/exception/BookSaveException.java b/src/main/java/org/example/exception/BookSaveException.java new file mode 100644 index 0000000..07a9142 --- /dev/null +++ b/src/main/java/org/example/exception/BookSaveException.java @@ -0,0 +1,7 @@ +package org.example.exception; + +public class BookSaveException extends RuntimeException { + public BookSaveException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/example/exception/BookValidationException.java b/src/main/java/org/example/exception/BookValidationException.java new file mode 100644 index 0000000..1ac75ff --- /dev/null +++ b/src/main/java/org/example/exception/BookValidationException.java @@ -0,0 +1,7 @@ +package org.example.exception; + +public class BookValidationException extends RuntimeException { + public BookValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/org/example/exception/EntityNotFoundException.java b/src/main/java/org/example/exception/EntityNotFoundException.java new file mode 100644 index 0000000..ac0627d --- /dev/null +++ b/src/main/java/org/example/exception/EntityNotFoundException.java @@ -0,0 +1,7 @@ +package org.example.exception; + +public class EntityNotFoundException extends RuntimeException { + public EntityNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/org/example/mapper/BookMapper.java b/src/main/java/org/example/mapper/BookMapper.java new file mode 100644 index 0000000..a91858a --- /dev/null +++ b/src/main/java/org/example/mapper/BookMapper.java @@ -0,0 +1,13 @@ +package org.example.mapper; + +import org.example.config.MapperConfig; +import org.example.dto.BookDto; +import org.example.dto.CreateBookRequestDto; +import org.example.model.Book; +import org.mapstruct.Mapper; + +@Mapper(config = MapperConfig.class) +public interface BookMapper { + BookDto toDto(Book book); + Book toModel(CreateBookRequestDto createBookRequestDto); +} diff --git a/src/main/java/org/example/model/Book.java b/src/main/java/org/example/model/Book.java index dd2094f..f636aae 100644 --- a/src/main/java/org/example/model/Book.java +++ b/src/main/java/org/example/model/Book.java @@ -1,4 +1,37 @@ package org.example.model; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Column; +import lombok.Data; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import java.math.BigDecimal; + +@Entity +@Data +@SQLDelete(sql = "UPDATE books SET is_deleted = TRUE WHERE id=?") +@Where(clause = "is_deleted=false") +@Table(name = "books") public class Book { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(nullable = false) + private String title; + @Column(nullable = false) + private String author; + @Column(nullable = false, unique = true) + private String isbn; + @Column(nullable = false) + private BigDecimal price; + private String description; + private String coverImage; + @Column(nullable = false) + private boolean isDeleted = false; } diff --git a/src/main/java/org/example/repository/BookRepository.java b/src/main/java/org/example/repository/BookRepository.java index b23074b..5cb6f40 100644 --- a/src/main/java/org/example/repository/BookRepository.java +++ b/src/main/java/org/example/repository/BookRepository.java @@ -1,4 +1,22 @@ package org.example.repository; -public interface BookRepository { +import org.example.model.Book; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +import java.math.BigDecimal; + +public interface BookRepository extends JpaRepository { + + @Modifying + @Query("UPDATE Book b SET " + + " b.title = :title," + + " b.author = :author," + + " b.isbn = :isbn," + + " b.price = :price," + + " b.description = :description," + + " b.coverImage = :coverImage WHERE b.id = :id AND b.isDeleted = false") + void updateBookById(Long id, String title, String author, String isbn, + BigDecimal price, String description, String coverImage); } diff --git a/src/main/java/org/example/repository/impl/BookRepositoryImpl.java b/src/main/java/org/example/repository/impl/BookRepositoryImpl.java deleted file mode 100644 index ef51b30..0000000 --- a/src/main/java/org/example/repository/impl/BookRepositoryImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.example.repository.impl; - -public class BookRepositoryImpl { -} diff --git a/src/main/java/org/example/service/BookService.java b/src/main/java/org/example/service/BookService.java index e8a91ce..872b954 100644 --- a/src/main/java/org/example/service/BookService.java +++ b/src/main/java/org/example/service/BookService.java @@ -1,4 +1,13 @@ package org.example.service; +import org.example.dto.BookDto; +import org.example.dto.CreateBookRequestDto; +import java.util.List; + public interface BookService { + BookDto save(CreateBookRequestDto createBookRequestDto); + List findAll(); + BookDto findById(Long id); + void deleteById(Long id); + void updateBookById(Long id, BookDto bookDto); } diff --git a/src/main/java/org/example/service/impl/BookServiceImpl.java b/src/main/java/org/example/service/impl/BookServiceImpl.java index 66e8c38..6ab4785 100644 --- a/src/main/java/org/example/service/impl/BookServiceImpl.java +++ b/src/main/java/org/example/service/impl/BookServiceImpl.java @@ -1,4 +1,68 @@ package org.example.service.impl; -public class BookServiceImpl { +import lombok.RequiredArgsConstructor; +import org.example.dto.BookDto; +import org.example.dto.CreateBookRequestDto; +import org.example.exception.BookNotFoundException; +import org.example.exception.BookSaveException; +import org.example.exception.BookValidationException; +import org.example.mapper.BookMapper; +import org.example.model.Book; +import org.example.repository.BookRepository; +import org.example.service.BookService; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BookServiceImpl implements BookService { + private final BookRepository bookRepository; + private final BookMapper bookMapper; + + @Override + public BookDto save(CreateBookRequestDto createBookRequestDto) { + try { + if (createBookRequestDto.getTitle() == null) { + throw new BookValidationException("Title can't be null"); + } + + Book book = bookMapper.toModel(createBookRequestDto); + Book savedBook = bookRepository.save(book); + + return bookMapper.toDto(savedBook); + + } catch (Exception e) { + throw new BookSaveException("Can't save book", e); + } + } + + @Override + public List findAll() { + return bookRepository.findAll().stream() + .map(bookMapper::toDto) + .toList(); + } + + @Override + public BookDto findById(Long id) { + Book book = bookRepository.findById(id) + .orElseThrow(() -> new BookNotFoundException("Book with id: " + id + " not found")); + return bookMapper.toDto(book); + } + + @Override + public void deleteById(Long id) { + bookRepository.deleteById(id); + } + + @Override + public void updateBookById(Long id, BookDto bookDto) { + if (!bookRepository.existsById(id)) { + throw new BookNotFoundException("Book not found with id " + id); + } + bookRepository.updateBookById(id, bookDto.getTitle(), + bookDto.getAuthor(), bookDto.getIsbn(), + bookDto.getPrice(), bookDto.getDescription(), + bookDto.getCoverImage()); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 89c3eb7..8e85d49 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,8 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/svitlana?serverTimezone=UTC +spring.datasource.url=jdbc:mysql://localhost:3306/lombok?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=Wetuop34! spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.jpa.hibernate.ddl-auto=create-drop -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=true +spring.liquibase.enabled=false \ No newline at end of file diff --git a/src/main/resources/db.changelog/changes/01-test.yaml b/src/main/resources/db.changelog/changes/01-test.yaml new file mode 100644 index 0000000..d1ffc18 --- /dev/null +++ b/src/main/resources/db.changelog/changes/01-test.yaml @@ -0,0 +1,5 @@ +databaseChangeLog: + - changeSet: + id: 01-test.yaml + author: artur + changes: diff --git a/src/main/resources/db.changelog/db.changelog-master.yaml b/src/main/resources/db.changelog/db.changelog-master.yaml new file mode 100644 index 0000000..7ae7003 --- /dev/null +++ b/src/main/resources/db.changelog/db.changelog-master.yaml @@ -0,0 +1,3 @@ +databaseChangeLog: + - include: + file: \ No newline at end of file diff --git a/src/main/resources/liquibase.properties b/src/main/resources/liquibase.properties new file mode 100644 index 0000000..5b0fb64 --- /dev/null +++ b/src/main/resources/liquibase.properties @@ -0,0 +1,7 @@ +url=jdbc:mysql://localhost:3306/lombok?serverTimezone=UTC +username=root +password=Wetuop34! +changeLogFile=src/main/resources/db/changelog/db.changelog-master.yaml +driver=com.mysql.cj.jdbc.Driver + + diff --git a/src/test/java/org/example/SpringBootBookShopApplicationTests.java b/src/test/java/org/example/SpringBootBookShopApplicationTests.java index e491a4d..cb2c0bf 100644 --- a/src/test/java/org/example/SpringBootBookShopApplicationTests.java +++ b/src/test/java/org/example/SpringBootBookShopApplicationTests.java @@ -1,13 +1,8 @@ package org.example; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SpringBootBookShopApplicationTests { - @Test - void contextLoads() { - } - }