Skip to content

Commit ca73214

Browse files
committed
Add Flyway and JDBC backed repo
1 parent 164cdd2 commit ca73214

7 files changed

Lines changed: 139 additions & 14 deletions

File tree

build.gradle

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id "java"
33
id "org.springframework.boot" version "1.5.4.RELEASE"
4+
id "org.flywaydb.flyway" version "5.1.4"
45
}
56

67
repositories {
@@ -10,13 +11,33 @@ repositories {
1011
dependencies {
1112
compile("org.springframework.boot:spring-boot-starter-web")
1213
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.1")
14+
compile("org.springframework.boot:spring-boot-starter-jdbc")
15+
16+
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
17+
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.12'
18+
1319
testCompile("org.springframework.boot:spring-boot-starter-test")
1420
}
1521

22+
def developmentDbUrl = "jdbc:mysql://localhost:3306/tracker_dev?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false"
1623
bootRun.environment([
1724
"WELCOME_MESSAGE": "hello",
25+
"SPRING_DATASOURCE_URL": developmentDbUrl,
1826
])
1927

28+
def testDbUrl = "jdbc:mysql://localhost:3306/tracker_test?user=tracker&useSSL=false&useTimezone=true&serverTimezone=UTC&useLegacyDatetimeCode=false"
2029
test.environment([
2130
"WELCOME_MESSAGE": "Hello from test",
31+
"SPRING_DATASOURCE_URL": testDbUrl,
2232
])
33+
34+
flyway {
35+
url = developmentDbUrl
36+
user = "tracker"
37+
password = ""
38+
locations = ["filesystem:databases/tracker/migrations"]
39+
}
40+
41+
task testMigrate(type: org.flywaydb.gradle.task.FlywayMigrateTask) {
42+
url = testDbUrl
43+
}

src/main/java/io/pivotal/pal/tracker/InMemoryTimeEntryRepository.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,9 @@ public TimeEntry update(Long id, TimeEntry updatedTimeEntry) {
3232
}
3333

3434
@Override
35-
public Long delete(Long id)
35+
public void delete(Long id)
3636
{
37-
Long returnValue = null;
38-
39-
if (TIME_ENTRY_MAP.keySet().contains(id)) {
40-
TIME_ENTRY_MAP.remove(id);
41-
returnValue = id;
42-
}
43-
44-
return returnValue;
37+
TIME_ENTRY_MAP.remove(id);
4538
}
4639

4740
@Override
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package io.pivotal.pal.tracker;
2+
3+
import org.springframework.jdbc.core.JdbcTemplate;
4+
import org.springframework.jdbc.core.ResultSetExtractor;
5+
import org.springframework.jdbc.core.RowMapper;
6+
import org.springframework.jdbc.support.GeneratedKeyHolder;
7+
8+
import javax.sql.DataSource;
9+
import java.sql.Date;
10+
import java.sql.PreparedStatement;
11+
import java.sql.Statement;
12+
import java.util.Collections;
13+
import java.util.List;
14+
15+
import static java.sql.Statement.RETURN_GENERATED_KEYS;
16+
17+
public class JdbcTimeEntryRepository implements TimeEntryRepository {
18+
19+
private JdbcTemplate jdbcTemplate;
20+
21+
private final RowMapper<TimeEntry> mapper = (rs, rowNum) -> new TimeEntry(
22+
rs.getLong("id"),
23+
rs.getLong("project_id"),
24+
rs.getLong("user_id"),
25+
rs.getDate("date").toLocalDate(),
26+
rs.getInt("hours")
27+
);
28+
29+
private final ResultSetExtractor<TimeEntry> extractor =
30+
(rs) -> rs.next() ? mapper.mapRow(rs, 1) : null;
31+
32+
public JdbcTimeEntryRepository(DataSource dataSource) {
33+
this.jdbcTemplate = new JdbcTemplate(dataSource);
34+
}
35+
36+
@Override
37+
public TimeEntry create(TimeEntry timeEntry) {
38+
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
39+
jdbcTemplate.update(con -> {
40+
41+
PreparedStatement statement = con.prepareStatement(
42+
43+
"INSERT INTO time_entries (project_id, user_id, date, hours) " +
44+
"VALUES (?, ?, ?, ?)",
45+
RETURN_GENERATED_KEYS
46+
);
47+
48+
statement.setLong(1, timeEntry.getProjectId());
49+
statement.setLong(2, timeEntry.getUserId());
50+
statement.setDate(3, Date.valueOf(timeEntry.getDate()));
51+
statement.setInt(4, timeEntry.getHours());
52+
53+
return statement;
54+
}, keyHolder);
55+
56+
return find(keyHolder.getKey().longValue());
57+
}
58+
59+
@Override
60+
public TimeEntry find(Long id) {
61+
return jdbcTemplate.query(
62+
"SELECT id, project_id, user_id, date, hours FROM time_entries WHERE id = ?",
63+
new Object[]{id},
64+
extractor);
65+
}
66+
67+
@Override
68+
public List<TimeEntry> list() {
69+
return jdbcTemplate.query("SELECT id, project_id, user_id, date, hours FROM time_entries", mapper);
70+
}
71+
72+
@Override
73+
public TimeEntry update(Long id, TimeEntry timeEntry) {
74+
jdbcTemplate.update("UPDATE time_entries " +
75+
"SET project_id = ?, user_id = ?, date = ?, hours = ? " +
76+
"WHERE id = ?",
77+
timeEntry.getProjectId(),
78+
timeEntry.getUserId(),
79+
Date.valueOf(timeEntry.getDate()),
80+
timeEntry.getHours(),
81+
id);
82+
83+
return find(id);
84+
}
85+
86+
@Override
87+
public void delete(Long id) {
88+
jdbcTemplate.update("DELETE FROM time_entries WHERE id = ?", id);
89+
}
90+
}

src/main/java/io/pivotal/pal/tracker/PalTrackerApplication.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.fasterxml.jackson.databind.SerializationFeature;
66
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
7+
import com.mysql.cj.jdbc.MysqlDataSource;
78
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.beans.factory.annotation.Value;
810
import org.springframework.boot.SpringApplication;
911
import org.springframework.boot.autoconfigure.SpringBootApplication;
1012
import org.springframework.context.annotation.Bean;
1113
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
1214

15+
import javax.sql.DataSource;
16+
1317
@SpringBootApplication
1418
public class PalTrackerApplication {
1519

@@ -18,8 +22,10 @@ public static void main(String[] args) {
1822
}
1923

2024
@Bean
21-
TimeEntryRepository timeEntryRepository() {
22-
return new InMemoryTimeEntryRepository();
25+
TimeEntryRepository timeEntryRepository(@Value("${SPRING_DATASOURCE_URL}") String dataSourceUrl) {
26+
DataSource dataSource = new MysqlDataSource();
27+
((MysqlDataSource) dataSource).setUrl(dataSourceUrl);
28+
return new JdbcTimeEntryRepository(dataSource);
2329
}
2430

2531
@Bean
@@ -30,5 +36,4 @@ public ObjectMapper jsonObjectMapper() {
3036
.modules(new JavaTimeModule())
3137
.build();
3238
}
33-
3439
}

src/main/java/io/pivotal/pal/tracker/TimeEntryController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public ResponseEntity<TimeEntry> update(@PathVariable("id") Long id, @RequestBod
4040
@DeleteMapping("/{id}")
4141
@ResponseStatus(HttpStatus.NO_CONTENT)
4242
public ResponseEntity<TimeEntry> delete(@PathVariable("id") Long id) {
43-
return new ResponseEntity<TimeEntry>(timeEntryRepository.delete(id) == null ? HttpStatus.NOT_FOUND : HttpStatus.NO_CONTENT);
43+
timeEntryRepository.delete(id);
44+
return ResponseEntity.noContent().build();
4445
}
4546

4647
@GetMapping

src/main/java/io/pivotal/pal/tracker/TimeEntryRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
public interface TimeEntryRepository {
66
TimeEntry create(TimeEntry timeEntry);
77
TimeEntry update(Long id, TimeEntry expectedTimeEntry);
8-
Long delete(Long id);
8+
void delete(Long id);
99
List<TimeEntry> list();
1010
TimeEntry find(Long id);
1111
}

src/test/java/test/pivotal/pal/trackerapi/TimeEntryApiTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package test.pivotal.pal.trackerapi;
22

33
import com.jayway.jsonpath.DocumentContext;
4+
import com.mysql.cj.jdbc.MysqlDataSource;
45
import io.pivotal.pal.tracker.PalTrackerApplication;
56
import io.pivotal.pal.tracker.TimeEntry;
7+
import org.junit.Before;
68
import org.junit.Test;
79
import org.junit.runner.RunWith;
810
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,10 +14,12 @@
1214
import org.springframework.http.HttpMethod;
1315
import org.springframework.http.HttpStatus;
1416
import org.springframework.http.ResponseEntity;
17+
import org.springframework.jdbc.core.JdbcTemplate;
1518
import org.springframework.test.context.junit4.SpringRunner;
1619

1720
import java.time.LocalDate;
1821
import java.util.Collection;
22+
import java.util.TimeZone;
1923

2024
import static com.jayway.jsonpath.JsonPath.parse;
2125
import static org.assertj.core.api.Assertions.assertThat;
@@ -30,6 +34,17 @@ public class TimeEntryApiTest {
3034

3135
private TimeEntry timeEntry = new TimeEntry(123L, 456L, LocalDate.parse("2017-01-08"), 8);
3236

37+
@Before
38+
public void setUp() throws Exception {
39+
MysqlDataSource dataSource = new MysqlDataSource();
40+
dataSource.setUrl(System.getenv("SPRING_DATASOURCE_URL"));
41+
42+
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
43+
jdbcTemplate.execute("TRUNCATE time_entries");
44+
45+
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
46+
}
47+
3348
@Test
3449
public void testCreate() throws Exception {
3550
ResponseEntity<String> createResponse = restTemplate.postForEntity("/time-entries", timeEntry, String.class);

0 commit comments

Comments
 (0)