From cac3638c10fd56be2ea157fbd66b5545ef58c484 Mon Sep 17 00:00:00 2001 From: Balamurugan Date: Thu, 23 Mar 2023 01:13:31 +0000 Subject: [PATCH] BAEL-5988: Added code for Spring R2DBC flyway migration with tests (#13551) * BAEL-5988: Added code for Spring R2DBC flyway migration with tests * BAEL-5988: Removed unnecessary logging config * BAEL-5988: Addressed review comments * BAEL-5988: Removed bean loading override config for test * BAEL-5988: Removed unnecessary import * BAEL-5988: Added properties for versions in pom --------- Co-authored-by: Bala Co-authored-by: balasr3 --- persistence-modules/r2dbc/pom.xml | 36 +++++++++- .../SpringWebfluxFlywayApplication.java | 13 ++++ .../r2dbc/flyway/config/DatabaseConfig.java | 23 ++++++ .../r2dbc/flyway/model/Department.java | 40 +++++++++++ .../examples/r2dbc/flyway/model/Student.java | 55 ++++++++++++++ .../repository/DepartmentRepository.java | 10 +++ .../flyway/repository/StudentRepository.java | 10 +++ .../r2dbc/flyway/rest/DepartmentResource.java | 29 ++++++++ .../r2dbc/flyway/rest/StudentResource.java | 29 ++++++++ .../r2dbc/src/main/resources/application.yml | 9 ++- .../src/main/resources/db/docker-compose.yml | 14 ++++ .../migration/V1_1__create_tables.sql | 17 +++++ .../migration/V1_2__insert_department.sql | 3 + .../flyway/rest/StudentResourceUnitTest.java | 71 +++++++++++++++++++ .../r2dbc/src/test/resources/application.yml | 23 ++++++ .../db/h2/migration/V1_1__create_tables.sql | 19 +++++ .../h2/migration/V1_2__insert_department.sql | 3 + 17 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/SpringWebfluxFlywayApplication.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/config/DatabaseConfig.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Department.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Student.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/DepartmentRepository.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/StudentRepository.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/DepartmentResource.java create mode 100644 persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResource.java create mode 100644 persistence-modules/r2dbc/src/main/resources/db/docker-compose.yml create mode 100644 persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_1__create_tables.sql create mode 100644 persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_2__insert_department.sql create mode 100644 persistence-modules/r2dbc/src/test/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResourceUnitTest.java create mode 100644 persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_1__create_tables.sql create mode 100644 persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_2__insert_department.sql diff --git a/persistence-modules/r2dbc/pom.xml b/persistence-modules/r2dbc/pom.xml index f32b37974b..cfe344eba9 100644 --- a/persistence-modules/r2dbc/pom.xml +++ b/persistence-modules/r2dbc/pom.xml @@ -49,6 +49,34 @@ org.springframework.boot spring-boot-devtools + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + org.postgresql + r2dbc-postgresql + ${r2dbc.postgresql.version} + + + org.postgresql + postgresql + ${jdbc.postgresql.version} + + + org.flywaydb + flyway-core + ${flyway.core.version} + + + io.r2dbc + r2dbc-h2 + test + @@ -60,4 +88,10 @@ - \ No newline at end of file + + 9.14.1 + 42.5.2 + 1.0.0.RELEASE + + + diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/SpringWebfluxFlywayApplication.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/SpringWebfluxFlywayApplication.java new file mode 100644 index 0000000000..7eb3f8180c --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/SpringWebfluxFlywayApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.examples.r2dbc.flyway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringWebfluxFlywayApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringWebfluxFlywayApplication.class, args); + } + +} diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/config/DatabaseConfig.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/config/DatabaseConfig.java new file mode 100644 index 0000000000..d4a4be93e9 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/config/DatabaseConfig.java @@ -0,0 +1,23 @@ +package com.baeldung.examples.r2dbc.flyway.config; + +import org.flywaydb.core.Flyway; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({ R2dbcProperties.class, FlywayProperties.class }) +class DatabaseConfig { + @Bean(initMethod = "migrate") + public Flyway flyway(FlywayProperties flywayProperties, R2dbcProperties r2dbcProperties) { + return Flyway.configure() + .dataSource(flywayProperties.getUrl(), r2dbcProperties.getUsername(), r2dbcProperties.getPassword()) + .locations(flywayProperties.getLocations() + .stream() + .toArray(String[]::new)) + .baselineOnMigrate(true) + .load(); + } +} \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Department.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Department.java new file mode 100644 index 0000000000..cfb1555847 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Department.java @@ -0,0 +1,40 @@ +package com.baeldung.examples.r2dbc.flyway.model; + +import java.util.UUID; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; +import org.springframework.data.relational.core.mapping.Table; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Table("department") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Department implements Persistable { + + @Id + @JsonProperty("uuid") + @JsonAlias("id") + private UUID id; + + @NotNull + @Size(max = 255, message = "The property 'name' must be less than or equal to 255 characters.") + private String name; + + @Override + @JsonIgnore + public boolean isNew() { + return true; + } +} diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Student.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Student.java new file mode 100644 index 0000000000..94dbe6e97f --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/model/Student.java @@ -0,0 +1,55 @@ +package com.baeldung.examples.r2dbc.flyway.model; + +import java.time.LocalDate; +import java.util.UUID; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Persistable; +import org.springframework.data.relational.core.mapping.Table; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Table("student") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Student implements Persistable { + + @Id + @JsonProperty("uuid") + @JsonAlias("id") + private UUID id; + + @NotNull + @Size(max = 255, message = "The property 'firstName' must be less than or equal to 255 characters.") + private String firstName; + + @NotNull + @Size(max = 255, message = "The property 'lastName' must be less than or equal to 255 characters.") + private String lastName; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate dateOfBirth; + + @NotNull + private UUID department; + + @Override + @JsonIgnore + public boolean isNew() { + return true; + } +} + diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/DepartmentRepository.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/DepartmentRepository.java new file mode 100644 index 0000000000..85edf964b7 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/DepartmentRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.examples.r2dbc.flyway.repository; + +import java.util.UUID; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +import com.baeldung.examples.r2dbc.flyway.model.Department; + +public interface DepartmentRepository extends ReactiveCrudRepository { +} diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/StudentRepository.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/StudentRepository.java new file mode 100644 index 0000000000..1fc3ae1641 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/repository/StudentRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.examples.r2dbc.flyway.repository; + +import java.util.UUID; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +import com.baeldung.examples.r2dbc.flyway.model.Student; + +public interface StudentRepository extends ReactiveCrudRepository { +} diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/DepartmentResource.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/DepartmentResource.java new file mode 100644 index 0000000000..32c3d5bbd8 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/DepartmentResource.java @@ -0,0 +1,29 @@ +package com.baeldung.examples.r2dbc.flyway.rest; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.examples.r2dbc.flyway.model.Department; +import com.baeldung.examples.r2dbc.flyway.repository.DepartmentRepository; + +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Mono; + +@RestController +@RequiredArgsConstructor +public class DepartmentResource { + + private final DepartmentRepository departmentRepository; + + @GetMapping(path = "/department") + public Mono>> getDepartments() { + return departmentRepository.findAll() + .collectList() + .map(departments -> new ResponseEntity(departments, HttpStatus.OK)); + } + +} diff --git a/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResource.java b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResource.java new file mode 100644 index 0000000000..624f673406 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResource.java @@ -0,0 +1,29 @@ +package com.baeldung.examples.r2dbc.flyway.rest; + +import javax.validation.Valid; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.examples.r2dbc.flyway.model.Student; +import com.baeldung.examples.r2dbc.flyway.repository.StudentRepository; + +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Mono; + +@RestController +@RequiredArgsConstructor +public class StudentResource { + + private final StudentRepository studentRepository; + + @PostMapping(path = "/student") + public Mono> createStudent(@RequestBody @Valid Mono createStudentRequest) { + return createStudentRequest.flatMap(studentRepository::save) + .map(student -> new ResponseEntity(student, HttpStatus.CREATED)); + } + +} diff --git a/persistence-modules/r2dbc/src/main/resources/application.yml b/persistence-modules/r2dbc/src/main/resources/application.yml index bb47c7261c..919a088209 100644 --- a/persistence-modules/r2dbc/src/main/resources/application.yml +++ b/persistence-modules/r2dbc/src/main/resources/application.yml @@ -1,7 +1,14 @@ spring: application: name: r2dbc-test - + + r2dbc: + username: local + password: local + url: r2dbc:postgresql://localhost:8082/flyway-test-db + flyway: + url: jdbc:postgresql://localhost:8082/flyway-test-db + locations: classpath:db/postgres/migration # R2DBC URL r2dbc: url: r2dbc:h2:mem://./testdb diff --git a/persistence-modules/r2dbc/src/main/resources/db/docker-compose.yml b/persistence-modules/r2dbc/src/main/resources/db/docker-compose.yml new file mode 100644 index 0000000000..5b95899e4b --- /dev/null +++ b/persistence-modules/r2dbc/src/main/resources/db/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.9' +networks: + obref: +services: + postgres_db_service: + container_name: postgres_db_service + image: postgres:11 + ports: + - "8082:5432" + hostname: postgres_db_service + environment: + - POSTGRES_PASSWORD=local + - POSTGRES_USER=local + - POSTGRES_DB=flyway-test-db \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_1__create_tables.sql b/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_1__create_tables.sql new file mode 100644 index 0000000000..23ecd2ca59 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_1__create_tables.sql @@ -0,0 +1,17 @@ + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE IF NOT EXISTS department +( + id uuid PRIMARY KEY UNIQUE DEFAULT uuid_generate_v4(), + name varchar(255) +); + +CREATE TABLE IF NOT EXISTS student +( + id uuid PRIMARY KEY UNIQUE DEFAULT uuid_generate_v4(), + first_name varchar(255), + last_name varchar(255), + date_of_birth DATE NOT NULL, + department uuid NOT NULL CONSTRAINT student_foreign_key1 REFERENCES department (id) +); \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_2__insert_department.sql b/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_2__insert_department.sql new file mode 100644 index 0000000000..62fa654d57 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/resources/db/postgres/migration/V1_2__insert_department.sql @@ -0,0 +1,3 @@ + +insert into department(name) values ('Computer Science'); +insert into department(name) values ('Biomedical'); \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/test/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResourceUnitTest.java b/persistence-modules/r2dbc/src/test/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResourceUnitTest.java new file mode 100644 index 0000000000..455b6862f8 --- /dev/null +++ b/persistence-modules/r2dbc/src/test/java/com/baeldung/examples/r2dbc/flyway/rest/StudentResourceUnitTest.java @@ -0,0 +1,71 @@ +package com.baeldung.examples.r2dbc.flyway.rest; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; + +import com.baeldung.examples.r2dbc.flyway.model.Department; +import com.baeldung.examples.r2dbc.flyway.model.Student; + +import reactor.core.publisher.Mono; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +class StudentResourceUnitTest { + + private static final String DEPARTMENT_ENDPOINT = "/department"; + private static final String STUDENT_ENDPOINT = "/student"; + + @Autowired + protected WebTestClient webTestClient; + + @Test + void givenDepartmentExists_WhenCreateStudentRequestIsSent_ShouldBeProcessedSuccessfully() { + + // Given + List departmentList = webTestClient.get() + .uri(DEPARTMENT_ENDPOINT) + .exchange() + .expectStatus() + .isOk() + .expectBodyList(Department.class) + .returnResult() + .getResponseBody(); + + Assertions.assertNotNull(departmentList); + + // When + Student student = webTestClient.post() + .uri(STUDENT_ENDPOINT) + .body(BodyInserters.fromPublisher(Mono.just(Student.builder() + .firstName("John") + .lastName("Doe") + .dateOfBirth(LocalDate.of(2015, 12, 1)) + .department(departmentList.get(0) + .getId()) + .build()), Student.class)) + .exchange() + .expectStatus() + .isEqualTo(201) + .expectBody(Student.class) + .returnResult() + .getResponseBody(); + + // Then + Assertions.assertNotNull(student.getId()); + Assertions.assertEquals("John", student.getFirstName()); + Assertions.assertEquals("Doe", student.getLastName()); + Assertions.assertEquals(LocalDate.of(2015, 12, 1), student.getDateOfBirth()); + } + +} \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/test/resources/application.yml b/persistence-modules/r2dbc/src/test/resources/application.yml index 8925116e4a..0903bed6fb 100644 --- a/persistence-modules/r2dbc/src/test/resources/application.yml +++ b/persistence-modules/r2dbc/src/test/resources/application.yml @@ -1,6 +1,29 @@ # R2DBC Test configuration r2dbc: url: r2dbc:h2:mem://./testdb + +server: + port: 8080 + +spring: + r2dbc: + host: localhost + port: 8082 + database: testdb + username: local + password: local + + h2: + console: + enabled: true + path: h2-console + + + flyway: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false; + user: local + password: local + locations: classpath:db/h2/migration diff --git a/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_1__create_tables.sql b/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_1__create_tables.sql new file mode 100644 index 0000000000..388b23fcf5 --- /dev/null +++ b/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_1__create_tables.sql @@ -0,0 +1,19 @@ + +CREATE TABLE department +( + id uuid DEFAULT random_uuid() PRIMARY KEY UNIQUE NOT NULL, + name varchar(255) +); + +CREATE TABLE student +( + id uuid DEFAULT random_uuid() UNIQUE NOT NULL, + first_name varchar(255), + last_name varchar(255), + date_of_birth DATE NOT NULL, + department uuid NOT NULL +); + + +ALTER TABLE student + ADD FOREIGN KEY (department) REFERENCES department(id); \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_2__insert_department.sql b/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_2__insert_department.sql new file mode 100644 index 0000000000..62fa654d57 --- /dev/null +++ b/persistence-modules/r2dbc/src/test/resources/db/h2/migration/V1_2__insert_department.sql @@ -0,0 +1,3 @@ + +insert into department(name) values ('Computer Science'); +insert into department(name) values ('Biomedical'); \ No newline at end of file