From 5afc8021d0e21524968fe0397257d7b60f8c73dc Mon Sep 17 00:00:00 2001 From: Robin Wilkins Date: Tue, 10 Jan 2023 12:12:58 +0000 Subject: [PATCH] Add Jooq database integration Add h2 database with a table containing basic person details. Database integration with the java code and an http endpoint is added. --- build.gradle | 3 ++ .../showcase/controller/JsonPerson.java | 12 +++++ .../showcase/controller/PersonController.java | 26 ++++++++++ .../com/wilkins/showcase/domain/Person.java | 7 +++ .../service/DefaultPersonService.java | 14 ++++++ .../service/JooqPersonRepository.java | 47 +++++++++++++++++++ .../showcase/service/PersonRepository.java | 9 ++++ .../showcase/service/PersonService.java | 9 ++++ src/main/resources/application.yml | 8 ++++ .../resources/db/migration/V0__add_person.sql | 12 +++++ .../controller/GreetingControllerTest.java | 2 +- .../controller/PersonControllerTest.java | 39 +++++++++++++++ .../service/JooqPersonRepositoryTest.java | 22 +++++++++ 13 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/wilkins/showcase/controller/JsonPerson.java create mode 100644 src/main/java/com/wilkins/showcase/controller/PersonController.java create mode 100644 src/main/java/com/wilkins/showcase/domain/Person.java create mode 100644 src/main/java/com/wilkins/showcase/service/DefaultPersonService.java create mode 100644 src/main/java/com/wilkins/showcase/service/JooqPersonRepository.java create mode 100644 src/main/java/com/wilkins/showcase/service/PersonRepository.java create mode 100644 src/main/java/com/wilkins/showcase/service/PersonService.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/migration/V0__add_person.sql create mode 100644 src/test/java/com/wilkins/showcase/controller/PersonControllerTest.java create mode 100644 src/test/java/com/wilkins/showcase/service/JooqPersonRepositoryTest.java diff --git a/build.gradle b/build.gradle index d93539e..4df5d14 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,9 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation('org.springframework.boot:spring-boot-starter-test') + implementation("org.springframework.boot:spring-boot-starter-jooq") + implementation('org.flywaydb:flyway-core') + runtimeOnly('com.h2database:h2') } test { diff --git a/src/main/java/com/wilkins/showcase/controller/JsonPerson.java b/src/main/java/com/wilkins/showcase/controller/JsonPerson.java new file mode 100644 index 0000000..255cf7b --- /dev/null +++ b/src/main/java/com/wilkins/showcase/controller/JsonPerson.java @@ -0,0 +1,12 @@ +package com.wilkins.showcase.controller; + +import com.wilkins.showcase.domain.Person; + +public record JsonPerson(String id, + String title, + String forename, + String surname) { + public static JsonPerson from(Person person) { + return new JsonPerson(person.id(), person.title(), person.forename(), person.surname()); + } +} diff --git a/src/main/java/com/wilkins/showcase/controller/PersonController.java b/src/main/java/com/wilkins/showcase/controller/PersonController.java new file mode 100644 index 0000000..85db3d6 --- /dev/null +++ b/src/main/java/com/wilkins/showcase/controller/PersonController.java @@ -0,0 +1,26 @@ +package com.wilkins.showcase.controller; + +import com.wilkins.showcase.service.PersonService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/persons") +public class PersonController { + private final PersonService personService; + + public PersonController(PersonService personService) { + this.personService = personService; + } + + @GetMapping + public List getPersons() { + return personService.findAll() + .stream() + .map(JsonPerson::from) + .toList(); + } +} diff --git a/src/main/java/com/wilkins/showcase/domain/Person.java b/src/main/java/com/wilkins/showcase/domain/Person.java new file mode 100644 index 0000000..97d4876 --- /dev/null +++ b/src/main/java/com/wilkins/showcase/domain/Person.java @@ -0,0 +1,7 @@ +package com.wilkins.showcase.domain; + +public record Person(String id, + String title, + String forename, + String surname) { +} diff --git a/src/main/java/com/wilkins/showcase/service/DefaultPersonService.java b/src/main/java/com/wilkins/showcase/service/DefaultPersonService.java new file mode 100644 index 0000000..838ae31 --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/DefaultPersonService.java @@ -0,0 +1,14 @@ +package com.wilkins.showcase.service; + +import com.wilkins.showcase.domain.Person; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public record DefaultPersonService(PersonRepository personRepository) implements PersonService{ + @Override + public List findAll() { + return personRepository.findAll(); + } +} diff --git a/src/main/java/com/wilkins/showcase/service/JooqPersonRepository.java b/src/main/java/com/wilkins/showcase/service/JooqPersonRepository.java new file mode 100644 index 0000000..f5b1f9b --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/JooqPersonRepository.java @@ -0,0 +1,47 @@ +package com.wilkins.showcase.service; + +import com.wilkins.showcase.domain.Person; +import org.jooq.*; +import org.jooq.Record; +import org.jooq.impl.DSL; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.List; + +import static org.jooq.impl.DSL.field; + +@Service +public class JooqPersonRepository implements PersonRepository { + + private final DSLContext dsl; + + public JooqPersonRepository(DataSource dataSource) throws SQLException { + this.dsl = DSL.using(dataSource.getConnection()); + } + + @Override + public List findAll() { + return dsl.select(personFields) + .from(personTable) + .fetch(asPerson()); + } + + private static RecordMapper asPerson() { + return r -> new Person(r.get(externalId), + r.get(title), + r.get(forename), + r.get(surname)); + } + + private static final Table personTable = DSL.table("person"); + private static final Field externalId = field("person.external_id", String.class); + private static final Field title = field("person.title", String.class); + private static final Field forename = field("person.forename", String.class); + private static final Field surname = field("person.surname", String.class); + private static final Field[] personFields = { + externalId, title, forename, surname + }; + +} diff --git a/src/main/java/com/wilkins/showcase/service/PersonRepository.java b/src/main/java/com/wilkins/showcase/service/PersonRepository.java new file mode 100644 index 0000000..e1af1da --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/PersonRepository.java @@ -0,0 +1,9 @@ +package com.wilkins.showcase.service; + +import com.wilkins.showcase.domain.Person; + +import java.util.List; + +public interface PersonRepository { + List findAll(); +} diff --git a/src/main/java/com/wilkins/showcase/service/PersonService.java b/src/main/java/com/wilkins/showcase/service/PersonService.java new file mode 100644 index 0000000..5940010 --- /dev/null +++ b/src/main/java/com/wilkins/showcase/service/PersonService.java @@ -0,0 +1,9 @@ +package com.wilkins.showcase.service; + +import com.wilkins.showcase.domain.Person; + +import java.util.List; + +public interface PersonService { + List findAll(); +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..9a99bf6 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,8 @@ +spring: + h2: + console: + enabled: true + datasource: + url: jdbc:h2:mem:showcase + username: showcase + password: Passw0rd! diff --git a/src/main/resources/db/migration/V0__add_person.sql b/src/main/resources/db/migration/V0__add_person.sql new file mode 100644 index 0000000..53a3ea6 --- /dev/null +++ b/src/main/resources/db/migration/V0__add_person.sql @@ -0,0 +1,12 @@ +create table person( + id bigint not null auto_increment, + external_id varchar(120) not null, + title varchar(120), + forename varchar(120), + surname varchar(120), + constraint person_pk primary key (id), + constraint person_external_id unique (external_id) +); + +insert into person(external_id, title, forename, surname) +values ( 'system-user', null, 'system', 'user' ); diff --git a/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java b/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java index 49b6a05..6e6ac92 100644 --- a/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java +++ b/src/test/java/com/wilkins/showcase/controller/GreetingControllerTest.java @@ -10,7 +10,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest +@WebMvcTest(controllers = GreetingController.class) public class GreetingControllerTest { @Autowired diff --git a/src/test/java/com/wilkins/showcase/controller/PersonControllerTest.java b/src/test/java/com/wilkins/showcase/controller/PersonControllerTest.java new file mode 100644 index 0000000..4adcf76 --- /dev/null +++ b/src/test/java/com/wilkins/showcase/controller/PersonControllerTest.java @@ -0,0 +1,39 @@ +package com.wilkins.showcase.controller; + +import com.wilkins.showcase.domain.Person; +import com.wilkins.showcase.service.PersonService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = PersonController.class) +public class PersonControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + PersonService personService; + + @Test + void canGetPersons() throws Exception { + var person1 = new Person("ms-lisa-simpson", "Ms", "Lisa", "Simpson"); + when(personService.findAll()).thenReturn(List.of(person1)); + mockMvc.perform(get("/persons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", is("ms-lisa-simpson"))) + .andExpect(jsonPath("$[0].title", is("Ms"))) + .andExpect(jsonPath("$[0].forename", is("Lisa"))) + .andExpect(jsonPath("$[0].surname", is("Simpson"))); + } +} diff --git a/src/test/java/com/wilkins/showcase/service/JooqPersonRepositoryTest.java b/src/test/java/com/wilkins/showcase/service/JooqPersonRepositoryTest.java new file mode 100644 index 0000000..2afb15c --- /dev/null +++ b/src/test/java/com/wilkins/showcase/service/JooqPersonRepositoryTest.java @@ -0,0 +1,22 @@ +package com.wilkins.showcase.service; + +import com.wilkins.showcase.domain.Person; +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.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.tuple; + +@SpringBootTest +public class JooqPersonRepositoryTest { + @Autowired + JooqPersonRepository underTest; + + @Test + void canFindPersons() { + assertThat(underTest.findAll()) + .extracting(Person::id, Person::title, Person::forename, Person::surname) + .contains(tuple("system-user", null, "system", "user")); + } +}