diff --git a/pom.xml b/pom.xml index 5c6e4c7c..fa71af21 100644 --- a/pom.xml +++ b/pom.xml @@ -25,11 +25,19 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.flywaydb</groupId> + <artifactId>flyway-mysql</artifactId> + </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> - <scope>runtime</scope> + <scope>test</scope> + </dependency> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> @@ -38,6 +46,18 @@ </dependency> </dependencies> + <profiles> + <profile> + <id>h2</id> + <dependencies> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + </dependency> + </dependencies> + </profile> + </profiles> + <build> <plugins> <plugin> diff --git a/src/main/java/guru/springframework/sdjpaintro/bootstrap/DataInitializer.java b/src/main/java/guru/springframework/sdjpaintro/bootstrap/DataInitializer.java new file mode 100644 index 00000000..9c561416 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/bootstrap/DataInitializer.java @@ -0,0 +1,58 @@ +package guru.springframework.sdjpaintro.bootstrap; + +import guru.springframework.sdjpaintro.domain.AuthorUuid; +import guru.springframework.sdjpaintro.domain.Book; + +import guru.springframework.sdjpaintro.domain.BookUuid; +import guru.springframework.sdjpaintro.repositories.AuthorUuidRepository; +import guru.springframework.sdjpaintro.repositories.BookRepository; +import guru.springframework.sdjpaintro.repositories.BookUuidRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** + * Created by jt on 6/12/21. + */ +@Profile({"local", "default"}) +@Component +public class DataInitializer implements CommandLineRunner { + + private final BookRepository bookRepository; + private final AuthorUuidRepository authorUuidRepository; + private final BookUuidRepository bookUuidRepository; + + public DataInitializer(BookRepository bookRepository, AuthorUuidRepository authorUuidRepository, BookUuidRepository bookUuidRepository) { + this.bookRepository = bookRepository; + this.authorUuidRepository = authorUuidRepository; + this.bookUuidRepository = bookUuidRepository; + } + + @Override + public void run(String... args) throws Exception { + bookRepository.deleteAll(); + authorUuidRepository.deleteAll(); + + Book bookDDD = new Book("Domain Driven Design", "123", "RandomHouse", null); + Book savedDDD = bookRepository.save(bookDDD); + + Book bookSIA = new Book("Spring In Action", "234234", "Oriely", null); + Book savedSIA = bookRepository.save(bookSIA); + + bookRepository.findAll().forEach(book -> { + System.out.println("Book Id: " + book.getId()); + System.out.println("Book Title: " + book.getTitle()); + }); + + AuthorUuid authorUuid = new AuthorUuid(); + authorUuid.setFirstName("Joe"); + authorUuid.setLastName("Buck"); + AuthorUuid savedAuthor = authorUuidRepository.save(authorUuid); + System.out.println("Saved Author UUID: " + savedAuthor.getId() ); + + BookUuid bookUuid = new BookUuid(); + bookUuid.setTitle("All About UUIDs"); + BookUuid savedBookUuid = bookUuidRepository.save(bookUuid); + System.out.println("Saved Book UUID: " + savedBookUuid.getId()); + } +} diff --git a/src/main/java/guru/springframework/sdjpaintro/config/DbClean.java b/src/main/java/guru/springframework/sdjpaintro/config/DbClean.java new file mode 100644 index 00000000..fef30bbc --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/config/DbClean.java @@ -0,0 +1,22 @@ +package guru.springframework.sdjpaintro.config; + +import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** + * Created by jt on 8/15/21. + */ +@Profile("clean") +@Configuration +public class DbClean { + + @Bean + public FlywayMigrationStrategy clean(){ + return flyway -> { + flyway.clean(); + flyway.migrate(); + }; + } +} diff --git a/src/main/java/guru/springframework/sdjpaintro/domain/Author.java b/src/main/java/guru/springframework/sdjpaintro/domain/Author.java new file mode 100644 index 00000000..048e6eaa --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/domain/Author.java @@ -0,0 +1,58 @@ +package guru.springframework.sdjpaintro.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +/** + * Created by jt on 8/14/21. + */ +@Entity +public class Author { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String firstName; + private String lastName; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Author author = (Author) o; + + return id != null ? id.equals(author.id) : author.id == null; + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/src/main/java/guru/springframework/sdjpaintro/domain/AuthorUuid.java b/src/main/java/guru/springframework/sdjpaintro/domain/AuthorUuid.java new file mode 100644 index 00000000..b6d94964 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/domain/AuthorUuid.java @@ -0,0 +1,44 @@ +package guru.springframework.sdjpaintro.domain; + +import jakarta.persistence.*; +import org.hibernate.annotations.Type; + +import java.util.UUID; + +@Entity +public class AuthorUuid { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Type(type = "org.hibernate.type.UUIDCharType") + @Column(length = 36, columnDefinition = "varchar(36)", updatable = false, nullable = false) + private UUID id; + + private String firstName; + private String lastName; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + +} diff --git a/src/main/java/guru/springframework/sdjpaintro/domain/Book.java b/src/main/java/guru/springframework/sdjpaintro/domain/Book.java new file mode 100644 index 00000000..5ac93f31 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/domain/Book.java @@ -0,0 +1,89 @@ +package guru.springframework.sdjpaintro.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.Objects; + +/** + * Created by jt on 6/12/21. + */ +@Entity +public class Book { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + private String isbn; + private String publisher; + private Long authorId; + + public Book() { + + } + + public Book(String title, String isbn, String publisher, Long authorId) { + this.title = title; + this.isbn = isbn; + this.publisher = publisher; + this.authorId = authorId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Book book = (Book) o; + + return Objects.equals(id, book.id); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getPublisher() { + return publisher; + } + + public void setPublisher(String publisher) { + this.publisher = publisher; + } + + public Long getAuthorId() { + return authorId; + } + + public void setAuthorId(Long authorId) { + this.authorId = authorId; + } +} diff --git a/src/main/java/guru/springframework/sdjpaintro/domain/BookUuid.java b/src/main/java/guru/springframework/sdjpaintro/domain/BookUuid.java new file mode 100644 index 00000000..1497bd09 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/domain/BookUuid.java @@ -0,0 +1,60 @@ +package guru.springframework.sdjpaintro.domain; + +import jakarta.persistence.*; +import org.hibernate.annotations.GenericGenerator; + +import java.util.UUID; + +@Entity +public class BookUuid { + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") //This is how to specify UUID RFC 4122 Primary Key + @Column(columnDefinition = "BINARY(16)", updatable = false, nullable = false) + private UUID id; + + private String title; + private String isbn; + private String publisher; + private Long authorId; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getPublisher() { + return publisher; + } + + public void setPublisher(String publisher) { + this.publisher = publisher; + } + + public Long getAuthorId() { + return authorId; + } + + public void setAuthorId(Long authorId) { + this.authorId = authorId; + } +} diff --git a/src/main/java/guru/springframework/sdjpaintro/repositories/AuthorUuidRepository.java b/src/main/java/guru/springframework/sdjpaintro/repositories/AuthorUuidRepository.java new file mode 100644 index 00000000..5322eea3 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/repositories/AuthorUuidRepository.java @@ -0,0 +1,10 @@ +package guru.springframework.sdjpaintro.repositories; + +import guru.springframework.sdjpaintro.domain.AuthorUuid; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Created by EI on 29/08/22. + */ +public interface AuthorUuidRepository extends JpaRepository<AuthorUuid, Long> { +} diff --git a/src/main/java/guru/springframework/sdjpaintro/repositories/BookRepository.java b/src/main/java/guru/springframework/sdjpaintro/repositories/BookRepository.java new file mode 100644 index 00000000..ad3b6850 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/repositories/BookRepository.java @@ -0,0 +1,10 @@ +package guru.springframework.sdjpaintro.repositories; + +import guru.springframework.sdjpaintro.domain.Book; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Created by jt on 6/12/21. + */ +public interface BookRepository extends JpaRepository<Book, Long> { +} diff --git a/src/main/java/guru/springframework/sdjpaintro/repositories/BookUuidRepository.java b/src/main/java/guru/springframework/sdjpaintro/repositories/BookUuidRepository.java new file mode 100644 index 00000000..24ecfe47 --- /dev/null +++ b/src/main/java/guru/springframework/sdjpaintro/repositories/BookUuidRepository.java @@ -0,0 +1,11 @@ +package guru.springframework.sdjpaintro.repositories; + +import guru.springframework.sdjpaintro.domain.AuthorUuid; +import guru.springframework.sdjpaintro.domain.BookUuid; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Created by EI on 29/08/22. + */ +public interface BookUuidRepository extends JpaRepository<BookUuid, Long> { +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 00000000..122eccd4 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,10 @@ +spring.datasource.username=bookuser +spring.datasource.password=password +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/bookdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC + +spring.jpa.hibernate.ddl-auto=validate + +spring.sql.init.mode=always + +spring.flyway.user=bookadmin +spring.flyway.password=password \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..21205ea6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,21 @@ +#spring.jpa.show-sql=true +#Show SQL +spring.jpa.properties.hibernate.show_sql=true + +#Format SQL +spring.jpa.properties.hibernate.format_sql=true + +#Show bind values +logging.level.org.hibernate.type.descriptor.sql=trace + +spring.h2.console.enabled=true + +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database=mysql +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.defer-datasource-initialization=false +spring.flyway.locations=classpath:db/migration/common,classpath:db/migration/{vendor} \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V1__init_database.sql b/src/main/resources/db/migration/common/V1__init_database.sql new file mode 100644 index 00000000..0e1f02c5 --- /dev/null +++ b/src/main/resources/db/migration/common/V1__init_database.sql @@ -0,0 +1,16 @@ +drop table if exists book; +drop table if exists hibernate_sequence; + +create table book ( + id bigint not null, + isbn varchar(255), + publisher varchar(255), + title varchar(255), + primary key (id) +) engine=InnoDB; + +create table hibernate_sequence ( + next_val bigint +) engine=InnoDB; + +insert into hibernate_sequence values ( 1 ); \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V2__add_author.sql b/src/main/resources/db/migration/common/V2__add_author.sql new file mode 100644 index 00000000..f82a5e89 --- /dev/null +++ b/src/main/resources/db/migration/common/V2__add_author.sql @@ -0,0 +1,7 @@ +create table author +( + id bigint not null, + first_name varchar(255), + last_name varchar(255), + primary key (id) +) engine = InnoDB; \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V3__add_author_id_to_book.sql b/src/main/resources/db/migration/common/V3__add_author_id_to_book.sql new file mode 100644 index 00000000..404f3a15 --- /dev/null +++ b/src/main/resources/db/migration/common/V3__add_author_id_to_book.sql @@ -0,0 +1 @@ +alter table book ADD author_id BIGINT; \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V4__autoincrement_pk.sql b/src/main/resources/db/migration/common/V4__autoincrement_pk.sql new file mode 100644 index 00000000..54a97e57 --- /dev/null +++ b/src/main/resources/db/migration/common/V4__autoincrement_pk.sql @@ -0,0 +1,2 @@ +alter table book change id id BIGINT auto_increment; +alter table author change id id BIGINT auto_increment; \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V5__autoincrement_pk.sql b/src/main/resources/db/migration/common/V5__autoincrement_pk.sql new file mode 100644 index 00000000..44bc3715 --- /dev/null +++ b/src/main/resources/db/migration/common/V5__autoincrement_pk.sql @@ -0,0 +1,7 @@ +create table author_uuid +( + id varchar(36) not null, + first_name varchar(255), + last_name varchar(255), + primary key (id) +) engine=InnoDB; \ No newline at end of file diff --git a/src/main/resources/db/migration/common/V6__autoincrement_pk.sql b/src/main/resources/db/migration/common/V6__autoincrement_pk.sql new file mode 100644 index 00000000..44100990 --- /dev/null +++ b/src/main/resources/db/migration/common/V6__autoincrement_pk.sql @@ -0,0 +1,8 @@ +create table book_uuid +( + id binary(16) not null, + isbn varchar(255), + publisher varchar(255), + title varchar(255), + primary key (id) +) engine = InnoDB; \ No newline at end of file diff --git a/src/main/resources/db/migration/h2/V4.1__h2_auto.sql b/src/main/resources/db/migration/h2/V4.1__h2_auto.sql new file mode 100644 index 00000000..b54f96e2 --- /dev/null +++ b/src/main/resources/db/migration/h2/V4.1__h2_auto.sql @@ -0,0 +1,20 @@ +drop table if exists book; +drop table if exists author; + +create table book +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY not null, + isbn varchar(255), + publisher varchar(255), + title varchar(255), + author_id BIGINT, + primary key (id) +); + +create table author +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY not null, + first_name varchar(255), + last_name varchar(255), + primary key (id) +); \ No newline at end of file diff --git a/src/main/scripts/mysqlusers.sql b/src/main/scripts/mysqlusers.sql new file mode 100644 index 00000000..05934cac --- /dev/null +++ b/src/main/scripts/mysqlusers.sql @@ -0,0 +1,10 @@ +DROP DATABASE IF EXISTS bookdb; +DROP USER IF EXISTS `bookadmin`@`%`; +DROP USER IF EXISTS `bookuser`@`%`; +CREATE DATABASE IF NOT EXISTS bookdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER IF NOT EXISTS `bookadmin`@`%` IDENTIFIED WITH mysql_native_password BY 'password'; +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, EXECUTE, CREATE VIEW, SHOW VIEW, +CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `bookdb`.* TO `bookadmin`@`%`; +CREATE USER IF NOT EXISTS `bookuser`@`%` IDENTIFIED WITH mysql_native_password BY 'password'; +GRANT SELECT, INSERT, UPDATE, DELETE, SHOW VIEW ON `bookdb`.* TO `bookuser`@`%`; +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/src/test/java/guru/springframework/sdjpaintro/MySQLIntegrationTest.java b/src/test/java/guru/springframework/sdjpaintro/MySQLIntegrationTest.java new file mode 100644 index 00000000..77c19a6d --- /dev/null +++ b/src/test/java/guru/springframework/sdjpaintro/MySQLIntegrationTest.java @@ -0,0 +1,35 @@ +package guru.springframework.sdjpaintro; + +import guru.springframework.sdjpaintro.repositories.BookRepository; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * Created by jt on 7/4/21. + */ +@ActiveProfiles("local") +@DataJpaTest +@ComponentScan(basePackages = {"guru.springframework.sdjpaintro.bootstrap"}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class MySQLIntegrationTest { + + @Autowired + BookRepository bookRepository; + + @Test + void testMySQL() { + long countBefore = bookRepository.count(); + assertThat(countBefore).isEqualTo(2); + + } + +} + + diff --git a/src/test/java/guru/springframework/sdjpaintro/SdjpaIntroApplicationTests.java b/src/test/java/guru/springframework/sdjpaintro/SdjpaIntroApplicationTests.java index 9fbb1484..012fec60 100644 --- a/src/test/java/guru/springframework/sdjpaintro/SdjpaIntroApplicationTests.java +++ b/src/test/java/guru/springframework/sdjpaintro/SdjpaIntroApplicationTests.java @@ -1,11 +1,25 @@ package guru.springframework.sdjpaintro; +import guru.springframework.sdjpaintro.repositories.BookRepository; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + @SpringBootTest class SdjpaIntroApplicationTests { + @Autowired + BookRepository bookRepository; + + @Test + void testBookRepository() { + long count = bookRepository.count(); + + assertThat(count).isGreaterThan(0); + } + @Test void contextLoads() { } diff --git a/src/test/java/guru/springframework/sdjpaintro/SpringBootJpaTestSlice.java b/src/test/java/guru/springframework/sdjpaintro/SpringBootJpaTestSlice.java new file mode 100644 index 00000000..fe45a153 --- /dev/null +++ b/src/test/java/guru/springframework/sdjpaintro/SpringBootJpaTestSlice.java @@ -0,0 +1,51 @@ +package guru.springframework.sdjpaintro; + +import guru.springframework.sdjpaintro.domain.Book; +import guru.springframework.sdjpaintro.repositories.BookRepository; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.annotation.Commit; +import org.springframework.test.annotation.Rollback; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * Created by jt on 7/3/21. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@DataJpaTest +@ComponentScan(basePackages = {"guru.springframework.sdjpaintro.bootstrap"}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class SpringBootJpaTestSlice { + + @Autowired + BookRepository bookRepository; + + @Commit + @Order(1) + @Test + void testJpaTestSplice() { + long countBefore = bookRepository.count(); + assertThat(countBefore).isEqualTo(2); + + bookRepository.save(new Book("My Book", "1235555", "Self", null)); + + long countAfter = bookRepository.count(); + + assertThat(countBefore).isLessThan(countAfter); + } + + @Order(2) + @Test + void testJpaTestSpliceTransaction() { + long countBefore = bookRepository.count(); + assertThat(countBefore).isEqualTo(3); + + } +}