diff --git a/README.md b/README.md
index e2cfdc7..0c11d7a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,12 @@
# OtusSpringHW-Library
-Домашние задания по Spring для Otus с базами данных.
+
+Домашнее задание #7:
+- Переделать билиотеку на Spring Data JPA.
+- Реализовать весь функционал работы с БД в приложении книг с использованием spring-data-jpa репозиториев.
+
+### 19-06-22 Доработки
+- Подключил liquibase.
+
+### 19-07-07 Доработки
+- Использование Mockito для тестирования сервисов.
+- Использование @NamedEntityGraph.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f2cd927
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,220 @@
+
+
+ 4.0.0
+
+ home-work
+
+ ru.otus.mkulikov
+ home-work
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+
+
+ UTF-8
+
+
+
+
+ net.sf.opencsv
+ opencsv
+ 2.3
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+ org.assertj
+ assertj-core
+ 3.12.2
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.3.2
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.3.2
+ test
+
+
+
+ org.junit.platform
+ junit-platform-launcher
+ 1.4.1
+ test
+
+
+
+
+ org.mockito
+ mockito-core
+ 2.21.0
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.0
+ test
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ 2.0.0.RELEASE
+
+
+
+
+ com.h2database
+ h2
+ 1.4.199
+
+
+
+ org.liquibase
+ liquibase-core
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.data
+ spring-data-jpa
+ 2.0.8.RELEASE
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 11
+ 11
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+ ${encoding}
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.0
+
+
+ false
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.liquibase
+ liquibase-maven-plugin
+ 3.6.3
+
+ true
+ false
+ classpath:/db/changelog/db.changelog-master.yaml
+
+
+
+
+
+
+
+
+
+
+
+ org.yaml
+ snakeyaml
+ 1.23
+
+
+
+
+
+
+
+ src/main/resources
+
+ **/*.csv
+ **/*.properties
+ **/*.txt
+ **/*.yml
+ **/*.sql
+ **/*.yaml
+
+ false
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/ru/otus/mkulikov/Application.java b/src/main/java/ru/otus/mkulikov/Application.java
new file mode 100644
index 0000000..9c2f534
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/Application.java
@@ -0,0 +1,20 @@
+package ru.otus.mkulikov;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 2019-03-14
+ * Time: 15:27
+ */
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(Application.class);
+ //Console.main(args);
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/AuthorDao.java b/src/main/java/ru/otus/mkulikov/app/dao/AuthorDao.java
new file mode 100644
index 0000000..b4b1933
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/AuthorDao.java
@@ -0,0 +1,17 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Author;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:06
+ */
+
+@Repository
+public interface AuthorDao extends JpaRepository {
+
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/BookDao.java b/src/main/java/ru/otus/mkulikov/app/dao/BookDao.java
new file mode 100644
index 0000000..7cbcd93
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/BookDao.java
@@ -0,0 +1,17 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Book;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 13:28
+ */
+
+@Repository
+public interface BookDao extends JpaRepository, BookDaoCustom {
+
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustom.java b/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustom.java
new file mode 100644
index 0000000..fcb0cb1
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustom.java
@@ -0,0 +1,22 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Book;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 24.06.2019
+ * Time: 11:48
+ */
+
+@Repository
+public interface BookDaoCustom {
+
+ Optional getById(long id);
+
+ List getAllObjects();
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustomImpl.java b/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustomImpl.java
new file mode 100644
index 0000000..7a4aec5
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/BookDaoCustomImpl.java
@@ -0,0 +1,49 @@
+package ru.otus.mkulikov.app.dao;
+
+import lombok.RequiredArgsConstructor;
+import ru.otus.mkulikov.app.model.Book;
+
+import javax.persistence.EntityGraph;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 13:28
+ */
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@RequiredArgsConstructor
+public class BookDaoCustomImpl implements BookDaoCustom {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public Optional getById(long id) {
+ EntityGraph entityGraph = em.getEntityGraph("BookGraph");
+ List books = em.createQuery("select b from Book b where b.id = :id ", Book.class)
+ .setHint("javax.persistence.fetchgraph", entityGraph)
+ .setParameter("id", id)
+ .getResultList();
+
+ em.clear();
+ return (books != null && !books.isEmpty()) ? Optional.of(books.get(0)) : Optional.empty();
+ }
+
+ @Override
+ public List getAllObjects() {
+ EntityGraph entityGraph = em.getEntityGraph("BookGraph");
+ List books = em.createQuery("select b from Book b order by b.id ", Book.class)
+ .setHint("javax.persistence.fetchgraph", entityGraph)
+ .getResultList();
+
+ em.clear();
+ return books;
+ }
+}
+
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/CommentDao.java b/src/main/java/ru/otus/mkulikov/app/dao/CommentDao.java
new file mode 100644
index 0000000..47269b3
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/CommentDao.java
@@ -0,0 +1,17 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Comment;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 20.06.2019
+ * Time: 23:48
+ */
+
+@Repository
+public interface CommentDao extends JpaRepository, CommentDaoCustom {
+
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustom.java b/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustom.java
new file mode 100644
index 0000000..3dcabef
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustom.java
@@ -0,0 +1,22 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Comment;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 20.06.2019
+ * Time: 23:48
+ */
+
+@Repository
+public interface CommentDaoCustom {
+
+ Optional getById(long id);
+
+ List getByBookId(long bookId);
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustomImpl.java b/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustomImpl.java
new file mode 100644
index 0000000..080bc7d
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/CommentDaoCustomImpl.java
@@ -0,0 +1,49 @@
+package ru.otus.mkulikov.app.dao;
+
+import lombok.RequiredArgsConstructor;
+import ru.otus.mkulikov.app.model.Comment;
+
+import javax.persistence.EntityGraph;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:06
+ */
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@RequiredArgsConstructor
+public class CommentDaoCustomImpl implements CommentDaoCustom {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public Optional getById(long id) {
+ EntityGraph entityGraph = em.getEntityGraph("CommentGraph");
+ List list = em.createQuery("select c from Comment c where c.id = :id ", Comment.class)
+ .setHint("javax.persistence.fetchgraph", entityGraph)
+ .setParameter("id", id)
+ .getResultList();
+
+ em.clear();
+ return (list != null && !list.isEmpty()) ? Optional.of(list.get(0)) : Optional.empty();
+ }
+
+ @Override
+ public List getByBookId(long bookId) {
+ EntityGraph entityGraph = em.getEntityGraph("CommentGraph");
+ List list = em.createQuery("select c from Comment c where c.book.id = :bookId ", Comment.class)
+ .setHint("javax.persistence.fetchgraph", entityGraph)
+ .setParameter("bookId", bookId)
+ .getResultList();
+
+ em.clear();
+ return list;
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/dao/GenreDao.java b/src/main/java/ru/otus/mkulikov/app/dao/GenreDao.java
new file mode 100644
index 0000000..2a95644
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/dao/GenreDao.java
@@ -0,0 +1,17 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.otus.mkulikov.app.model.Genre;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:07
+ */
+
+@Repository
+public interface GenreDao extends JpaRepository {
+
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/model/Author.java b/src/main/java/ru/otus/mkulikov/app/model/Author.java
new file mode 100644
index 0000000..edca570
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/model/Author.java
@@ -0,0 +1,57 @@
+package ru.otus.mkulikov.app.model;
+
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:02
+ */
+
+@Data
+@Entity
+@Table(name = "AUTHOR")
+public class Author {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_author")
+ @SequenceGenerator(name = "sq_author", sequenceName = "sq_author", allocationSize = 1)
+ @Column(name = "ID")
+ private long id;
+ @Column(name = "SURNAME")
+ private String surname;
+ @Column(name = "FIRST_NAME")
+ private String firstName;
+ @Column(name = "SECOND_NAME")
+ private String secondName;
+
+ public Author() {
+ }
+
+ public Author(long id, String surname, String firstName, String secondName) {
+ this.id = id;
+ this.surname = surname;
+ this.firstName = firstName;
+ this.secondName = secondName;
+ }
+
+ public Author(String surname, String firstName, String secondName) {
+ this.id = 0L;
+ this.surname = surname;
+ this.firstName = firstName;
+ this.secondName = secondName;
+ }
+
+ @Override
+ public String toString() {
+ return "Author{" +
+ "id=" + id +
+ ", surname='" + surname + '\'' +
+ ", firstName='" + firstName + '\'' +
+ ", secondName='" + secondName + '\'' +
+ "}\n";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/model/Book.java b/src/main/java/ru/otus/mkulikov/app/model/Book.java
new file mode 100644
index 0000000..0c9cb7c
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/model/Book.java
@@ -0,0 +1,113 @@
+package ru.otus.mkulikov.app.model;
+
+import lombok.Data;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 13:28
+ */
+
+@Data
+@Entity
+@Table(name = "BOOK")
+
+@NamedEntityGraph(
+ name = "BookGraph",
+ attributeNodes = {
+ @NamedAttributeNode(value = "id"),
+ @NamedAttributeNode(value = "addRecordDate"),
+ @NamedAttributeNode(value = "caption"),
+ @NamedAttributeNode(value = "description"),
+ @NamedAttributeNode(value = "author", subgraph = "authorGraph"),
+ @NamedAttributeNode(value = "genre", subgraph = "genreGraph")
+ },
+ subgraphs = {
+ @NamedSubgraph(
+ name = "authorGraph",
+ attributeNodes = {
+ @NamedAttributeNode(value = "id"),
+ @NamedAttributeNode(value = "surname"),
+ @NamedAttributeNode(value = "firstName"),
+ @NamedAttributeNode(value = "secondName")
+ }
+ ),
+ @NamedSubgraph(
+ name = "genreGraph",
+ attributeNodes = {
+ @NamedAttributeNode(value = "id"),
+ @NamedAttributeNode(value = "name")
+ }
+ )
+ }
+)
+
+public class Book {
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_book")
+ @SequenceGenerator(name = "sq_book", sequenceName = "sq_book", allocationSize = 1)
+ @Column(name = "ID")
+ private long id;
+ @Column(name = "ADD_RECORD_DATE")
+ private Date addRecordDate;
+ @Column(name = "CAPTION")
+ private String caption;
+ @Column(name = "DESCRIPTION")
+ private String description;
+
+ @Fetch(FetchMode.JOIN)
+ @ManyToOne(optional=false, fetch = FetchType.EAGER)
+ private Author author;
+
+ @Fetch(FetchMode.JOIN)
+ @ManyToOne(optional=false, fetch = FetchType.EAGER)
+ private Genre genre;
+
+ public Book() {
+ }
+
+ public Book(long id, Date addRecordDate, String caption, Author author, Genre genre, String description) {
+ this.id = id;
+ this.addRecordDate = addRecordDate;
+ this.author = author;
+ this.genre = genre;
+ this.caption = caption;
+ this.description = description;
+ }
+
+ public Book(long id, String caption, Author author, Genre genre, String description) {
+ this.id = id;
+ this.addRecordDate = new Date();
+ this.caption = caption;
+ this.author = author;
+ this.genre = genre;
+ this.description = description;
+ }
+
+ public Book(String caption, Author author, Genre genre, String description) {
+ this.id = 0L;
+ this.addRecordDate = new Date();
+ this.caption = caption;
+ this.author = author;
+ this.genre = genre;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return "Book{" +
+ "id=" + id +
+ ", addRecordDate=" + addRecordDate +
+ ", caption='" + caption + '\'' +
+ ", description=" + description +
+ ", author=" + author +
+ ", genre=" + genre +
+ "}\n";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/model/Comment.java b/src/main/java/ru/otus/mkulikov/app/model/Comment.java
new file mode 100644
index 0000000..fe03461
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/model/Comment.java
@@ -0,0 +1,79 @@
+package ru.otus.mkulikov.app.model;
+
+import lombok.Data;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 20.06.2019
+ * Time: 23:38
+ */
+
+@Data
+@Entity
+@Table(name = "COMMENT")
+
+@NamedEntityGraph(
+ name = "CommentGraph",
+ attributeNodes = {
+ @NamedAttributeNode(value = "id"),
+ @NamedAttributeNode(value = "addRecordDate"),
+ @NamedAttributeNode(value = "userName"),
+ @NamedAttributeNode(value = "text"),
+ @NamedAttributeNode(value = "book")
+ }
+)
+public class Comment {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_comment")
+ @SequenceGenerator(name = "sq_comment", sequenceName = "sq_comment", allocationSize = 1)
+ @Column(name = "ID")
+ private long id;
+ @Column(name = "ADD_RECORD_DATE")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date addRecordDate;
+ @Column(name = "USER_NAME")
+ private String userName;
+ @Column(name = "TEXT")
+ private String text;
+
+ @Fetch(FetchMode.JOIN)
+ @ManyToOne(optional=false, fetch = FetchType.EAGER)
+ private Book book;
+
+ public Comment() {
+ }
+
+ public Comment(long id, Book book, Date addRecordDate, String userName, String text) {
+ this.id = id;
+ this.book = book;
+ this.addRecordDate = addRecordDate;
+ this.userName = userName;
+ this.text = text;
+ }
+
+ public Comment(Book book, Date addRecordDate, String userName, String text) {
+ this.id = 0L;
+ this.book = book;
+ this.addRecordDate = addRecordDate;
+ this.userName = userName;
+ this.text = text;
+ }
+
+ @Override
+ public String toString() {
+ return "Comment{" +
+ "id=" + id +
+ ", addRecordDate=" + addRecordDate +
+ ", userName=" + userName +
+ ", text='" + text + '\'' +
+ ", book='" + book + '\'' +
+ "}\n";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/model/Genre.java b/src/main/java/ru/otus/mkulikov/app/model/Genre.java
new file mode 100644
index 0000000..dc37847
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/model/Genre.java
@@ -0,0 +1,47 @@
+package ru.otus.mkulikov.app.model;
+
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:04
+ */
+
+@Data
+@Entity
+@Table(name = "GENRE")
+public class Genre {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_genre")
+ @SequenceGenerator(name = "sq_genre", sequenceName = "sq_genre", allocationSize = 1)
+ @Column(name = "ID")
+ private long id;
+ @Column(name = "NAME")
+ private String name;
+
+ public Genre() {
+ }
+
+ public Genre(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Genre(String name) {
+ this.id = 0L;
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "Genre{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ "}\n";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/otus/mkulikov/app/service/AuthorManageService.java b/src/main/java/ru/otus/mkulikov/app/service/AuthorManageService.java
new file mode 100644
index 0000000..de2eaaf
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/AuthorManageService.java
@@ -0,0 +1,25 @@
+package ru.otus.mkulikov.app.service;
+
+import ru.otus.mkulikov.app.model.Author;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 14:59
+ */
+
+public interface AuthorManageService {
+
+ T getAuthorById(long id);
+
+ List getAuthors();
+
+ long addAuthor(String surname, String firstName, String secondName);
+
+ int updateAuthor(long id, String surname, String firstName, String secondName);
+
+ int deleteAuthor(long id);
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/AuthorManageServiceImpl.java b/src/main/java/ru/otus/mkulikov/app/service/AuthorManageServiceImpl.java
new file mode 100644
index 0000000..2cb2494
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/AuthorManageServiceImpl.java
@@ -0,0 +1,68 @@
+package ru.otus.mkulikov.app.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.otus.mkulikov.app.dao.AuthorDao;
+import ru.otus.mkulikov.app.model.Author;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:53
+ */
+
+@Service
+@RequiredArgsConstructor
+public class AuthorManageServiceImpl implements AuthorManageService {
+
+ private final AuthorDao authorDao;
+
+ @Override
+ public Author getAuthorById(long id) {
+ Optional author = authorDao.findById(id);
+ return author.orElse(null);
+ }
+
+ @Override
+ public List getAuthors() {
+ Iterable authors = authorDao.findAll();
+ List list = StreamSupport
+ .stream(authors.spliterator(), false)
+ .collect(Collectors.toList());
+
+ return list;
+ }
+
+ @Override
+ public long addAuthor(String surname, String firstName, String secondName) {
+ Author author = authorDao.save(new Author(surname, firstName, secondName));
+ return author.getId();
+ }
+
+ @Override
+ public int updateAuthor(long id, String surname, String firstName, String secondName) {
+ Author author = authorDao.findById(id).orElse(null);
+ author.setSurname(surname);
+ author.setFirstName(firstName);
+ author.setSecondName(secondName);
+
+ authorDao.save(author);
+ return 1;
+ }
+
+ @Override
+ public int deleteAuthor(long id) {
+ authorDao.deleteById(id);
+ // при удалении записи, которая используется в другой таблице не выдает никакой ошибки,
+ // ошибка выдается только при вызове следующей команды
+ // поэтому вызываю count
+ authorDao.count();
+ return 1;
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/BookManageSevice.java b/src/main/java/ru/otus/mkulikov/app/service/BookManageSevice.java
new file mode 100644
index 0000000..4dab801
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/BookManageSevice.java
@@ -0,0 +1,25 @@
+package ru.otus.mkulikov.app.service;
+
+import ru.otus.mkulikov.app.model.Book;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 27.05.2019
+ * Time: 13:42
+ */
+
+public interface BookManageSevice {
+
+ T getBookById(long id);
+
+ List getBooks();
+
+ long addBook(String caption, long authorId, long genreId, String description);
+
+ int updateBook(long id, String caption, long authorId, long genreId, String description);
+
+ int deleteBook(long id);
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/BookManageSeviceImpl.java b/src/main/java/ru/otus/mkulikov/app/service/BookManageSeviceImpl.java
new file mode 100644
index 0000000..4445926
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/BookManageSeviceImpl.java
@@ -0,0 +1,70 @@
+package ru.otus.mkulikov.app.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.otus.mkulikov.app.dao.AuthorDao;
+import ru.otus.mkulikov.app.dao.BookDao;
+import ru.otus.mkulikov.app.dao.GenreDao;
+import ru.otus.mkulikov.app.model.Author;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 27.05.2019
+ * Time: 13:42
+ */
+
+@Service
+@RequiredArgsConstructor
+public class BookManageSeviceImpl implements BookManageSevice {
+
+ private final BookDao bookDao;
+ private final AuthorDao authorDao;
+ private final GenreDao genreDao;
+
+ @Override
+ public Book getBookById(long id) {
+ return bookDao.getById(id).orElse(null);
+ }
+
+ @Override
+ public List getBooks() {
+ return bookDao.getAllObjects();
+ }
+
+ @Override
+ public long addBook(String caption, long authorId, long genreId, String description) {
+ Author author = authorDao.findById(authorId).orElse(null);
+ Genre genre = genreDao.findById(genreId).orElse(null);
+
+ Book book = bookDao.save(new Book(caption, author, genre, description));
+ return book.getId();
+ }
+
+ @Override
+ public int updateBook(long id, String caption, long authorId, long genreId, String description) {
+ Author author = authorDao.findById(authorId).orElse(null);
+ Genre genre = genreDao.findById(genreId).orElse(null);
+
+ Book book = bookDao.getById(id).orElse(null);
+ book.setCaption(caption);
+ book.setAuthor(author);
+ book.setGenre(genre);
+ book.setGenre(genre);
+ book.setDescription(description);
+
+ bookDao.save(book);
+ return 1;
+ }
+
+ @Override
+ public int deleteBook(long id) {
+ bookDao.deleteById(id);
+ bookDao.count();
+ return 1;
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/CommentManageService.java b/src/main/java/ru/otus/mkulikov/app/service/CommentManageService.java
new file mode 100644
index 0000000..ebdf3e1
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/CommentManageService.java
@@ -0,0 +1,26 @@
+package ru.otus.mkulikov.app.service;
+
+import ru.otus.mkulikov.app.model.Comment;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:50
+ */
+public interface CommentManageService {
+
+ T getCommentById(long id);
+
+ List getComments();
+
+ List getCommentsByBookId(long bookId);
+
+ long addComment(long bookId, String userName, String text);
+
+ int updateComment(long id, String userName, String text);
+
+ int deleteComment(long id);
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/CommentManageServiceImpl.java b/src/main/java/ru/otus/mkulikov/app/service/CommentManageServiceImpl.java
new file mode 100644
index 0000000..7607fe6
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/CommentManageServiceImpl.java
@@ -0,0 +1,69 @@
+package ru.otus.mkulikov.app.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.otus.mkulikov.app.dao.BookDao;
+import ru.otus.mkulikov.app.dao.CommentDao;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Comment;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:52
+ */
+
+@Service
+@RequiredArgsConstructor
+public class CommentManageServiceImpl implements CommentManageService {
+
+ private final CommentDao commentDao;
+ private final BookDao bookDao;
+
+ @Override
+ public Comment getCommentById(long id) {
+ Optional comment = commentDao.getById(id);
+ return comment.orElse(null);
+ }
+
+ @Override
+ public List getComments() {
+ return commentDao.findAll();
+ }
+
+ @Override
+ public List getCommentsByBookId(long bookId) {
+ return commentDao.getByBookId(bookId);
+ }
+
+ @Override
+ public long addComment(long bookId, String userName, String text) {
+ Book book = bookDao.getById(bookId).orElse(null);
+
+ Comment comment = commentDao.save(new Comment(book, new Date(), userName, text));
+ return comment.getId();
+ }
+
+ @Override
+ public int updateComment(long id, String userName, String text) {
+ Comment comment = commentDao.getById(id).orElse(null);
+ comment.setAddRecordDate(new Date());
+ comment.setUserName(userName);
+ comment.setText(text);
+
+ commentDao.save(comment);
+ return 1;
+ }
+
+ @Override
+ public int deleteComment(long id) {
+ commentDao.deleteById(id);
+ commentDao.count();
+ return 1;
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/GenreManageService.java b/src/main/java/ru/otus/mkulikov/app/service/GenreManageService.java
new file mode 100644
index 0000000..f6b83ee
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/GenreManageService.java
@@ -0,0 +1,24 @@
+package ru.otus.mkulikov.app.service;
+
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:50
+ */
+public interface GenreManageService {
+
+ T getGenreById(long id);
+
+ List getGenres();
+
+ long addGenre(String name);
+
+ int updateGenre(long id, String name);
+
+ int deleteGenre(long id);
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/service/GenreManageServiceImpl.java b/src/main/java/ru/otus/mkulikov/app/service/GenreManageServiceImpl.java
new file mode 100644
index 0000000..e5c71d8
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/service/GenreManageServiceImpl.java
@@ -0,0 +1,63 @@
+package ru.otus.mkulikov.app.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.otus.mkulikov.app.dao.GenreDao;
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:52
+ */
+
+@Service
+@RequiredArgsConstructor
+public class GenreManageServiceImpl implements GenreManageService {
+
+ private final GenreDao genreDao;
+
+ @Override
+ public Genre getGenreById(long id) {
+ Optional genre = genreDao.findById(id);
+ return genre.orElse(null);
+ }
+
+ @Override
+ public List getGenres() {
+ Iterable authors = genreDao.findAll();
+ List list = StreamSupport
+ .stream(authors.spliterator(), false)
+ .collect(Collectors.toList());
+
+ return list;
+ }
+
+ @Override
+ public long addGenre(String name) {
+ Genre genre = genreDao.save(new Genre(name));
+ return genre.getId();
+ }
+
+ @Override
+ public int updateGenre(long id, String name) {
+ Genre genre = genreDao.findById(id).orElse(null);
+ genre.setName(name);
+
+ genreDao.save(genre);
+ return 1;
+ }
+
+ @Override
+ public int deleteGenre(long id) {
+ genreDao.deleteById(id);
+ genreDao.count();
+ return 1;
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/app/utils/DateUtil.java b/src/main/java/ru/otus/mkulikov/app/utils/DateUtil.java
new file mode 100644
index 0000000..68c0a16
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/app/utils/DateUtil.java
@@ -0,0 +1,44 @@
+package ru.otus.mkulikov.app.utils;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 05.06.2019
+ * Time: 11:33
+ */
+
+public class DateUtil {
+
+ private final static String DATE_FORMAT = "yyyy-MM-dd";
+
+ private final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ public static String dateToString(Date date) {
+ DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+ return date != null ? dateFormat.format(date) : null;
+ }
+
+ public static String dateTimeToString(Date date) {
+ DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
+ return date != null ? dateFormat.format(date) : null;
+ }
+
+ public static java.util.Date stringToDateTime(String stringDate) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
+ LocalDateTime dateTime = LocalDateTime.parse(stringDate, formatter);
+ return dateTime != null ? convertToDateViaInstant(dateTime) : null;
+ }
+
+ public static Date convertToDateViaInstant(LocalDateTime dateToConvert) {
+ return java.util.Date
+ .from(dateToConvert.atZone(ZoneId.systemDefault())
+ .toInstant());
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/shell/AuthorCommands.java b/src/main/java/ru/otus/mkulikov/shell/AuthorCommands.java
new file mode 100644
index 0000000..c239e0d
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/shell/AuthorCommands.java
@@ -0,0 +1,54 @@
+package ru.otus.mkulikov.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellOption;
+import ru.otus.mkulikov.app.model.Author;
+import ru.otus.mkulikov.app.service.AuthorManageService;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 14:46
+ */
+
+@ShellComponent
+@RequiredArgsConstructor
+public class AuthorCommands {
+
+ private final AuthorManageService authorManageService;
+
+ @ShellMethod(key = {"getAuthorById"}, value = "Select author by id.")
+ public String getAuthorById(@ShellOption long id) {
+ Author author = authorManageService.getAuthorById(id);
+ return author.toString();
+ }
+
+ @ShellMethod(key = {"getAuthors"}, value = "Select all authors.")
+ public String getAuthors() {
+ List allObjects = authorManageService.getAuthors();
+ return allObjects.toString();
+ }
+
+ @ShellMethod(key = {"addAuthor"}, value = "Add new author.")
+ public String addAuthor(@ShellOption String surname, @ShellOption String firstName, @ShellOption String secondName) {
+ long id = authorManageService.addAuthor(surname, firstName, secondName);
+ return "Add " + (id != 0 ? 1 : 0) + " row(s)";
+ }
+
+ @ShellMethod(key = {"updateAuthor"}, value = "Update author by id.")
+ public String updateAuthor(@ShellOption long id, @ShellOption String surname, @ShellOption String firstName, @ShellOption String secondName) {
+ int count = authorManageService.updateAuthor(id, surname, firstName, secondName);
+ return "Updated " + count + " row(s)";
+ }
+
+ @ShellMethod(key = {"deleteAuthor"}, value = "Delete author by id.")
+ public String deleteAuthor(@ShellOption long id) {
+ int count = authorManageService.deleteAuthor(id);
+ return "Deleted " + count + " row(s)";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/shell/BookCommands.java b/src/main/java/ru/otus/mkulikov/shell/BookCommands.java
new file mode 100644
index 0000000..d33aed8
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/shell/BookCommands.java
@@ -0,0 +1,54 @@
+package ru.otus.mkulikov.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellOption;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.service.BookManageSevice;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 23.05.2019
+ * Time: 17:33
+ */
+
+@ShellComponent
+@RequiredArgsConstructor
+public class BookCommands {
+
+ private final BookManageSevice bookManageSevice;
+
+ @ShellMethod(key = {"getBookById"}, value = "Select book by id.")
+ public String getBookById(@ShellOption long id) {
+ Book book = bookManageSevice.getBookById(id);
+ return book.toString();
+ }
+
+ @ShellMethod(key = {"getBooks"}, value = "Select all books.")
+ public String getBooks() {
+ List allObjects = bookManageSevice.getBooks();
+ return allObjects.toString();
+ }
+
+ @ShellMethod(key = {"addBook"}, value = "Add new book.")
+ public String addBook(@ShellOption String caption, @ShellOption int authorId, @ShellOption int genreId, @ShellOption String description) {
+ long id = bookManageSevice.addBook(caption, authorId, genreId, description);
+ return "Add " + (id != 0 ? 1 : 0) + " row(s)";
+ }
+
+ @ShellMethod(key = {"updateBook"}, value = "Update book by id.")
+ public String updateBook(@ShellOption long id, @ShellOption String caption, @ShellOption int authorId, @ShellOption int genreId, @ShellOption String description) {
+ int count = bookManageSevice.updateBook(id, caption, authorId, genreId, description);
+ return "Updated " + count + " row(s)";
+ }
+
+ @ShellMethod(key = {"deleteBook"}, value = "Delete book by id.")
+ public String deleteBook(@ShellOption long id) {
+ int count = bookManageSevice.deleteBook(id);
+ return "Deleted " + count + " row(s)";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/shell/CommentCommands.java b/src/main/java/ru/otus/mkulikov/shell/CommentCommands.java
new file mode 100644
index 0000000..e1dde06
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/shell/CommentCommands.java
@@ -0,0 +1,60 @@
+package ru.otus.mkulikov.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellOption;
+import ru.otus.mkulikov.app.model.Comment;
+import ru.otus.mkulikov.app.service.CommentManageService;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 14:47
+ */
+
+@ShellComponent
+@RequiredArgsConstructor
+public class CommentCommands {
+
+ private final CommentManageService commentManageService;
+
+ @ShellMethod(key = {"getCommentById"}, value = "Select comment by id.")
+ public String getCommentById(@ShellOption long id) {
+ Comment comment = commentManageService.getCommentById(id);
+ return comment.toString();
+ }
+
+ @ShellMethod(key = {"getComments"}, value = "Select all comments.")
+ public String getComments() {
+ List allObjects = commentManageService.getComments();
+ return allObjects.toString();
+ }
+
+ @ShellMethod(key = {"getCommentsByBookId"}, value = "Select all comments.")
+ public String getCommentsByBookId(@ShellOption long bookId) {
+ List allObjects = commentManageService.getCommentsByBookId(bookId);
+ return allObjects.toString();
+ }
+
+ @ShellMethod(key = {"addComment"}, value = "Add new comment.")
+ public String addComment(@ShellOption long bookId, @ShellOption String userName, @ShellOption String text) {
+ long id = commentManageService.addComment(bookId, userName, text);
+ return "Add " + (id != 0 ? 1 : 0) + " row(s)";
+ }
+
+ @ShellMethod(key = {"updateComment"}, value = "Update comment by id.")
+ public String updateComment(@ShellOption long id, @ShellOption String userName, @ShellOption String text) {
+ int count = commentManageService.updateComment(id, userName, text);
+ return "Updated " + count + " row(s)";
+ }
+
+ @ShellMethod(key = {"deleteComment"}, value = "Delete comment by id.")
+ public String deleteComment(@ShellOption long id) {
+ int count = commentManageService.deleteComment(id);
+ return "Deleted " + count + " row(s)";
+ }
+}
diff --git a/src/main/java/ru/otus/mkulikov/shell/GenreCommands.java b/src/main/java/ru/otus/mkulikov/shell/GenreCommands.java
new file mode 100644
index 0000000..9d73476
--- /dev/null
+++ b/src/main/java/ru/otus/mkulikov/shell/GenreCommands.java
@@ -0,0 +1,54 @@
+package ru.otus.mkulikov.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellOption;
+import ru.otus.mkulikov.app.model.Genre;
+import ru.otus.mkulikov.app.service.GenreManageService;
+
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 14:47
+ */
+
+@ShellComponent
+@RequiredArgsConstructor
+public class GenreCommands {
+
+ private final GenreManageService genreManageService;
+
+ @ShellMethod(key = {"getGenreById"}, value = "Select genre by id.")
+ public String getGenreById(@ShellOption long id) {
+ Genre genre = genreManageService.getGenreById(id);
+ return genre.toString();
+ }
+
+ @ShellMethod(key = {"getGenres"}, value = "Select all genres.")
+ public String getGenres() {
+ List allObjects = genreManageService.getGenres();
+ return allObjects.toString();
+ }
+
+ @ShellMethod(key = {"addGenre"}, value = "Add new genre.")
+ public String addGenre(@ShellOption String name) {
+ long id = genreManageService.addGenre(name);
+ return "Add " + (id != 0 ? 1 : 0) + " row(s)";
+ }
+
+ @ShellMethod(key = {"updateGenre"}, value = "Update genre by id.")
+ public String updateGenre(@ShellOption long id, @ShellOption String name) {
+ int count = genreManageService.updateGenre(id, name);
+ return "Updated " + count + " row(s)";
+ }
+
+ @ShellMethod(key = {"deleteGenre"}, value = "Delete genre by id.")
+ public String deleteGenre(@ShellOption long id) {
+ int count = genreManageService.deleteGenre(id);
+ return "Deleted " + count + " row(s)";
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..1184619
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,19 @@
+spring:
+ banner:
+ location: "classpath:banner.txt"
+
+ datasource:
+ initialization-mode: always
+ platform: h2
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+
+ properties:
+ hibernate:
+ show_sql: false
+ format_sql: false
+
+ liquibase:
+ enabled: true
\ No newline at end of file
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
new file mode 100644
index 0000000..1687640
--- /dev/null
+++ b/src/main/resources/banner.txt
@@ -0,0 +1,11 @@
+ ___ ___ ___ ___ ___
+ /\__\ /\ \ /\__\ /\ \ /\ \
+ /:/ / /::\ \ /:/ / /::\ \ /::\ \
+ /:/ / /:/\:\ \ /:/ / /:/\:\ \ /:/\ \ \
+ /:/ / ___ /:/ \:\ \ /:/ / /::\~\:\ \ _\:\~\ \ \
+ /:/__/ /\__\ /:/__/ \:\__\ /:/__/ /:/\:\ \:\__\ /\ \:\ \ \__\
+ \:\ \ /:/ / \:\ \ /:/ / \:\ \ \:\~\:\ \/__/ \:\ \:\ \/__/
+ \:\ /:/ / \:\ /:/ / \:\ \ \:\ \:\__\ \:\ \:\__\
+ \:\/:/ / \:\/:/ / \:\ \ \:\ \/__/ \:\/:/ /
+ \::/ / \::/ / \:\__\ \:\__\ \::/ /
+ \/__/ \/__/ \/__/ \/__/ \/__/
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml b/src/main/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml
new file mode 100644
index 0000000..92d188b
--- /dev/null
+++ b/src/main/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml
@@ -0,0 +1,154 @@
+databaseChangeLog:
+- changeSet:
+ id: 2019-06-22--0001-schema-author
+ author: KulikovMV
+ createTable:
+ tableName: author
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_author
+ - column:
+ name: surname
+ type: varchar(100)
+ constraints:
+ nullable: false
+ - column:
+ name: first_name
+ type: varchar(100)
+ constraints:
+ nullable: false
+ - column:
+ name: second_name
+ type: varchar(100)
+- changeSet:
+ id: 2019-06-22--0001-schema-genre
+ author: KulikovMV
+ createTable:
+ tableName: genre
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_genre
+ - column:
+ name: name
+ type: varchar(100)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-book
+ author: KulikovMV
+ createTable:
+ tableName: book
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_book
+ - column:
+ name: add_record_date
+ type: timestamp
+ defaultValueDate: sysdate
+ constraints:
+ nullable: false
+ - column:
+ name: caption
+ type: varchar(255)
+ constraints:
+ nullable: false
+ - column:
+ name: author_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_author
+ references: author(id)
+ - column:
+ name: genre_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_genre
+ references: genre(id)
+ - column:
+ name: description
+ type: varchar(255)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-comment
+ author: KulikovMV
+ createTable:
+ tableName: comment
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_comment
+ - column:
+ name: book_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_comment
+ references: book(id)
+ deleteCascade: true
+ onDelete: cascade
+ - column:
+ name: add_record_date
+ type: timestamp
+ defaultValueDate: sysdate
+ constraints:
+ nullable: false
+ - column:
+ name: user_name
+ type: varchar(20)
+ constraints:
+ nullable: false
+ - column:
+ name: text
+ type: varchar(500)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-sequences
+ author: KulikovMV
+ changes:
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_author
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_genre
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_book
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_comment
+ startValue: 1
diff --git a/src/main/resources/db/changelog/data/2019-06-22--0001-data.yaml b/src/main/resources/db/changelog/data/2019-06-22--0001-data.yaml
new file mode 100644
index 0000000..cbfcff5
--- /dev/null
+++ b/src/main/resources/db/changelog/data/2019-06-22--0001-data.yaml
@@ -0,0 +1,14 @@
+databaseChangeLog:
+- changeSet:
+ id: 0001-chema-sql
+ author: KulikovMV
+ context: test
+ changes:
+ - sqlFile:
+ dbms: h2, oracle
+ encoding: utf8
+ endDelimiter: ;
+ path: sql/2019-06-22--0001-data-h2.sql
+ relativeToChangelogFile: true
+ splitStatements: true
+ stripComments: true
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql b/src/main/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql
new file mode 100644
index 0000000..bca97c4
--- /dev/null
+++ b/src/main/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql
@@ -0,0 +1,39 @@
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname1', 'FirstName1', 'SecondName1');
+
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname2', 'FirstName2', 'SecondName2');
+
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname3', 'FirstName3', 'SecondName3');
+
+--------------------------
+
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre1');
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre2');
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre3');
+
+--------------------------
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_1', 1, 1, 'description');
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_2', 1, 1, 'description');
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_3', 1, 1, 'description');
+
+--------------------------
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 1, to_date('2019-01-01 10:01:01','YYYY-MM-DD HH24-MI-SS'), 'user1', 'text1');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 1, to_date('2019-01-01 10:01:02','YYYY-MM-DD HH24-MI-SS'), 'user2', 'text2');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 2, to_date('2019-01-01 10:01:03','YYYY-MM-DD HH24-MI-SS'), 'user3', 'text3');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 2, to_date('2019-01-01 10:01:04','YYYY-MM-DD HH24-MI-SS'), 'user4', 'text4');
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 0000000..3adc919
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1,5 @@
+databaseChangeLog:
+- include:
+ file: db/changelog/1.0/2019-06-22--0001-schema.yaml
+- include:
+ file: db/changelog/data/2019-06-22--0001-data.yaml
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/dao/AuthorDaoJpaTest.java b/src/test/java/ru/otus/mkulikov/app/dao/AuthorDaoJpaTest.java
new file mode 100644
index 0000000..261afcd
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/dao/AuthorDaoJpaTest.java
@@ -0,0 +1,106 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import ru.otus.mkulikov.app.model.Author;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 29.05.2019
+ * Time: 10:12
+ */
+
+@DataJpaTest
+@DisplayName("Класс AuthorDaoJpa")
+@ComponentScan("ru.otus.mkulikov.app")
+@TestPropertySource(locations= "classpath:application.yml")
+class AuthorDaoJpaTest {
+
+ @Autowired
+ private AuthorDao authorDao;
+
+ @Test
+ @DisplayName("Получение автора по id")
+ void getById() {
+ Optional author = authorDao.findById(1L);
+
+ assertAll(
+ "author",
+ () -> assertNotNull(author.orElse(null)),
+ () -> assertEquals(1L, author.orElse(null).getId()),
+ () -> assertEquals("Surname", author.orElse(null).getSurname()),
+ () -> assertEquals("FirstName", author.orElse(null).getFirstName()),
+ () -> assertEquals("SecondName", author.orElse(null).getSecondName())
+ );
+ }
+
+ @Test
+ @DisplayName("Получение всех авторов")
+ void getAllObjects() {
+ List authors = authorDao.findAll();
+
+ assertAll(
+ "authors",
+ () -> assertNotNull(authors),
+ () -> assertEquals(3, authors.size()),
+ () -> assertEquals("Surname", authors.get(0).getSurname()),
+ () -> assertEquals("Surname2", authors.get(1).getSurname()),
+ () -> assertEquals("Surname3", authors.get(2).getSurname())
+ );
+ }
+
+ @Test
+ @DisplayName("Добавление автора")
+ void addObject() {
+ Author author = authorDao.save(new Author("TestSurname", "TestFirstName", "TestSecondName"));
+ Optional author_selected = authorDao.findById(4L);
+
+ assertAll(
+ "author",
+ () -> assertNotNull(author_selected),
+ () -> assertEquals(4, author_selected.orElse(null).getId()),
+ () -> assertEquals(author.getSurname(), author_selected.orElse(null).getSurname()),
+ () -> assertEquals(author.getFirstName(), author_selected.orElse(null).getFirstName()),
+ () -> assertEquals(author.getSecondName(), author_selected.orElse(null).getSecondName())
+ );
+ }
+
+ @Test
+ @DisplayName("Удаление автора, который используется в таблице книг")
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ void deleteObject() {
+ assertThrows(DataIntegrityViolationException.class, () -> authorDao.deleteById(1L));
+ assertThat(authorDao.count()).isEqualTo(3);
+ }
+
+ @Test
+ @DisplayName("Обновление автора")
+ void updateObject() {
+ authorDao.save(
+ new Author(1L, "TestSurname", "TestFirstName", "TestSecondName")
+ );
+ Author author = authorDao.findById(1L).orElse(null);
+
+ assertAll(
+ "author",
+ () -> assertEquals(1L, author.getId()),
+ () -> assertEquals("TestSurname", author.getSurname()),
+ () -> assertEquals("TestFirstName", author.getFirstName()),
+ () -> assertEquals("TestSecondName", author.getSecondName())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/dao/BookDaoJpaTest.java b/src/test/java/ru/otus/mkulikov/app/dao/BookDaoJpaTest.java
new file mode 100644
index 0000000..e397ace
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/dao/BookDaoJpaTest.java
@@ -0,0 +1,123 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.model.Author;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Genre;
+import ru.otus.mkulikov.app.utils.DateUtil;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 28.05.2019
+ * Time: 13:16
+ */
+
+@DataJpaTest
+@DisplayName("Класс BookDaoJpa")
+@ComponentScan("ru.otus.mkulikov.app")
+@TestPropertySource(locations= "classpath:application.yml")
+class BookDaoJpaTest {
+
+ @Autowired
+ private BookDao bookDaoJpa;
+
+ @Autowired
+ private AuthorDao authorDao;
+
+ @Autowired
+ private GenreDao genreDao;
+
+ @Test
+ @DisplayName("Получение книги по id")
+ void getById() {
+ Book book = bookDaoJpa.getById(1L).orElse(null);
+
+ assertAll(
+ "book",
+ () -> assertNotNull(book),
+ () -> assertEquals(1L, book.getId()),
+ () -> assertEquals("2019-01-01", DateUtil.dateToString(book.getAddRecordDate())),
+ () -> assertEquals("book_1", book.getCaption()),
+ () -> assertEquals(1, book.getAuthor().getId()),
+ () -> assertEquals(1, book.getGenre().getId()),
+ () -> assertEquals("description", book.getDescription())
+ );
+ }
+
+ @Test
+ @DisplayName("Получение всех книг")
+ void getAllObjects() {
+ List books = bookDaoJpa.getAllObjects();
+
+ assertAll(
+ "books",
+ () -> assertNotNull(books),
+ () -> assertEquals(3, books.size()),
+ () -> assertEquals("book_1", books.get(0).getCaption()),
+ () -> assertEquals("book_2", books.get(1).getCaption()),
+ () -> assertEquals("book_3", books.get(2).getCaption())
+ );
+ }
+
+ @Test
+ @DisplayName("Добавление книги")
+ void addObject() {
+ bookDaoJpa.save(getNewBook());
+ Book book = bookDaoJpa.getById(4L).orElse(null);
+
+ assertAll(
+ "book",
+ () -> assertNotNull(book),
+ () -> assertEquals(4L, book.getId()),
+ () -> assertEquals("Test_Book", book.getCaption()),
+ () -> assertEquals(1, book.getAuthor().getId()),
+ () -> assertEquals(1, book.getGenre().getId()),
+ () -> assertEquals("Test_Description", book.getDescription())
+ );
+ }
+
+ @Test
+ @DisplayName("Удаление книги по id")
+ void deleteObject() {
+ bookDaoJpa.deleteById(1L);
+ Optional book = bookDaoJpa.getById(1L);
+ assertTrue(book.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Обновление книги")
+ void updateObject() {
+ bookDaoJpa.save(getUpdatedBook());
+ Book book2 = bookDaoJpa.getById(1L).orElse(null);
+
+ assertAll(
+ "book",
+ () -> assertEquals("Test_Book", book2.getCaption()),
+ () -> assertEquals("Test_Description", book2.getDescription())
+ );
+ }
+
+ private Book getNewBook() {
+ Author author = authorDao.findById(1L).orElse(null);
+ Genre genre = genreDao.findById(1L).orElse(null);
+ return new Book("Test_Book", author, genre, "Test_Description");
+ }
+
+ private Book getUpdatedBook() {
+ Author author = authorDao.findById(1L).orElse(null);
+ Genre genre = genreDao.findById(1L).orElse(null);
+ return new Book(1L, new Date(), "Test_Book", author, genre, "Test_Description");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/dao/CommentDaoJpaTest.java b/src/test/java/ru/otus/mkulikov/app/dao/CommentDaoJpaTest.java
new file mode 100644
index 0000000..9c82366
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/dao/CommentDaoJpaTest.java
@@ -0,0 +1,136 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Comment;
+import ru.otus.mkulikov.app.utils.DateUtil;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 28.05.2019
+ * Time: 9:45
+ */
+
+@DataJpaTest
+@DisplayName("Класс CommentDaoJpa")
+@ComponentScan("ru.otus.mkulikov.app")
+@TestPropertySource(locations= "classpath:application.yml")
+class CommentDaoJpaTest {
+
+ @Autowired
+ private BookDao bookDaoJpa;
+
+ @Autowired
+ private CommentDao commentDaoJpa;
+
+ @Test
+ @DisplayName("Получение комментария по id")
+ void getById() {
+ Comment comment = commentDaoJpa.getById(1L).orElse(null);
+
+ assertAll(
+ "comment",
+ () -> assertNotNull(comment),
+ () -> assertNotNull(comment.getBook()),
+ () -> assertEquals(1L, comment.getId()),
+ () -> assertEquals("user1", comment.getUserName()),
+ () -> assertEquals("2019-01-01 10:01:01", DateUtil.dateTimeToString(comment.getAddRecordDate())),
+ () -> assertEquals("text1", comment.getText())
+ );
+ }
+
+ @Test
+ @DisplayName("Получение всех комментариев")
+ void getAllObjects() {
+ List comments = commentDaoJpa.findAll();
+
+ assertAll(
+ "comments",
+ () -> assertNotNull(comments),
+ () -> assertEquals(4, comments.size()),
+ () -> assertEquals("text1", comments.get(0).getText()),
+ () -> assertEquals("text2", comments.get(1).getText()),
+ () -> assertEquals("text3", comments.get(2).getText()),
+ () -> assertEquals("text4", comments.get(3).getText())
+ );
+ }
+
+ @Test
+ @DisplayName("Получение всех комментариев для книги")
+ void getObjectsByBook() {
+ List comments = commentDaoJpa.getByBookId(1L);
+
+ assertAll(
+ "comments",
+ () -> assertNotNull(comments),
+ () -> assertEquals(2, comments.size()),
+ () -> assertEquals("text1", comments.get(0).getText()),
+ () -> assertEquals("text2", comments.get(1).getText())
+ );
+ }
+
+ @Test
+ @DisplayName("Добавление комментария")
+ void addObject() {
+ Date date = new Date();
+ Book book = bookDaoJpa.getById(1L).orElse(null);
+ commentDaoJpa.save(new Comment(book, date, "user5", "text5"));
+
+ Comment comment = commentDaoJpa.getById(5L).orElse(null);
+
+ assertAll(
+ "comment",
+ () -> assertNotNull(comment),
+ () -> assertNotNull(comment.getBook()),
+ () -> assertEquals(5L, comment.getId()),
+ () -> assertEquals(date, comment.getAddRecordDate()),
+ () -> assertEquals("user5", comment.getUserName()),
+ () -> assertEquals("text5", comment.getText())
+ );
+ }
+
+ @Test
+ @DisplayName("Удаление комментария")
+ void deleteObject() {
+ commentDaoJpa.deleteById(1L);
+ Optional comment = commentDaoJpa.getById(1L);
+ assertTrue(comment.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Обновление комментария")
+ void updateObject() {
+ Comment comment1 = commentDaoJpa.getById(1L).orElse(null);
+
+ Date date = new Date();
+ commentDaoJpa.save(new Comment(1L, comment1.getBook(), date, "TestUser", "TestText"));
+
+ Comment comment2 = commentDaoJpa.getById(1L).orElse(null);
+
+ assertAll(
+ "comment",
+ () -> assertNotNull(comment1),
+ () -> assertNotNull(comment2),
+ () -> assertNotNull(comment1.getBook()),
+ () -> assertNotNull(comment2.getBook()),
+ () -> assertEquals("user1", comment1.getUserName()),
+ () -> assertEquals("text1", comment1.getText()),
+ () -> assertEquals(date, comment2.getAddRecordDate()),
+ () -> assertEquals("TestUser", comment2.getUserName()),
+ () -> assertEquals("TestText", comment2.getText())
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/dao/GenreDaoJpaTest.java b/src/test/java/ru/otus/mkulikov/app/dao/GenreDaoJpaTest.java
new file mode 100644
index 0000000..128679d
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/dao/GenreDaoJpaTest.java
@@ -0,0 +1,97 @@
+package ru.otus.mkulikov.app.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 28.05.2019
+ * Time: 9:45
+ */
+
+@DataJpaTest
+@DisplayName("Класс GenreDaoJpa")
+@ComponentScan("ru.otus.mkulikov.app")
+@TestPropertySource(locations= "classpath:application.yml")
+class GenreDaoJpaTest {
+
+ @Autowired
+ private GenreDao genreDao;
+
+ @Test
+ @DisplayName("Получение жанра по id")
+ void getById() {
+ Genre genre = genreDao.findById(1L).orElse(null);
+
+ assertAll(
+ "genre",
+ () -> assertNotNull(genre),
+ () -> assertEquals(1L, genre.getId()),
+ () -> assertEquals("Genre1", genre.getName())
+ );
+ }
+
+ @Test
+ @DisplayName("Получение всех жанров")
+ void getAllObjects() {
+ List genres = genreDao.findAll();
+
+ assertAll(
+ "genres",
+ () -> assertNotNull(genres),
+ () -> assertEquals(3, genres.size()),
+ () -> assertEquals("Genre1", genres.get(0).getName()),
+ () -> assertEquals("Genre2", genres.get(1).getName()),
+ () -> assertEquals("Genre3", genres.get(2).getName())
+ );
+ }
+
+ @Test
+ @DisplayName("Добавление жанра")
+ void addObject() {
+ genreDao.save(new Genre("Test4"));
+ Genre genre = genreDao.findById(4L).orElse(null);
+
+ assertAll(
+ "genre",
+ () -> assertNotNull(genre),
+ () -> assertEquals(4L, genre.getId()),
+ () -> assertEquals("Test4", genre.getName())
+ );
+ }
+
+ @Test
+ @DisplayName("Удаление жанра, который используется в таблице книг")
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ void deleteObject() {
+ assertThrows(DataIntegrityViolationException.class, () -> genreDao.deleteById(1L));
+ assertThat(genreDao.count()).isEqualTo(3);
+ }
+
+ @Test
+ @DisplayName("Обновление жанра")
+ void updateObject() {
+ genreDao.save(new Genre(1L, "UpdatedName"));
+ Genre genre = genreDao.findById(1L).orElse(null);
+
+ assertAll(
+ "genre",
+ () -> assertEquals(1L, genre.getId()),
+ () -> assertEquals("UpdatedName", genre.getName())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/model/DateUtilTest.java b/src/test/java/ru/otus/mkulikov/app/model/DateUtilTest.java
new file mode 100644
index 0000000..1065900
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/model/DateUtilTest.java
@@ -0,0 +1,54 @@
+package ru.otus.mkulikov.app.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import ru.otus.mkulikov.app.utils.DateUtil;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 29.05.2019
+ * Time: 11:39
+ */
+
+@DisplayName("Класс DateUtil")
+class DateUtilTest {
+
+ private final String DATE_PATTERN = "yyyy-MM-dd";
+ private final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ @Test
+ @DisplayName("Перевод даты в строку, формат даты '" + DATE_PATTERN + "'")
+ void dateToString() {
+ Date date = new Date();
+
+ DateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
+ assertEquals(dateFormat.format(date), DateUtil.dateToString(date));
+ }
+
+ @Test
+ @DisplayName("Перевод даты в строку, формат даты '" + DATETIME_PATTERN + "'")
+ void dateTimeToString() {
+ Date date = new Date();
+
+ DateFormat dateFormat = new SimpleDateFormat(DATETIME_PATTERN);
+ assertEquals(dateFormat.format(date), DateUtil.dateTimeToString(date));
+ }
+
+ @Test
+ @DisplayName("Перевод строки в дату, формат даты '" + DATETIME_PATTERN + "'")
+ void stringToDateTime() {
+ String datetime = "2019-01-01 10:01:01";
+
+ Date date = DateUtil.stringToDateTime(datetime);
+ DateFormat dateFormat = new SimpleDateFormat(DATETIME_PATTERN);
+
+ assertEquals(datetime, dateFormat.format(date));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/service/AuthorManageSeviceImplTest.java b/src/test/java/ru/otus/mkulikov/app/service/AuthorManageSeviceImplTest.java
new file mode 100644
index 0000000..74b63f5
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/service/AuthorManageSeviceImplTest.java
@@ -0,0 +1,129 @@
+package ru.otus.mkulikov.app.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.dao.AuthorDao;
+import ru.otus.mkulikov.app.model.Author;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:59
+ */
+
+@DisplayName("Класс AuthorManageSevice")
+@TestPropertySource(locations= "classpath:application.yml")
+@ExtendWith(MockitoExtension.class)
+class AuthorManageSeviceImplTest {
+
+ @Mock
+ private AuthorDao authorDao;
+
+ @InjectMocks
+ private AuthorManageServiceImpl authorManageService;
+
+ @Test
+ @DisplayName("Получение автора по id")
+ void getAuthorById() {
+ when(authorDao.findById(anyLong())).thenReturn( Optional.of(getAuthor(1L)) );
+ Author author = authorManageService.getAuthorById(1L);
+
+ assertThat(author).isNotNull();
+ assertThat(author).isEqualTo(getAuthor(1L));
+ }
+
+ @Test
+ @DisplayName("Получение всех авторов")
+ void getAuthors() {
+ when(authorDao.findAll()).thenReturn( getAuthorList() );
+ List authors = authorManageService.getAuthors();
+
+ assertThat(authors).isNotNull();
+ assertThat(authors).hasSize(3);
+ assertThat(authors).containsAll(getAuthorList());
+ }
+
+ @Test
+ @DisplayName("Добавление автора")
+ void addAuthor() {
+ when(authorDao.save(any(Author.class))).then(new Answer() {
+ int sequence = 1;
+
+ @Override
+ public Author answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Author author = (Author) invocationOnMock.getArgument(0);
+ author.setId(++sequence);
+ return author;
+ }
+ });
+ when(authorDao.findById(anyLong())).thenReturn( Optional.of(getAuthor(2L)) );
+
+ long id = authorManageService.addAuthor("Surname1", "FirstName1", "SecondName1");
+ Author author = authorManageService.getAuthorById(id);
+
+ assertThat(id).isEqualTo(2L);
+ assertThat(author).isNotNull();
+ assertThat(author).isEqualTo(getAuthor(2L));
+ }
+
+ @Test
+ @DisplayName("Обновление автора")
+ void updateAuthor() {
+ when(authorDao.save(any(Author.class))).then(new Answer() {
+
+ @Override
+ public Author answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Author author = (Author) invocationOnMock.getArgument(0);
+ author.setId(1L);
+ return author;
+ }
+ });
+ when(authorDao.findById(anyLong())).thenReturn( Optional.of(getAuthor(1L)) );
+
+ int count = authorManageService.updateAuthor(1L, "Surname1", "FirstName1", "SecondName1");
+ Author author = authorManageService.getAuthorById(1L);
+
+ assertThat(count).isEqualTo(1);
+ assertThat(author).isNotNull();
+ assertThat(author).isEqualTo(getAuthor(1L));
+ }
+
+ @Test
+ @DisplayName("Удаление автора, который используется в таблице книг")
+ void deleteAuthor() {
+ doThrow(DataIntegrityViolationException.class).when(authorDao).deleteById(1L);
+ assertThrows(DataIntegrityViolationException.class, () -> { authorManageService.deleteAuthor(1L); });
+ }
+
+ private List getAuthorList() {
+ List authors = new ArrayList();
+ authors.add(getAuthor(1L));
+ authors.add(getAuthor(2L));
+ authors.add(getAuthor(3L));
+ return authors;
+ }
+
+ private Author getAuthor(long id) {
+ return new Author(id, "Surname" + id, "FirstName" + id, "SecondName" + id);
+ }
+}
diff --git a/src/test/java/ru/otus/mkulikov/app/service/BookManageSeviceImplTest.java b/src/test/java/ru/otus/mkulikov/app/service/BookManageSeviceImplTest.java
new file mode 100644
index 0000000..0885b9f
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/service/BookManageSeviceImplTest.java
@@ -0,0 +1,148 @@
+package ru.otus.mkulikov.app.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.dao.AuthorDao;
+import ru.otus.mkulikov.app.dao.BookDao;
+import ru.otus.mkulikov.app.dao.GenreDao;
+import ru.otus.mkulikov.app.model.Author;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 29.05.2019
+ * Time: 13:40
+ */
+
+@DisplayName("Класс BookManageSevice")
+@ExtendWith(MockitoExtension.class)
+@TestPropertySource(locations= "classpath:application.yml")
+class BookManageSeviceImplTest {
+
+ @Mock
+ private BookDao bookDao;
+
+ @Mock
+ private AuthorDao authorDao;
+
+ @Mock
+ private GenreDao genreDao;
+
+ @InjectMocks
+ private BookManageSeviceImpl booksManageSevice;
+
+ @Test
+ @DisplayName("Получение книги по id")
+ void getBookById() {
+ Book book = getBook(1L);
+ when(bookDao.getById(anyLong())).thenReturn( Optional.of(book) );
+ Book bookById = booksManageSevice.getBookById(1L);
+
+ assertThat(bookById).isNotNull();
+ assertThat(bookById).isEqualTo(book);
+ }
+
+ @Test
+ @DisplayName("Получение всех книг")
+ void getBooks() {
+ List list = getBooksList();
+ when(bookDao.getAllObjects()).thenReturn( list );
+ List comments = booksManageSevice.getBooks();
+
+ assertThat(comments).isNotNull();
+ assertThat(comments).hasSize(3);
+ assertThat(comments).containsAll(list);
+ }
+
+ @Test
+ @DisplayName("Добавление книги")
+ void addBook() {
+ when(bookDao.save(any(Book.class))).then(new Answer() {
+ int sequence = 1;
+
+ @Override
+ public Book answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Book book = (Book) invocationOnMock.getArgument(0);
+ book.setId(++sequence);
+ return book;
+ }
+ });
+ when(authorDao.findById(anyLong())).thenReturn( Optional.of(getAuthor(1L)) );
+ when(genreDao.findById(anyLong())).thenReturn( Optional.of(getGenre(1L)) );
+
+ long id = booksManageSevice.addBook("Caption", 1L, 1L, "description");
+
+ assertThat(id).isEqualTo(2L);
+ }
+
+ @Test
+ @DisplayName("Обновление книги")
+ void updateBook() {
+ when(bookDao.save(any(Book.class))).then(new Answer() {
+
+ @Override
+ public Book answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Book book = (Book) invocationOnMock.getArgument(0);
+ book.setId(1L);
+ return book;
+ }
+ });
+ when(authorDao.findById(anyLong())).thenReturn( Optional.of(getAuthor(1L)) );
+ when(genreDao.findById(anyLong())).thenReturn( Optional.of(getGenre(1L)) );
+ when(bookDao.getById(anyLong())).thenReturn( Optional.of(getBook(1L)) );
+
+ int count = booksManageSevice.updateBook(1L, "Caption", 1L, 1L, "description");
+
+ assertThat(count).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("Удаление книги по id")
+ void deleteBook() {
+ doThrow(DataIntegrityViolationException.class).when(bookDao).deleteById(1L);
+ assertThrows(DataIntegrityViolationException.class, () -> { booksManageSevice.deleteBook(1L); });
+ }
+
+ private Author getAuthor(long id) {
+ return new Author(id, "Surname" + id, "FirstName" + id, "SecondName" + id);
+ }
+
+ private Genre getGenre(long id) {
+ return new Genre(id, "Genre" + id);
+ }
+
+ private Book getBook(long id) {
+ Author author = getAuthor(id);
+ Genre genre = getGenre(id);
+ return new Book(id, "Test_Book", author, genre, "Test_Description");
+ }
+
+ private List getBooksList() {
+ List list = new ArrayList<>();
+ list.add(getBook(1L));
+ list.add(getBook(2L));
+ list.add(getBook(3L));
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/otus/mkulikov/app/service/CommentManageSeviceImplTest.java b/src/test/java/ru/otus/mkulikov/app/service/CommentManageSeviceImplTest.java
new file mode 100644
index 0000000..40b9e65
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/service/CommentManageSeviceImplTest.java
@@ -0,0 +1,156 @@
+package ru.otus.mkulikov.app.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.dao.BookDao;
+import ru.otus.mkulikov.app.dao.CommentDao;
+import ru.otus.mkulikov.app.model.Author;
+import ru.otus.mkulikov.app.model.Book;
+import ru.otus.mkulikov.app.model.Comment;
+import ru.otus.mkulikov.app.model.Genre;
+import ru.otus.mkulikov.app.utils.DateUtil;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:56
+ */
+
+@DisplayName("Класс CommentManageSevice")
+@ExtendWith(MockitoExtension.class)
+@TestPropertySource(locations= "classpath:application.yml")
+class CommentManageSeviceImplTest {
+
+ @Mock
+ private CommentDao commentDao;
+
+ @Mock
+ private BookDao bookDao;
+
+ @InjectMocks
+ private CommentManageServiceImpl commentManageService;
+
+ @Test
+ @DisplayName("Получение комментария по id")
+ void getCommentById() {
+ Comment comment = getComment(1L);
+ when(commentDao.getById(anyLong())).thenReturn( Optional.of(comment) );
+ Comment commentById = commentManageService.getCommentById(1L);
+
+ assertThat(commentById).isNotNull();
+ assertThat(commentById).isEqualTo(comment);
+ }
+
+ @Test
+ @DisplayName("Получение всех комментариев")
+ void getComments() {
+ List commentsList = getCommentList();
+ when(commentDao.findAll()).thenReturn( commentsList );
+ List comments = commentManageService.getComments();
+
+ assertThat(comments).isNotNull();
+ assertThat(comments).hasSize(4);
+ assertThat(comments).containsAll(commentsList);
+ }
+
+ @Test
+ @DisplayName("Добавление комментария")
+ void addComment() {
+ Book book = getBook(1L);
+
+ when(commentDao.save(any(Comment.class))).then(new Answer() {
+ int sequence = 1;
+
+ @Override
+ public Comment answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Comment comment = (Comment) invocationOnMock.getArgument(0);
+ comment.setId(++sequence);
+ return comment;
+ }
+ });
+ when(bookDao.getById(anyLong())).thenReturn( Optional.of(book) );
+
+ long id = commentManageService.addComment(1L, "user5", "text5");
+
+ assertThat(id).isEqualTo(2L);
+ }
+
+ @Test
+ @DisplayName("Получение комментариев по Id книги")
+ void getCommentsByBookId() {
+ List commentList = getCommentList();
+ when(commentDao.getByBookId(anyLong())).thenReturn( commentList );
+ List comments = commentManageService.getCommentsByBookId(1L);
+
+ assertThat(comments).isNotNull();
+ assertThat(comments).isEqualTo(commentList);
+ }
+
+ @Test
+ @DisplayName("Обновление комментария")
+ void updateComment() {
+ when(commentDao.save(any(Comment.class))).then(new Answer() {
+
+ @Override
+ public Comment answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Comment comment = (Comment) invocationOnMock.getArgument(0);
+ comment.setId(1L);
+ return comment;
+ }
+ });
+ when(commentDao.getById(anyLong())).thenReturn( Optional.of(getComment(1L)) );
+
+ int count = commentManageService.updateComment(1L, "TestUser", "TestText");
+
+ assertThat(count).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("Удаление комментария")
+ void deleteComment() {
+ doThrow(DataIntegrityViolationException.class).when(commentDao).deleteById(1L);
+ assertThrows(DataIntegrityViolationException.class, () -> { commentManageService.deleteComment(1L); });
+ }
+
+ private Comment getComment(long id) {
+ Date date = DateUtil.stringToDateTime("2019-01-01 10:01:01");
+ Book book = getBook(id);
+ return new Comment(id, book, date, "user" + id, "text" + id);
+ }
+
+ private Book getBook(long id) {
+ Author author = new Author(id, "Surname" + id, "FirstName" + id, "SecondName" + id);
+ Genre genre = new Genre(id, "Genre" + id);
+ return new Book(id, "Test_Book", author, genre, "Test_Description");
+ }
+
+ private List getCommentList() {
+ List comments = new ArrayList();
+ comments.add(getComment(1L));
+ comments.add(getComment(2L));
+ comments.add(getComment(3L));
+ comments.add(getComment(4L));
+ return comments;
+ }
+}
diff --git a/src/test/java/ru/otus/mkulikov/app/service/GenreManageSeviceImplTest.java b/src/test/java/ru/otus/mkulikov/app/service/GenreManageSeviceImplTest.java
new file mode 100644
index 0000000..fd97035
--- /dev/null
+++ b/src/test/java/ru/otus/mkulikov/app/service/GenreManageSeviceImplTest.java
@@ -0,0 +1,122 @@
+package ru.otus.mkulikov.app.service;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.context.TestPropertySource;
+import ru.otus.mkulikov.app.dao.GenreDao;
+import ru.otus.mkulikov.app.model.Genre;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by IntelliJ IDEA.
+ * Developer: Maksim Kulikov
+ * Date: 30.05.2019
+ * Time: 15:56
+ */
+
+@DisplayName("Класс GenreManageSevice")
+@ExtendWith(MockitoExtension.class)
+@TestPropertySource(locations= "classpath:application.yml")
+class GenreManageSeviceImplTest {
+
+ @Mock
+ private GenreDao genreDao;
+
+ @InjectMocks
+ private GenreManageServiceImpl genreManageService;
+
+ @Test
+ @DisplayName("Получение жанра по id")
+ void getGenreById() {
+ when(genreDao.findById(anyLong())).thenReturn( Optional.of(getGenre(1L)) );
+ Genre genre = genreManageService.getGenreById(1L);
+
+ assertThat(genre).isNotNull();
+ assertThat(genre).isEqualTo(getGenre(1L));
+ }
+
+ @Test
+ @DisplayName("Получение всех жанров")
+ void getGenres() {
+ when(genreDao.findAll()).thenReturn( getGenreList() );
+ List authors = genreManageService.getGenres();
+
+ assertThat(authors).isNotNull();
+ assertThat(authors).hasSize(3);
+ assertThat(authors).containsAll(getGenreList());
+ }
+
+ @Test
+ @DisplayName("Добавление жанра")
+ void addGenre() {
+ when(genreDao.save(any(Genre.class))).then(new Answer() {
+ int sequence = 1;
+
+ @Override
+ public Genre answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Genre genre = (Genre) invocationOnMock.getArgument(0);
+ genre.setId(++sequence);
+ return genre;
+ }
+ });
+
+ long id = genreManageService.addGenre("Test4");
+
+ assertThat(id).isEqualTo(2L);
+ }
+
+ @Test
+ @DisplayName("Обновление жанра")
+ void updateGenre() {
+ when(genreDao.save(any(Genre.class))).then(new Answer() {
+
+ @Override
+ public Genre answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Genre genre = (Genre) invocationOnMock.getArgument(0);
+ genre.setId(1L);
+ return genre;
+ }
+ });
+ when(genreDao.findById(anyLong())).thenReturn( Optional.of(getGenre(1L)) );
+
+ int count = genreManageService.updateGenre(1L, "UpdatedName");
+
+ assertThat(count).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("Удаление жанра, который используется в таблице книг")
+ void deleteGenre() {
+ doThrow(DataIntegrityViolationException.class).when(genreDao).deleteById(1L);
+ assertThrows(DataIntegrityViolationException.class, () -> { genreManageService.deleteGenre(1L); });
+ }
+
+ private List getGenreList() {
+ List genres = new ArrayList();
+ genres.add(getGenre(1L));
+ genres.add(getGenre(2L));
+ genres.add(getGenre(3L));
+ return genres;
+ }
+
+ private Genre getGenre(long id) {
+ return new Genre(id, "Genre" + id);
+ }
+}
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
new file mode 100644
index 0000000..26a0056
--- /dev/null
+++ b/src/test/resources/application.yml
@@ -0,0 +1,19 @@
+spring:
+ banner:
+ location: "classpath:banner-test.txt"
+
+ datasource:
+ initialization-mode: always
+ platform: test
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+
+ properties:
+ hibernate:
+ show_sql: true
+ format_sql: true
+
+ liquibase:
+ enabled: true
diff --git a/src/test/resources/banner-test.txt b/src/test/resources/banner-test.txt
new file mode 100644
index 0000000..50a692c
--- /dev/null
+++ b/src/test/resources/banner-test.txt
@@ -0,0 +1,11 @@
+ ___ ___ ___ ___ ___ ___ ___
+ /\__\ /\ \ /\__\ /\ \ /\ \ ___ / /\ / /\ ___
+ /:/ / /::\ \ /:/ / /::\ \ /::\ \ /__/\ / /::\ / /::\ /__/\
+ /:/ / /:/\:\ \ /:/ / /:/\:\ \ /:/\ \ \ \ \:\ / /:/\:\ /__/:/\:\ \ \:\
+ /:/ / ___ /:/ \:\ \ /:/ / /::\~\:\ \ _\:\~\ \ \ \__\:\ / /::\ \:\ _\_ \:\ \:\ \__\:\
+ /:/__/ /\__\ /:/__/ \:\__\ /:/__/ /:/\:\ \:\__\ /\ \:\ \ \__\ *** / /::\ /__/:/\:\ \:\ /__/\ \:\ \:\ / /::\
+ \:\ \ /:/ / \:\ \ /:/ / \:\ \ \:\~\:\ \/__/ \:\ \:\ \/__/ / /:/\:\ \ \:\ \:\_\/ \ \:\ \:\_\/ / /:/\:\
+ \:\ /:/ / \:\ /:/ / \:\ \ \:\ \:\__\ \:\ \:\__\ / /:/__\/ \ \:\ \:\ \ \:\_\:\ / /:/__\/
+ \:\/:/ / \:\/:/ / \:\ \ \:\ \/__/ \:\/:/ / /__/:/ \ \:\_\/ \ \:\/:/ /__/:/
+ \::/ / \::/ / \:\__\ \:\__\ \::/ / \__\/ \ \:\ \ \::/ \__\/
+ \/__/ \/__/ \/__/ \/__/ \/__/ \__\/ \__\/
diff --git a/src/test/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml b/src/test/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml
new file mode 100644
index 0000000..92d188b
--- /dev/null
+++ b/src/test/resources/db/changelog/1.0/2019-06-22--0001-schema.yaml
@@ -0,0 +1,154 @@
+databaseChangeLog:
+- changeSet:
+ id: 2019-06-22--0001-schema-author
+ author: KulikovMV
+ createTable:
+ tableName: author
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_author
+ - column:
+ name: surname
+ type: varchar(100)
+ constraints:
+ nullable: false
+ - column:
+ name: first_name
+ type: varchar(100)
+ constraints:
+ nullable: false
+ - column:
+ name: second_name
+ type: varchar(100)
+- changeSet:
+ id: 2019-06-22--0001-schema-genre
+ author: KulikovMV
+ createTable:
+ tableName: genre
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_genre
+ - column:
+ name: name
+ type: varchar(100)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-book
+ author: KulikovMV
+ createTable:
+ tableName: book
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_book
+ - column:
+ name: add_record_date
+ type: timestamp
+ defaultValueDate: sysdate
+ constraints:
+ nullable: false
+ - column:
+ name: caption
+ type: varchar(255)
+ constraints:
+ nullable: false
+ - column:
+ name: author_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_author
+ references: author(id)
+ - column:
+ name: genre_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_genre
+ references: genre(id)
+ - column:
+ name: description
+ type: varchar(255)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-comment
+ author: KulikovMV
+ createTable:
+ tableName: comment
+ columns:
+ - column:
+ name: id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ primaryKey: true
+ primaryKeyName: pk_comment
+ - column:
+ name: book_id
+ type: number(20,0)
+ constraints:
+ nullable: false
+ foreignKeyName: fk_comment
+ references: book(id)
+ deleteCascade: true
+ onDelete: cascade
+ - column:
+ name: add_record_date
+ type: timestamp
+ defaultValueDate: sysdate
+ constraints:
+ nullable: false
+ - column:
+ name: user_name
+ type: varchar(20)
+ constraints:
+ nullable: false
+ - column:
+ name: text
+ type: varchar(500)
+ constraints:
+ nullable: false
+- changeSet:
+ id: 2019-06-22--0001-schema-sequences
+ author: KulikovMV
+ changes:
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_author
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_genre
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_book
+ startValue: 1
+ - createSequence:
+ cycle: true
+ incrementBy: 1
+ ordered: true
+ sequenceName: sq_comment
+ startValue: 1
diff --git a/src/test/resources/db/changelog/data/2019-06-22--0001-data.yaml b/src/test/resources/db/changelog/data/2019-06-22--0001-data.yaml
new file mode 100644
index 0000000..cbfcff5
--- /dev/null
+++ b/src/test/resources/db/changelog/data/2019-06-22--0001-data.yaml
@@ -0,0 +1,14 @@
+databaseChangeLog:
+- changeSet:
+ id: 0001-chema-sql
+ author: KulikovMV
+ context: test
+ changes:
+ - sqlFile:
+ dbms: h2, oracle
+ encoding: utf8
+ endDelimiter: ;
+ path: sql/2019-06-22--0001-data-h2.sql
+ relativeToChangelogFile: true
+ splitStatements: true
+ stripComments: true
\ No newline at end of file
diff --git a/src/test/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql b/src/test/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql
new file mode 100644
index 0000000..75ebe2d
--- /dev/null
+++ b/src/test/resources/db/changelog/data/sql/2019-06-22--0001-data-h2.sql
@@ -0,0 +1,39 @@
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname', 'FirstName', 'SecondName');
+
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname2', 'FirstName2', 'SecondName2');
+
+insert into AUTHOR (ID, SURNAME, FIRST_NAME, SECOND_NAME)
+values (sq_author.nextval, 'Surname3', 'FirstName3', 'SecondName3');
+
+--------------------------
+
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre1');
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre2');
+insert into GENRE (ID, NAME) values (sq_genre.nextval, 'Genre3');
+
+--------------------------
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_1', 1, 1, 'description');
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_2', 1, 1, 'description');
+
+insert into BOOK (ID, ADD_RECORD_DATE, CAPTION, AUTHOR_ID, GENRE_ID, DESCRIPTION)
+values (sq_book.nextval, to_date('2019-01-01','YYYY-MM-DD'), 'book_3', 1, 1, 'description');
+
+--------------------------
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 1, to_date('2019-01-01 10:01:01','YYYY-MM-DD HH24-MI-SS'), 'user1', 'text1');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 1, to_date('2019-01-01 10:01:02','YYYY-MM-DD HH24-MI-SS'), 'user2', 'text2');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 2, to_date('2019-01-01 10:01:03','YYYY-MM-DD HH24-MI-SS'), 'user3', 'text3');
+
+insert into COMMENT (ID, BOOK_ID, ADD_RECORD_DATE, USER_NAME, TEXT)
+values (sq_comment.nextval, 2, to_date('2019-01-01 10:01:04','YYYY-MM-DD HH24-MI-SS'), 'user4', 'text4');
\ No newline at end of file
diff --git a/src/test/resources/db/changelog/db.changelog-master.yaml b/src/test/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 0000000..3adc919
--- /dev/null
+++ b/src/test/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1,5 @@
+databaseChangeLog:
+- include:
+ file: db/changelog/1.0/2019-06-22--0001-schema.yaml
+- include:
+ file: db/changelog/data/2019-06-22--0001-data.yaml
\ No newline at end of file