diff --git a/.gitignore b/.gitignore
index e3d6056a..ffda238a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
*.iml
.idea/
target/
+spring-shell.log
!**/src/main/**/target/
!**/src/test/**/target/
diff --git a/templates/hw05-jdbc-hard-template/.gitignore b/templates/hw05-jdbc-hard-template/.gitignore
new file mode 100644
index 00000000..b96df348
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/.gitignore
@@ -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/
diff --git a/templates/hw05-jdbc-hard-template/pom.xml b/templates/hw05-jdbc-hard-template/pom.xml
new file mode 100644
index 00000000..86f97d4b
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/pom.xml
@@ -0,0 +1,100 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.2
+
+
+
+ ru.otus.hw
+ hw05-jdbc
+ 0.0.1-SNAPSHOT
+ spring-jdbc-demo
+
+
+ 17
+ 17
+ 17
+ 2.2.220
+ 3.1.3
+ 2.0
+ 3.2.2
+ 10.11.0
+
+ https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ ${spring.shell.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle-plugin.version}
+
+
+ com.puppycrawl.tools
+ checkstyle
+ ${checkstyle.version}
+
+
+
+ ${checkstyle.config.url}
+
+
+
+
+ check
+
+
+
+
+
+
+
+
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/Application.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/Application.java
new file mode 100644
index 00000000..a3f0fd27
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/Application.java
@@ -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);
+ }
+
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java
new file mode 100644
index 00000000..c832e8ea
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java
@@ -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()));
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/BookCommands.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/BookCommands.java
new file mode 100644
index 00000000..0716dd6a
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/BookCommands.java
@@ -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 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 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);
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/GenreCommands.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/GenreCommands.java
new file mode 100644
index 00000000..2b5705b8
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/commands/GenreCommands.java
@@ -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()));
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java
new file mode 100644
index 00000000..7d4d6c9c
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java
@@ -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());
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/BookConverter.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/BookConverter.java
new file mode 100644
index 00000000..c174791d
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/BookConverter.java
@@ -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);
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/GenreConverter.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/GenreConverter.java
new file mode 100644
index 00000000..92d500ca
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/converters/GenreConverter.java
@@ -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());
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java
new file mode 100644
index 00000000..eb27d491
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.hw.exceptions;
+
+public class EntityNotFoundException extends RuntimeException {
+ public EntityNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Author.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Author.java
new file mode 100644
index 00000000..7db6dbbe
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Author.java
@@ -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;
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Book.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Book.java
new file mode 100644
index 00000000..c71d15e1
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Book.java
@@ -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 genres;
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Genre.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Genre.java
new file mode 100644
index 00000000..e59ddab3
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/models/Genre.java
@@ -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;
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java
new file mode 100644
index 00000000..743de402
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java
@@ -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 findAll();
+
+ Optional findById(long id);
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java
new file mode 100644
index 00000000..b12c0c2c
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java
@@ -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 findAll() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public Optional findById(long id) {
+ return Optional.empty();
+ }
+
+ private static class AuthorRowMapper implements RowMapper {
+
+ @Override
+ public Author mapRow(ResultSet rs, int i) throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepository.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepository.java
new file mode 100644
index 00000000..ab507c33
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepository.java
@@ -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 findById(long id);
+
+ List findAll();
+
+ Book save(Book book);
+
+ void deleteById(long id);
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java
new file mode 100644
index 00000000..bb99e66e
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java
@@ -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 findById(long id) {
+ return Optional.empty();
+ }
+
+ @Override
+ public List 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 getAllBooksWithoutGenres() {
+ return new ArrayList<>();
+ }
+
+ private List getAllGenreRelations() {
+ return new ArrayList<>();
+ }
+
+ private void mergeBooksInfo(List booksWithoutGenres, List genres,
+ List 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 {
+
+ @Override
+ public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("ClassCanBeRecord")
+ @RequiredArgsConstructor
+ private static class BookResultSetExtractor implements ResultSetExtractor> {
+
+ @Override
+ public List extractData(ResultSet rs) throws SQLException, DataAccessException {
+ return new ArrayList<>();
+ }
+ }
+
+ private record BookGenreRelation(long bookId, long genreId) {
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java
new file mode 100644
index 00000000..02e87b10
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java
@@ -0,0 +1,11 @@
+package ru.otus.hw.repositories;
+
+import ru.otus.hw.models.Genre;
+
+import java.util.List;
+
+public interface GenreRepository {
+ List findAll();
+
+ List findAllByIds(List ids);
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java
new file mode 100644
index 00000000..e87daa58
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java
@@ -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 findAll() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List findAllByIds(List ids) {
+ return new ArrayList<>();
+ }
+
+ private static class GnreRowMapper implements RowMapper {
+
+ @Override
+ public Genre mapRow(ResultSet rs, int i) throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorService.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorService.java
new file mode 100644
index 00000000..87e92f8c
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorService.java
@@ -0,0 +1,9 @@
+package ru.otus.hw.services;
+
+import ru.otus.hw.models.Author;
+
+import java.util.List;
+
+public interface AuthorService {
+ List findAll();
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java
new file mode 100644
index 00000000..36740ca1
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java
@@ -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 findAll() {
+ return authorRepository.findAll();
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookService.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookService.java
new file mode 100644
index 00000000..8b37a4b0
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookService.java
@@ -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 findById(long id);
+
+ List findAll();
+
+ Book insert(String title, long authorId, List genresIds);
+
+ Book update(long id, String title, long authorId, List genresIds);
+
+ void deleteById(long id);
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java
new file mode 100644
index 00000000..a6495210
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java
@@ -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 findById(long id) {
+ return bookRepository.findById(id);
+ }
+
+ @Override
+ public List findAll() {
+ return bookRepository.findAll();
+ }
+
+ @Override
+ public Book insert(String title, long authorId, List genresIds) {
+ return save(0, title, authorId, genresIds);
+ }
+
+ @Override
+ public Book update(long id, String title, long authorId, List 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 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);
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreService.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreService.java
new file mode 100644
index 00000000..3fd4170b
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreService.java
@@ -0,0 +1,9 @@
+package ru.otus.hw.services;
+
+import ru.otus.hw.models.Genre;
+
+import java.util.List;
+
+public interface GenreService {
+ List findAll();
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java
new file mode 100644
index 00000000..8d1f620a
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java
@@ -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 findAll() {
+ return genreRepository.findAll();
+ }
+}
diff --git a/templates/hw05-jdbc-hard-template/src/main/resources/application.yml b/templates/hw05-jdbc-hard-template/src/main/resources/application.yml
new file mode 100644
index 00000000..c037758d
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:maindb
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
\ No newline at end of file
diff --git a/templates/hw05-jdbc-hard-template/src/main/resources/data.sql b/templates/hw05-jdbc-hard-template/src/main/resources/data.sql
new file mode 100644
index 00000000..3dd3a166
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/resources/data.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);
diff --git a/templates/hw05-jdbc-hard-template/src/main/resources/schema.sql b/templates/hw05-jdbc-hard-template/src/main/resources/schema.sql
new file mode 100644
index 00000000..b085e18a
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/main/resources/schema.sql
@@ -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)
+);
\ No newline at end of file
diff --git a/templates/hw05-jdbc-hard-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java b/templates/hw05-jdbc-hard-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java
new file mode 100644
index 00000000..a226d7ab
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java
@@ -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 dbAuthors;
+
+ private List dbGenres;
+
+ private List 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 getDbAuthors() {
+ return IntStream.range(1, 4).boxed()
+ .map(id -> new Author(id, "Author_" + id))
+ .toList();
+ }
+
+ private static List getDbGenres() {
+ return IntStream.range(1, 7).boxed()
+ .map(id -> new Genre(id, "Genre_" + id))
+ .toList();
+ }
+
+ private static List getDbBooks(List dbAuthors, List 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 getDbBooks() {
+ var dbAuthors = getDbAuthors();
+ var dbGenres = getDbGenres();
+ return getDbBooks(dbAuthors, dbGenres);
+ }
+}
\ No newline at end of file
diff --git a/templates/hw05-jdbc-hard-template/src/test/resources/application.yml b/templates/hw05-jdbc-hard-template/src/test/resources/application.yml
new file mode 100644
index 00000000..45c16b3d
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/test/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
\ No newline at end of file
diff --git a/templates/hw05-jdbc-hard-template/src/test/resources/data.sql b/templates/hw05-jdbc-hard-template/src/test/resources/data.sql
new file mode 100644
index 00000000..3dd3a166
--- /dev/null
+++ b/templates/hw05-jdbc-hard-template/src/test/resources/data.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);
diff --git a/templates/hw05-jdbc-simple-template/.gitignore b/templates/hw05-jdbc-simple-template/.gitignore
new file mode 100644
index 00000000..b96df348
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/.gitignore
@@ -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/
diff --git a/templates/hw05-jdbc-simple-template/pom.xml b/templates/hw05-jdbc-simple-template/pom.xml
new file mode 100644
index 00000000..86f97d4b
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/pom.xml
@@ -0,0 +1,100 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.2
+
+
+
+ ru.otus.hw
+ hw05-jdbc
+ 0.0.1-SNAPSHOT
+ spring-jdbc-demo
+
+
+ 17
+ 17
+ 17
+ 2.2.220
+ 3.1.3
+ 2.0
+ 3.2.2
+ 10.11.0
+
+ https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ ${spring.shell.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+ ${h2.version}
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle-plugin.version}
+
+
+ com.puppycrawl.tools
+ checkstyle
+ ${checkstyle.version}
+
+
+
+ ${checkstyle.config.url}
+
+
+
+
+ check
+
+
+
+
+
+
+
+
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/Application.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/Application.java
new file mode 100644
index 00000000..a3f0fd27
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/Application.java
@@ -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);
+ }
+
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java
new file mode 100644
index 00000000..c832e8ea
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/AuthorCommands.java
@@ -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()));
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/BookCommands.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/BookCommands.java
new file mode 100644
index 00000000..2a6124f0
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/BookCommands.java
@@ -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);
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/GenreCommands.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/GenreCommands.java
new file mode 100644
index 00000000..2b5705b8
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/commands/GenreCommands.java
@@ -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()));
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java
new file mode 100644
index 00000000..7d4d6c9c
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/AuthorConverter.java
@@ -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());
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/BookConverter.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/BookConverter.java
new file mode 100644
index 00000000..adcd894d
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/BookConverter.java
@@ -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()));
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/GenreConverter.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/GenreConverter.java
new file mode 100644
index 00000000..92d500ca
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/converters/GenreConverter.java
@@ -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());
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java
new file mode 100644
index 00000000..eb27d491
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/exceptions/EntityNotFoundException.java
@@ -0,0 +1,7 @@
+package ru.otus.hw.exceptions;
+
+public class EntityNotFoundException extends RuntimeException {
+ public EntityNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Author.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Author.java
new file mode 100644
index 00000000..7db6dbbe
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Author.java
@@ -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;
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Book.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Book.java
new file mode 100644
index 00000000..e5625435
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Book.java
@@ -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;
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Genre.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Genre.java
new file mode 100644
index 00000000..e59ddab3
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/models/Genre.java
@@ -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;
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java
new file mode 100644
index 00000000..743de402
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepository.java
@@ -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 findAll();
+
+ Optional findById(long id);
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java
new file mode 100644
index 00000000..b12c0c2c
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/AuthorRepositoryJdbc.java
@@ -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 findAll() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public Optional findById(long id) {
+ return Optional.empty();
+ }
+
+ private static class AuthorRowMapper implements RowMapper {
+
+ @Override
+ public Author mapRow(ResultSet rs, int i) throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepository.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepository.java
new file mode 100644
index 00000000..ab507c33
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepository.java
@@ -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 findById(long id);
+
+ List findAll();
+
+ Book save(Book book);
+
+ void deleteById(long id);
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java
new file mode 100644
index 00000000..fb3c70c3
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/BookRepositoryJdbc.java
@@ -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 findById(long id) {
+ return Optional.empty();
+ }
+
+ @Override
+ public List 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 {
+
+ @Override
+ public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java
new file mode 100644
index 00000000..e2bb6df1
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepository.java
@@ -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 findAll();
+
+ Optional findById(long id);
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java
new file mode 100644
index 00000000..075e7f82
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/repositories/GenreRepositoryJdbc.java
@@ -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 findAll() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public Optional findById(long id) {
+ return Optional.empty();
+ }
+
+ private static class GnreRowMapper implements RowMapper {
+
+ @Override
+ public Genre mapRow(ResultSet rs, int i) throws SQLException {
+ return null;
+ }
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorService.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorService.java
new file mode 100644
index 00000000..87e92f8c
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorService.java
@@ -0,0 +1,9 @@
+package ru.otus.hw.services;
+
+import ru.otus.hw.models.Author;
+
+import java.util.List;
+
+public interface AuthorService {
+ List findAll();
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java
new file mode 100644
index 00000000..36740ca1
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/AuthorServiceImpl.java
@@ -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 findAll() {
+ return authorRepository.findAll();
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookService.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookService.java
new file mode 100644
index 00000000..f9230908
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookService.java
@@ -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 findById(long id);
+
+ List findAll();
+
+ Book insert(String title, long authorId, long genreId);
+
+ Book update(long id, String title, long authorId, long genreId);
+
+ void deleteById(long id);
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java
new file mode 100644
index 00000000..d4627c95
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/BookServiceImpl.java
@@ -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 findById(long id) {
+ return bookRepository.findById(id);
+ }
+
+ @Override
+ public List 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);
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreService.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreService.java
new file mode 100644
index 00000000..3fd4170b
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreService.java
@@ -0,0 +1,9 @@
+package ru.otus.hw.services;
+
+import ru.otus.hw.models.Genre;
+
+import java.util.List;
+
+public interface GenreService {
+ List findAll();
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java
new file mode 100644
index 00000000..8d1f620a
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/java/ru/otus/hw/services/GenreServiceImpl.java
@@ -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 findAll() {
+ return genreRepository.findAll();
+ }
+}
diff --git a/templates/hw05-jdbc-simple-template/src/main/resources/application.yml b/templates/hw05-jdbc-simple-template/src/main/resources/application.yml
new file mode 100644
index 00000000..c037758d
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:maindb
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
\ No newline at end of file
diff --git a/templates/hw05-jdbc-simple-template/src/main/resources/data.sql b/templates/hw05-jdbc-simple-template/src/main/resources/data.sql
new file mode 100644
index 00000000..13b890e6
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/resources/data.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);
diff --git a/templates/hw05-jdbc-simple-template/src/main/resources/schema.sql b/templates/hw05-jdbc-simple-template/src/main/resources/schema.sql
new file mode 100644
index 00000000..d590a44b
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/main/resources/schema.sql
@@ -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)
+);
\ No newline at end of file
diff --git a/templates/hw05-jdbc-simple-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java b/templates/hw05-jdbc-simple-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java
new file mode 100644
index 00000000..10e1b640
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/test/java/ru/otus/hw/repositories/BookRepositoryJdbcTest.java
@@ -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 dbAuthors;
+
+ private List dbGenres;
+
+ private List 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 getDbAuthors() {
+ return IntStream.range(1, 4).boxed()
+ .map(id -> new Author(id, "Author_" + id))
+ .toList();
+ }
+
+ private static List getDbGenres() {
+ return IntStream.range(1, 4).boxed()
+ .map(id -> new Genre(id, "Genre_" + id))
+ .toList();
+ }
+
+ private static List getDbBooks(List dbAuthors, List 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 getDbBooks() {
+ var dbAuthors = getDbAuthors();
+ var dbGenres = getDbGenres();
+ return getDbBooks(dbAuthors, dbGenres);
+ }
+}
\ No newline at end of file
diff --git a/templates/hw05-jdbc-simple-template/src/test/resources/application.yml b/templates/hw05-jdbc-simple-template/src/test/resources/application.yml
new file mode 100644
index 00000000..45c16b3d
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/test/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
\ No newline at end of file
diff --git a/templates/hw05-jdbc-simple-template/src/test/resources/data.sql b/templates/hw05-jdbc-simple-template/src/test/resources/data.sql
new file mode 100644
index 00000000..13b890e6
--- /dev/null
+++ b/templates/hw05-jdbc-simple-template/src/test/resources/data.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);