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