mirror of
https://github.com/OtusTeam/Spring.git
synced 2026-05-23 10:50:34 +00:00
jbdc hw templates added
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
*.iml
|
||||
.idea/
|
||||
target/
|
||||
spring-shell.log
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
spring-shell.log
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.otus.hw</groupId>
|
||||
<artifactId>hw05-jdbc</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-jdbc-demo</name>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<h2.version>2.2.220</h2.version>
|
||||
<spring.shell.version>3.1.3</spring.shell.version>
|
||||
<snakeyaml.version>2.0</snakeyaml.version>
|
||||
<checkstyle-plugin.version>3.2.2</checkstyle-plugin.version>
|
||||
<checkstyle.version>10.11.0</checkstyle.version>
|
||||
<checkstyle.config.url>
|
||||
https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
|
||||
</checkstyle.config.url>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-starter</artifactId>
|
||||
<version>${spring.shell.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${checkstyle-plugin.version}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>${checkstyle.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<configLocation>${checkstyle.config.url}</configLocation>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package ru.otus.hw;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.AuthorConverter;
|
||||
import ru.otus.hw.services.AuthorService;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class AuthorCommands {
|
||||
|
||||
private final AuthorService authorService;
|
||||
|
||||
private final AuthorConverter authorConverter;
|
||||
|
||||
@ShellMethod(value = "Find all authors", key = "aa")
|
||||
public String findAllAuthors() {
|
||||
return authorService.findAll().stream()
|
||||
.map(authorConverter::authorToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.BookConverter;
|
||||
import ru.otus.hw.services.BookService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class BookCommands {
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
private final BookConverter bookConverter;
|
||||
|
||||
@ShellMethod(value = "Find all books", key = "ab")
|
||||
public String findAllBooks() {
|
||||
return bookService.findAll().stream()
|
||||
.map(bookConverter::bookToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Find book by id", key = "bbid")
|
||||
public String findBookById(long id) {
|
||||
return bookService.findById(id)
|
||||
.map(bookConverter::bookToString)
|
||||
.orElse("Book with id %d not found".formatted(id));
|
||||
}
|
||||
|
||||
//bins aaaaaaaaaaaaa 1 1,6//bins aaaaaaaaaaaaa 1 1,6
|
||||
@ShellMethod(value = "Insert book", key = "bins")
|
||||
public String insertBook(String title, long authorId, List<Long> genresIds) {
|
||||
var savedBook = bookService.insert(title, authorId, genresIds);
|
||||
return bookConverter.bookToString(savedBook);
|
||||
}
|
||||
|
||||
//bupd 4 dfasdfasdfasd 3 2,5
|
||||
@ShellMethod(value = "Update book", key = "bupd")
|
||||
public String updateBook(long id, String title, long authorId, List<Long> genresIds) {
|
||||
var savedBook = bookService.update(id, title, authorId, genresIds);
|
||||
return bookConverter.bookToString(savedBook);
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Delete book by id", key = "bdel")
|
||||
public void updateBook(long id) {
|
||||
bookService.deleteById(id);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.GenreConverter;
|
||||
import ru.otus.hw.services.GenreService;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class GenreCommands {
|
||||
|
||||
private final GenreService genreService;
|
||||
|
||||
private final GenreConverter genreConverter;
|
||||
|
||||
@ShellMethod(value = "Find all genres", key = "ag")
|
||||
public String findAllGenres() {
|
||||
return genreService.findAll().stream()
|
||||
.map(genreConverter::genreToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
@Component
|
||||
public class AuthorConverter {
|
||||
public String authorToString(Author author) {
|
||||
return "Id: %d, FullName: %s".formatted(author.getId(), author.getFullName());
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class BookConverter {
|
||||
private final AuthorConverter authorConverter;
|
||||
|
||||
private final GenreConverter genreConverter;
|
||||
|
||||
public String bookToString(Book book) {
|
||||
var genresString = book.getGenres().stream()
|
||||
.map(genreConverter::genreToString)
|
||||
.map("{%s}"::formatted)
|
||||
.collect(Collectors.joining(", "));
|
||||
return "Id: %d, title: %s, author: {%s}, genres: [%s]".formatted(
|
||||
book.getId(),
|
||||
book.getTitle(),
|
||||
authorConverter.authorToString(book.getAuthor()),
|
||||
genresString);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
@Component
|
||||
public class GenreConverter {
|
||||
public String genreToString(Genre genre) {
|
||||
return "Id: %d, Name: %s".formatted(genre.getId(), genre.getName());
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ru.otus.hw.exceptions;
|
||||
|
||||
public class EntityNotFoundException extends RuntimeException {
|
||||
public EntityNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Author {
|
||||
private long id;
|
||||
|
||||
private String fullName;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Book {
|
||||
private long id;
|
||||
|
||||
private String title;
|
||||
|
||||
private Author author;
|
||||
|
||||
private List<Genre> genres;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Genre {
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AuthorRepository {
|
||||
List<Author> findAll();
|
||||
|
||||
Optional<Author> findById(long id);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class AuthorRepositoryJdbc implements AuthorRepository {
|
||||
|
||||
@Override
|
||||
public List<Author> findAll() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Author> findById(long id) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static class AuthorRowMapper implements RowMapper<Author> {
|
||||
|
||||
@Override
|
||||
public Author mapRow(ResultSet rs, int i) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BookRepository {
|
||||
Optional<Book> findById(long id);
|
||||
|
||||
List<Book> findAll();
|
||||
|
||||
Book save(Book book);
|
||||
|
||||
void deleteById(long id);
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Book;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class BookRepositoryJdbc implements BookRepository {
|
||||
|
||||
private final GenreRepository genreRepository;
|
||||
|
||||
@Override
|
||||
public Optional<Book> findById(long id) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> findAll() {
|
||||
var genres = genreRepository.findAll();
|
||||
var relations = getAllGenreRelations();
|
||||
var books = getAllBooksWithoutGenres();
|
||||
mergeBooksInfo(books, genres, relations);
|
||||
return books;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book save(Book book) {
|
||||
if (book.getId() == 0) {
|
||||
return insert(book);
|
||||
}
|
||||
return update(book);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(long id) {
|
||||
//...
|
||||
}
|
||||
|
||||
private List<Book> getAllBooksWithoutGenres() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<BookGenreRelation> getAllGenreRelations() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private void mergeBooksInfo(List<Book> booksWithoutGenres, List<Genre> genres,
|
||||
List<BookGenreRelation> relations) {
|
||||
// Добавить книгам (booksWithoutGenres) жанры (genres) в соответствии со связями (relations)
|
||||
}
|
||||
|
||||
private Book insert(Book book) {
|
||||
var keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
//...
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
book.setId(keyHolder.getKeyAs(Long.class));
|
||||
batchInsertGenresRelationsFor(book);
|
||||
return book;
|
||||
}
|
||||
|
||||
private Book update(Book book) {
|
||||
//...
|
||||
|
||||
removeGenresRelationsFor(book);
|
||||
batchInsertGenresRelationsFor(book);
|
||||
|
||||
return book;
|
||||
}
|
||||
|
||||
private void batchInsertGenresRelationsFor(Book book) {
|
||||
// batchUpdate
|
||||
}
|
||||
|
||||
private void removeGenresRelationsFor(Book book) {
|
||||
//...
|
||||
}
|
||||
|
||||
private static class BookRowMapper implements RowMapper<Book> {
|
||||
|
||||
@Override
|
||||
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
@RequiredArgsConstructor
|
||||
private static class BookResultSetExtractor implements ResultSetExtractor<List<Book>> {
|
||||
|
||||
@Override
|
||||
public List<Book> extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private record BookGenreRelation(long bookId, long genreId) {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GenreRepository {
|
||||
List<Genre> findAll();
|
||||
|
||||
List<Genre> findAllByIds(List<Long> ids);
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class GenreRepositoryJdbc implements GenreRepository {
|
||||
|
||||
@Override
|
||||
public List<Genre> findAll() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Genre> findAllByIds(List<Long> ids) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private static class GnreRowMapper implements RowMapper<Genre> {
|
||||
|
||||
@Override
|
||||
public Genre mapRow(ResultSet rs, int i) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AuthorService {
|
||||
List<Author> findAll();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.models.Author;
|
||||
import ru.otus.hw.repositories.AuthorRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AuthorServiceImpl implements AuthorService {
|
||||
private final AuthorRepository authorRepository;
|
||||
|
||||
@Override
|
||||
public List<Author> findAll() {
|
||||
return authorRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BookService {
|
||||
Optional<Book> findById(long id);
|
||||
|
||||
List<Book> findAll();
|
||||
|
||||
Book insert(String title, long authorId, List<Long> genresIds);
|
||||
|
||||
Book update(long id, String title, long authorId, List<Long> genresIds);
|
||||
|
||||
void deleteById(long id);
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.exceptions.EntityNotFoundException;
|
||||
import ru.otus.hw.models.Book;
|
||||
import ru.otus.hw.repositories.AuthorRepository;
|
||||
import ru.otus.hw.repositories.BookRepository;
|
||||
import ru.otus.hw.repositories.GenreRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.util.CollectionUtils.isEmpty;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class BookServiceImpl implements BookService {
|
||||
private final AuthorRepository authorRepository;
|
||||
|
||||
private final GenreRepository genreRepository;
|
||||
|
||||
private final BookRepository bookRepository;
|
||||
|
||||
@Override
|
||||
public Optional<Book> findById(long id) {
|
||||
return bookRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> findAll() {
|
||||
return bookRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book insert(String title, long authorId, List<Long> genresIds) {
|
||||
return save(0, title, authorId, genresIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book update(long id, String title, long authorId, List<Long> genresIds) {
|
||||
return save(id, title, authorId, genresIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(long id) {
|
||||
bookRepository.deleteById(id);
|
||||
}
|
||||
|
||||
private Book save(long id, String title, long authorId, List<Long> genresIds) {
|
||||
var author = authorRepository.findById(authorId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Author with id %d not found".formatted(authorId)));
|
||||
var genres = genreRepository.findAllByIds(genresIds);
|
||||
if (isEmpty(genres)) {
|
||||
throw new EntityNotFoundException("Genres with ids %s not found".formatted(genresIds));
|
||||
}
|
||||
var book = new Book(id, title, author, genres);
|
||||
return bookRepository.save(book);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GenreService {
|
||||
List<Genre> findAll();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.models.Genre;
|
||||
import ru.otus.hw.repositories.GenreRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class GenreServiceImpl implements GenreService {
|
||||
private final GenreRepository genreRepository;
|
||||
|
||||
@Override
|
||||
public List<Genre> findAll() {
|
||||
return genreRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:maindb
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
data-locations: data.sql
|
||||
schema-locations: schema.sql
|
||||
@@ -0,0 +1,14 @@
|
||||
insert into authors(full_name)
|
||||
values ('Author_1'), ('Author_2'), ('Author_3');
|
||||
|
||||
insert into genres(name)
|
||||
values ('Genre_1'), ('Genre_2'), ('Genre_3'),
|
||||
('Genre_4'), ('Genre_5'), ('Genre_6');
|
||||
|
||||
insert into books(title, author_id)
|
||||
values ('BookTitle_1', 1), ('BookTitle_2', 2), ('BookTitle_3', 3);
|
||||
|
||||
insert into books_genres(book_id, genre_id)
|
||||
values (1, 1), (1, 2),
|
||||
(2, 3), (2, 4),
|
||||
(3, 5), (3, 6);
|
||||
@@ -0,0 +1,24 @@
|
||||
create table authors (
|
||||
id bigserial,
|
||||
full_name varchar(255),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table genres (
|
||||
id bigserial,
|
||||
name varchar(255),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table books (
|
||||
id bigserial,
|
||||
title varchar(255),
|
||||
author_id bigint references authors (id) on delete cascade,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table books_genres (
|
||||
book_id bigint references books(id) on delete cascade,
|
||||
genre_id bigint references genres(id) on delete cascade,
|
||||
primary key (book_id, genre_id)
|
||||
);
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import ru.otus.hw.models.Author;
|
||||
import ru.otus.hw.models.Book;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Репозиторий на основе Jdbc для работы с книгами ")
|
||||
@JdbcTest
|
||||
@Import({BookRepositoryJdbc.class, GenreRepositoryJdbc.class})
|
||||
class BookRepositoryJdbcTest {
|
||||
|
||||
@Autowired
|
||||
private BookRepositoryJdbc repositoryJdbc;
|
||||
|
||||
private List<Author> dbAuthors;
|
||||
|
||||
private List<Genre> dbGenres;
|
||||
|
||||
private List<Book> dbBooks;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dbAuthors = getDbAuthors();
|
||||
dbGenres = getDbGenres();
|
||||
dbBooks = getDbBooks(dbAuthors, dbGenres);
|
||||
}
|
||||
|
||||
@DisplayName("должен загружать книгу по id")
|
||||
@ParameterizedTest
|
||||
@MethodSource("getDbBooks")
|
||||
void shouldReturnCorrectBookById(Book expectedBook) {
|
||||
var actualBook = repositoryJdbc.findById(expectedBook.getId());
|
||||
assertThat(actualBook).isPresent()
|
||||
.get()
|
||||
.isEqualTo(expectedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен загружать список всех книг")
|
||||
@Test
|
||||
void shouldReturnCorrectBooksList() {
|
||||
var actualBooks = repositoryJdbc.findAll();
|
||||
var expectedBooks = dbBooks;
|
||||
|
||||
assertThat(actualBooks).containsExactlyElementsOf(expectedBooks);
|
||||
actualBooks.forEach(System.out::println);
|
||||
}
|
||||
|
||||
@DisplayName("должен сохранять новую книгу")
|
||||
@Test
|
||||
void shouldSaveNewBook() {
|
||||
var expectedBook = new Book(0, "BookTitle_10500", dbAuthors.get(0),
|
||||
List.of(dbGenres.get(0), dbGenres.get(2)));
|
||||
var returnedBook = repositoryJdbc.save(expectedBook);
|
||||
assertThat(returnedBook).isNotNull()
|
||||
.matches(book -> book.getId() > 0)
|
||||
.usingRecursiveComparison().ignoringExpectedNullFields().isEqualTo(expectedBook);
|
||||
|
||||
assertThat(repositoryJdbc.findById(returnedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isEqualTo(returnedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен сохранять измененную книгу")
|
||||
@Test
|
||||
void shouldSaveUpdatedBook() {
|
||||
var expectedBook = new Book(1L, "BookTitle_10500", dbAuthors.get(2),
|
||||
List.of(dbGenres.get(4), dbGenres.get(5)));
|
||||
|
||||
assertThat(repositoryJdbc.findById(expectedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isNotEqualTo(expectedBook);
|
||||
|
||||
var returnedBook = repositoryJdbc.save(expectedBook);
|
||||
assertThat(returnedBook).isNotNull()
|
||||
.matches(book -> book.getId() > 0)
|
||||
.usingRecursiveComparison().ignoringExpectedNullFields().isEqualTo(expectedBook);
|
||||
|
||||
assertThat(repositoryJdbc.findById(returnedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isEqualTo(returnedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен удалять книгу по id ")
|
||||
@Test
|
||||
void shouldDeleteBook() {
|
||||
assertThat(repositoryJdbc.findById(1L)).isPresent();
|
||||
repositoryJdbc.deleteById(1L);
|
||||
assertThat(repositoryJdbc.findById(1L)).isEmpty();
|
||||
}
|
||||
|
||||
private static List<Author> getDbAuthors() {
|
||||
return IntStream.range(1, 4).boxed()
|
||||
.map(id -> new Author(id, "Author_" + id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Genre> getDbGenres() {
|
||||
return IntStream.range(1, 7).boxed()
|
||||
.map(id -> new Genre(id, "Genre_" + id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Book> getDbBooks(List<Author> dbAuthors, List<Genre> dbGenres) {
|
||||
return IntStream.range(1, 4).boxed()
|
||||
.map(id -> new Book(id,
|
||||
"BookTitle_" + id,
|
||||
dbAuthors.get(id - 1),
|
||||
dbGenres.subList((id - 1) * 2, (id - 1) * 2 + 2)
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Book> getDbBooks() {
|
||||
var dbAuthors = getDbAuthors();
|
||||
var dbGenres = getDbGenres();
|
||||
return getDbBooks(dbAuthors, dbGenres);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
data-locations: data.sql
|
||||
schema-locations: schema.sql
|
||||
@@ -0,0 +1,14 @@
|
||||
insert into authors(full_name)
|
||||
values ('Author_1'), ('Author_2'), ('Author_3');
|
||||
|
||||
insert into genres(name)
|
||||
values ('Genre_1'), ('Genre_2'), ('Genre_3'),
|
||||
('Genre_4'), ('Genre_5'), ('Genre_6');
|
||||
|
||||
insert into books(title, author_id)
|
||||
values ('BookTitle_1', 1), ('BookTitle_2', 2), ('BookTitle_3', 3);
|
||||
|
||||
insert into books_genres(book_id, genre_id)
|
||||
values (1, 1), (1, 2),
|
||||
(2, 3), (2, 4),
|
||||
(3, 5), (3, 6);
|
||||
@@ -0,0 +1,30 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
spring-shell.log
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.otus.hw</groupId>
|
||||
<artifactId>hw05-jdbc</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-jdbc-demo</name>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<h2.version>2.2.220</h2.version>
|
||||
<spring.shell.version>3.1.3</spring.shell.version>
|
||||
<snakeyaml.version>2.0</snakeyaml.version>
|
||||
<checkstyle-plugin.version>3.2.2</checkstyle-plugin.version>
|
||||
<checkstyle.version>10.11.0</checkstyle.version>
|
||||
<checkstyle.config.url>
|
||||
https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
|
||||
</checkstyle.config.url>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-starter</artifactId>
|
||||
<version>${spring.shell.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${checkstyle-plugin.version}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>${checkstyle.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<configLocation>${checkstyle.config.url}</configLocation>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package ru.otus.hw;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.AuthorConverter;
|
||||
import ru.otus.hw.services.AuthorService;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class AuthorCommands {
|
||||
|
||||
private final AuthorService authorService;
|
||||
|
||||
private final AuthorConverter authorConverter;
|
||||
|
||||
@ShellMethod(value = "Find all authors", key = "aa")
|
||||
public String findAllAuthors() {
|
||||
return authorService.findAll().stream()
|
||||
.map(authorConverter::authorToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.BookConverter;
|
||||
import ru.otus.hw.services.BookService;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class BookCommands {
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
private final BookConverter bookConverter;
|
||||
|
||||
@ShellMethod(value = "Find all books", key = "ab")
|
||||
public String findAllBooks() {
|
||||
return bookService.findAll().stream()
|
||||
.map(bookConverter::bookToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Find book by id", key = "bbid")
|
||||
public String findBookById(long id) {
|
||||
return bookService.findById(id)
|
||||
.map(bookConverter::bookToString)
|
||||
.orElse("Book with id %d not found".formatted(id));
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Insert book", key = "bins")
|
||||
public String insertBook(String title, long authorId, long genreId) {
|
||||
var savedBook = bookService.insert(title, authorId, genreId);
|
||||
return bookConverter.bookToString(savedBook);
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Update book", key = "bupd")
|
||||
public String updateBook(long id, String title, long authorId, long genreId) {
|
||||
var savedBook = bookService.update(id, title, authorId, genreId);
|
||||
return bookConverter.bookToString(savedBook);
|
||||
}
|
||||
|
||||
@ShellMethod(value = "Delete book by id", key = "bdel")
|
||||
public void updateBook(long id) {
|
||||
bookService.deleteById(id);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package ru.otus.hw.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import ru.otus.hw.converters.GenreConverter;
|
||||
import ru.otus.hw.services.GenreService;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ShellComponent
|
||||
public class GenreCommands {
|
||||
|
||||
private final GenreService genreService;
|
||||
|
||||
private final GenreConverter genreConverter;
|
||||
|
||||
@ShellMethod(value = "Find all genres", key = "ag")
|
||||
public String findAllGenres() {
|
||||
return genreService.findAll().stream()
|
||||
.map(genreConverter::genreToString)
|
||||
.collect(Collectors.joining("," + System.lineSeparator()));
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
@Component
|
||||
public class AuthorConverter {
|
||||
public String authorToString(Author author) {
|
||||
return "Id: %d, FullName: %s".formatted(author.getId(), author.getFullName());
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class BookConverter {
|
||||
private final AuthorConverter authorConverter;
|
||||
|
||||
private final GenreConverter genreConverter;
|
||||
|
||||
public String bookToString(Book book) {
|
||||
return "Id: %d, title: %s, author: {%s}, genres: [%s]".formatted(
|
||||
book.getId(),
|
||||
book.getTitle(),
|
||||
authorConverter.authorToString(book.getAuthor()),
|
||||
genreConverter.genreToString(book.getGenre()));
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.hw.converters;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
@Component
|
||||
public class GenreConverter {
|
||||
public String genreToString(Genre genre) {
|
||||
return "Id: %d, Name: %s".formatted(genre.getId(), genre.getName());
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ru.otus.hw.exceptions;
|
||||
|
||||
public class EntityNotFoundException extends RuntimeException {
|
||||
public EntityNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Author {
|
||||
private long id;
|
||||
|
||||
private String fullName;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Book {
|
||||
private long id;
|
||||
|
||||
private String title;
|
||||
|
||||
private Author author;
|
||||
|
||||
private Genre genre;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.otus.hw.models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Genre {
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AuthorRepository {
|
||||
List<Author> findAll();
|
||||
|
||||
Optional<Author> findById(long id);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class AuthorRepositoryJdbc implements AuthorRepository {
|
||||
|
||||
@Override
|
||||
public List<Author> findAll() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Author> findById(long id) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static class AuthorRowMapper implements RowMapper<Author> {
|
||||
|
||||
@Override
|
||||
public Author mapRow(ResultSet rs, int i) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BookRepository {
|
||||
Optional<Book> findById(long id);
|
||||
|
||||
List<Book> findAll();
|
||||
|
||||
Book save(Book book);
|
||||
|
||||
void deleteById(long id);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class BookRepositoryJdbc implements BookRepository {
|
||||
|
||||
@Override
|
||||
public Optional<Book> findById(long id) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> findAll() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book save(Book book) {
|
||||
if (book.getId() == 0) {
|
||||
return insert(book);
|
||||
}
|
||||
return update(book);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(long id) {
|
||||
//...
|
||||
}
|
||||
|
||||
private Book insert(Book book) {
|
||||
var keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
//...
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
book.setId(keyHolder.getKeyAs(Long.class));
|
||||
return book;
|
||||
}
|
||||
|
||||
private Book update(Book book) {
|
||||
//...
|
||||
return book;
|
||||
}
|
||||
|
||||
private static class BookRowMapper implements RowMapper<Book> {
|
||||
|
||||
@Override
|
||||
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface GenreRepository {
|
||||
List<Genre> findAll();
|
||||
|
||||
Optional<Genre> findById(long id);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class GenreRepositoryJdbc implements GenreRepository {
|
||||
|
||||
@Override
|
||||
public List<Genre> findAll() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Genre> findById(long id) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static class GnreRowMapper implements RowMapper<Genre> {
|
||||
|
||||
@Override
|
||||
public Genre mapRow(ResultSet rs, int i) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Author;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AuthorService {
|
||||
List<Author> findAll();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.models.Author;
|
||||
import ru.otus.hw.repositories.AuthorRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class AuthorServiceImpl implements AuthorService {
|
||||
private final AuthorRepository authorRepository;
|
||||
|
||||
@Override
|
||||
public List<Author> findAll() {
|
||||
return authorRepository.findAll();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Book;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BookService {
|
||||
Optional<Book> findById(long id);
|
||||
|
||||
List<Book> findAll();
|
||||
|
||||
Book insert(String title, long authorId, long genreId);
|
||||
|
||||
Book update(long id, String title, long authorId, long genreId);
|
||||
|
||||
void deleteById(long id);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.exceptions.EntityNotFoundException;
|
||||
import ru.otus.hw.models.Book;
|
||||
import ru.otus.hw.repositories.AuthorRepository;
|
||||
import ru.otus.hw.repositories.BookRepository;
|
||||
import ru.otus.hw.repositories.GenreRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class BookServiceImpl implements BookService {
|
||||
private final AuthorRepository authorRepository;
|
||||
|
||||
private final GenreRepository genreRepository;
|
||||
|
||||
private final BookRepository bookRepository;
|
||||
|
||||
@Override
|
||||
public Optional<Book> findById(long id) {
|
||||
return bookRepository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> findAll() {
|
||||
return bookRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book insert(String title, long authorId, long genreId) {
|
||||
return save(0, title, authorId, genreId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book update(long id, String title, long authorId, long genreId) {
|
||||
return save(id, title, authorId, genreId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(long id) {
|
||||
bookRepository.deleteById(id);
|
||||
}
|
||||
|
||||
private Book save(long id, String title, long authorId, long genreId) {
|
||||
var author = authorRepository.findById(authorId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Author with id %d not found".formatted(authorId)));
|
||||
var genre = genreRepository.findById(genreId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Genre with id %d not found".formatted(genreId)));
|
||||
var book = new Book(id, title, author, genre);
|
||||
return bookRepository.save(book);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GenreService {
|
||||
List<Genre> findAll();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package ru.otus.hw.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.otus.hw.models.Genre;
|
||||
import ru.otus.hw.repositories.GenreRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class GenreServiceImpl implements GenreService {
|
||||
private final GenreRepository genreRepository;
|
||||
|
||||
@Override
|
||||
public List<Genre> findAll() {
|
||||
return genreRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:maindb
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
data-locations: data.sql
|
||||
schema-locations: schema.sql
|
||||
@@ -0,0 +1,8 @@
|
||||
insert into authors(full_name)
|
||||
values ('Author_1'), ('Author_2'), ('Author_3');
|
||||
|
||||
insert into genres(name)
|
||||
values ('Genre_1'), ('Genre_2'), ('Genre_3');
|
||||
|
||||
insert into books(title, author_id, genre_id)
|
||||
values ('BookTitle_1', 1, 1), ('BookTitle_2', 2, 2), ('BookTitle_3', 3, 3);
|
||||
@@ -0,0 +1,19 @@
|
||||
create table authors (
|
||||
id bigserial,
|
||||
full_name varchar(255),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table genres (
|
||||
id bigserial,
|
||||
name varchar(255),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table books (
|
||||
id bigserial,
|
||||
title varchar(255),
|
||||
author_id bigint references authors (id) on delete cascade,
|
||||
genre_id bigint references genres(id) on delete cascade,
|
||||
primary key (id)
|
||||
);
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package ru.otus.hw.repositories;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import ru.otus.hw.models.Author;
|
||||
import ru.otus.hw.models.Book;
|
||||
import ru.otus.hw.models.Genre;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Репозиторий на основе Jdbc для работы с книгами ")
|
||||
@JdbcTest
|
||||
@Import({BookRepositoryJdbc.class, GenreRepositoryJdbc.class})
|
||||
class BookRepositoryJdbcTest {
|
||||
|
||||
@Autowired
|
||||
private BookRepositoryJdbc repositoryJdbc;
|
||||
|
||||
private List<Author> dbAuthors;
|
||||
|
||||
private List<Genre> dbGenres;
|
||||
|
||||
private List<Book> dbBooks;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dbAuthors = getDbAuthors();
|
||||
dbGenres = getDbGenres();
|
||||
dbBooks = getDbBooks(dbAuthors, dbGenres);
|
||||
}
|
||||
|
||||
@DisplayName("должен загружать книгу по id")
|
||||
@ParameterizedTest
|
||||
@MethodSource("getDbBooks")
|
||||
void shouldReturnCorrectBookById(Book expectedBook) {
|
||||
var actualBook = repositoryJdbc.findById(expectedBook.getId());
|
||||
assertThat(actualBook).isPresent()
|
||||
.get()
|
||||
.isEqualTo(expectedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен загружать список всех книг")
|
||||
@Test
|
||||
void shouldReturnCorrectBooksList() {
|
||||
var actualBooks = repositoryJdbc.findAll();
|
||||
var expectedBooks = dbBooks;
|
||||
|
||||
assertThat(actualBooks).containsExactlyElementsOf(expectedBooks);
|
||||
actualBooks.forEach(System.out::println);
|
||||
}
|
||||
|
||||
@DisplayName("должен сохранять новую книгу")
|
||||
@Test
|
||||
void shouldSaveNewBook() {
|
||||
var expectedBook = new Book(0, "BookTitle_10500", dbAuthors.get(0), dbGenres.get(0));
|
||||
var returnedBook = repositoryJdbc.save(expectedBook);
|
||||
assertThat(returnedBook).isNotNull()
|
||||
.matches(book -> book.getId() > 0)
|
||||
.usingRecursiveComparison().ignoringExpectedNullFields().isEqualTo(expectedBook);
|
||||
|
||||
assertThat(repositoryJdbc.findById(returnedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isEqualTo(returnedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен сохранять измененную книгу")
|
||||
@Test
|
||||
void shouldSaveUpdatedBook() {
|
||||
var expectedBook = new Book(1L, "BookTitle_10500", dbAuthors.get(2), dbGenres.get(2));
|
||||
|
||||
assertThat(repositoryJdbc.findById(expectedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isNotEqualTo(expectedBook);
|
||||
|
||||
var returnedBook = repositoryJdbc.save(expectedBook);
|
||||
assertThat(returnedBook).isNotNull()
|
||||
.matches(book -> book.getId() > 0)
|
||||
.usingRecursiveComparison().ignoringExpectedNullFields().isEqualTo(expectedBook);
|
||||
|
||||
assertThat(repositoryJdbc.findById(returnedBook.getId()))
|
||||
.isPresent()
|
||||
.get()
|
||||
.isEqualTo(returnedBook);
|
||||
}
|
||||
|
||||
@DisplayName("должен удалять книгу по id ")
|
||||
@Test
|
||||
void shouldDeleteBook() {
|
||||
assertThat(repositoryJdbc.findById(1L)).isPresent();
|
||||
repositoryJdbc.deleteById(1L);
|
||||
assertThat(repositoryJdbc.findById(1L)).isEmpty();
|
||||
}
|
||||
|
||||
private static List<Author> getDbAuthors() {
|
||||
return IntStream.range(1, 4).boxed()
|
||||
.map(id -> new Author(id, "Author_" + id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Genre> getDbGenres() {
|
||||
return IntStream.range(1, 4).boxed()
|
||||
.map(id -> new Genre(id, "Genre_" + id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Book> getDbBooks(List<Author> dbAuthors, List<Genre> dbGenres) {
|
||||
return IntStream.range(1, 4).boxed()
|
||||
.map(id -> new Book(id, "BookTitle_" + id, dbAuthors.get(id - 1), dbGenres.get(id - 1)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static List<Book> getDbBooks() {
|
||||
var dbAuthors = getDbAuthors();
|
||||
var dbGenres = getDbGenres();
|
||||
return getDbBooks(dbAuthors, dbGenres);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
data-locations: data.sql
|
||||
schema-locations: schema.sql
|
||||
@@ -0,0 +1,8 @@
|
||||
insert into authors(full_name)
|
||||
values ('Author_1'), ('Author_2'), ('Author_3');
|
||||
|
||||
insert into genres(name)
|
||||
values ('Genre_1'), ('Genre_2'), ('Genre_3');
|
||||
|
||||
insert into books(title, author_id, genre_id)
|
||||
values ('BookTitle_1', 1, 1), ('BookTitle_2', 2, 2), ('BookTitle_3', 3, 3);
|
||||
Reference in New Issue
Block a user