├── .gitignore
├── README.md
├── pom.xml
├── sql
└── TestData.sql
└── src
├── main
├── java
│ └── com
│ │ └── ill
│ │ └── test
│ │ └── sqltx
│ │ ├── SqlrxApplication.java
│ │ ├── controller
│ │ └── AgentController.java
│ │ ├── repository
│ │ ├── AgentRepository.java
│ │ └── AgentRow.java
│ │ └── service
│ │ └── AgentService.java
└── resources
│ └── application.properties
└── test
├── java
└── com
│ └── ill
│ └── test
│ └── sqltx
│ ├── SqlrxApplicationTests.java
│ ├── controller
│ └── AgentControllerTest.java
│ └── repository
│ └── AgentRepositoryTest.java
└── resources
└── example.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | HELP.md
3 | target/
4 | !.mvn/wrapper/maven-wrapper.jar
5 | !**/src/main/**/target/
6 | !**/src/test/**/target/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 |
17 | ### IntelliJ IDEA ###
18 | .idea
19 | *.iws
20 | *.iml
21 | *.ipr
22 |
23 | ### NetBeans ###
24 | /nbproject/private/
25 | /nbbuild/
26 | /dist/
27 | /nbdist/
28 | /.nb-gradle/
29 | build/
30 | !**/src/main/**/build/
31 | !**/src/test/**/build/
32 |
33 | ### VS Code ###
34 | .vscode/
35 | mvnw
36 | mvnw.cmd
37 | commitfix.sh
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SQL RX
2 |
3 | A working Java project that uses Spring Boot and R2DBC to return data reactively from a MySQL 8 database. Configurable with application properties and with build-time tests.
4 |
5 | Read more about this project at [Medium.com](https://robinedwardellis.medium.com/reactive-mysql-with-spring-boot-1b184b9ea58a)
6 |
7 | ## Test data
8 |
9 | There is a SQL script in sql folder that will drop the schema if it exists and create it again. Remember to apply a user to the schema and update the username and password in application.properties
10 |
11 | Test data is the agent list from the static data dump of EVE Online, May 2022.
12 |
13 | ## Building
14 |
15 | `mvn clean install`
16 |
17 | ## Endpoints
18 |
19 | - `/agents` - get all agents
20 | - `/agents/101` - get an agent by ID (range is 3008416 to 3019501)
21 | - `/agents/ids` - get IDs for all agents (integer)
22 | - `/agents/corp/id` - get agents for the given corporation ID (range is 1000002 to 1000182)
23 | - `/agents/location/id` - get agents for the given location ID (range is 60000004 to 60015146)
24 |
25 | ### Examples
26 |
27 | `curl -i http://localhost:8080/agents` - get all agents
28 |
29 | `curl -i http://localhost:8080/agents/3019483` - get a single agent
30 |
31 | `curl -i http://localhost:8080/agents/101` - agent not found
32 |
33 |
34 | ## CCP hf. Copyright Notice
35 |
36 | EVE Online, the EVE logo, EVE and all associated logos and designs are the intellectual property of CCP hf. All artwork, screenshots, characters, vehicles, storylines, world facts or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of CCP hf. EVE Online and the EVE logo are the registered trademarks of CCP hf. All rights are reserved worldwide. All other trademarks are the property of their respective owners. CCP hf. has granted permission to Robin Ellis to use EVE Online and all associated logos and designs for promotional and information purposes but does not endorse, and is not in any way affiliated with, Robin Ellis or this project. CCP is in no way responsible for the content on or functioning of this project, nor can it be liable for any damage arising from the use of this project.
37 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 3.1.5
10 |
11 |
12 |
13 | com.ill.test
14 | sqlrx
15 | 0.0.1-SNAPSHOT
16 | sqlrx
17 | MySQL Rx Test
18 |
19 |
20 | UTF-8
21 | UTF-8
22 | 17
23 |
24 | 1.0.1
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-webflux
31 |
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-data-r2dbc
36 |
37 |
38 |
39 | io.asyncer
40 | r2dbc-mysql
41 | ${mysql-driver.version}
42 |
43 |
44 |
45 | org.projectlombok
46 | lombok
47 | true
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-devtools
53 | runtime
54 | true
55 |
56 |
57 |
58 | org.springframework.boot
59 | spring-boot-starter-test
60 | test
61 |
62 |
63 |
64 | io.projectreactor
65 | reactor-test
66 | test
67 |
68 |
69 |
70 |
71 | clean package
72 |
73 |
74 |
75 | org.apache.maven.plugins
76 | maven-enforcer-plugin
77 |
78 |
79 | enforce-maven
80 |
81 | enforce
82 |
83 |
84 |
85 |
86 |
87 | 3.6.3
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.apache.maven.plugins
97 | maven-compiler-plugin
98 |
99 | ${project.build.sourceEncoding}
100 | ${java.version}
101 | ${java.version}
102 | -Xlint:all
103 | true
104 | true
105 |
106 |
107 |
108 |
109 | org.springframework.boot
110 | spring-boot-maven-plugin
111 |
112 |
113 |
114 | org.projectlombok
115 | lombok
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/main/java/com/ill/test/sqltx/SqlrxApplication.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SqlrxApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(SqlrxApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/ill/test/sqltx/controller/AgentController.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.controller;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import com.ill.test.sqltx.repository.AgentRow;
11 | import com.ill.test.sqltx.service.AgentService;
12 | import reactor.core.publisher.Flux;
13 | import reactor.core.publisher.Mono;
14 |
15 | @RestController
16 | @RequestMapping("/agents")
17 | public class AgentController {
18 |
19 | @Autowired
20 | private AgentService service;
21 |
22 | @GetMapping("")
23 | public Flux getAll() {
24 | return service.getAll();
25 | }
26 |
27 | @GetMapping("/ids")
28 | public Flux getAllIds() {
29 | return service.getAll().map(AgentRow::getAgentId);
30 | }
31 |
32 | @GetMapping("/corp/{corpId}")
33 | public Flux getForCorp(@PathVariable int corpId) {
34 | return service.getForCorp(corpId);
35 | }
36 |
37 | @GetMapping("/location/{locationId}")
38 | public Flux getForLocation(@PathVariable int locationId) {
39 | return service.getForLocation(locationId);
40 | }
41 |
42 | @GetMapping("/{agentId}")
43 | public Mono> getAgent(@PathVariable int agentId) {
44 | return service
45 | .getAgent(agentId)
46 | .map(agent -> new ResponseEntity<>(agent, HttpStatus.OK))
47 | .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/ill/test/sqltx/repository/AgentRepository.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.repository;
2 |
3 | import org.springframework.data.r2dbc.repository.R2dbcRepository;
4 | import reactor.core.publisher.Flux;
5 |
6 | public interface AgentRepository extends R2dbcRepository {
7 |
8 | Flux findAllByCorporationId(int corpId);
9 |
10 | Flux findAllByLocationId(int locationId);
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/ill/test/sqltx/repository/AgentRow.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.repository;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.relational.core.mapping.Column;
5 | import org.springframework.data.relational.core.mapping.Table;
6 |
7 | import lombok.Data;
8 |
9 | @Table("agtAgents")
10 | @Data
11 | public class AgentRow {
12 |
13 | @Id
14 | @Column("agentID")
15 | private Integer agentId;
16 |
17 | @Column("divisionID")
18 | private int divisionId;
19 |
20 | @Column("corporationID")
21 | private int corporationId;
22 |
23 | @Column("locationID")
24 | private int locationId;
25 |
26 | @Column("level")
27 | private int level;
28 |
29 | @Column("agentTypeID")
30 | private int agentTypeId;
31 |
32 | @Column("isLocator")
33 | private boolean locator;
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/ill/test/sqltx/service/AgentService.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.service;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Service;
5 |
6 | import com.ill.test.sqltx.repository.AgentRepository;
7 | import com.ill.test.sqltx.repository.AgentRow;
8 |
9 | import reactor.core.publisher.Flux;
10 | import reactor.core.publisher.Mono;
11 |
12 | @Service
13 | public class AgentService {
14 |
15 | @Autowired
16 | private AgentRepository repo;
17 |
18 | public Flux getAll() {
19 | return repo.findAll();
20 | }
21 |
22 | public Flux getForCorp(int corpId) {
23 | return repo.findAllByCorporationId(corpId);
24 | }
25 |
26 | public Flux getForLocation(int locationId) {
27 | return repo.findAllByLocationId(locationId);
28 | }
29 |
30 | public Mono getAgent(int agentId) {
31 | return repo.findById(agentId);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # dev properties
2 | spring.r2dbc.url=r2dbc:pool:mysql://localhost:3306/SQL_RX_TEST?zeroDateTimeBehavior=convertToNull&useSSL=false&useServerPrepareStatement=true
3 | spring.r2dbc.username=sqlrx
4 | spring.r2dbc.password=sqlrx
5 |
6 | logging.level.org.springframework.data.repository=DEBUG
7 | logging.level.org.springframework.r2dbc.core=DEBUG
8 |
--------------------------------------------------------------------------------
/src/test/java/com/ill/test/sqltx/SqlrxApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SqlrxApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/ill/test/sqltx/controller/AgentControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.controller;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
6 | import org.springframework.boot.test.mock.mockito.MockBean;
7 | import org.springframework.test.web.reactive.server.WebTestClient;
8 | import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
9 | import com.ill.test.sqltx.repository.AgentRow;
10 | import com.ill.test.sqltx.service.AgentService;
11 | import static org.mockito.Mockito.when;
12 | import reactor.core.publisher.Flux;
13 | import reactor.core.publisher.Mono;
14 | import reactor.test.StepVerifier;
15 |
16 | @WebFluxTest(controllers = { AgentController.class })
17 | class AgentControllerTest {
18 |
19 | private static final String AGENTS_URI = "http://localhost:8080/agents";
20 | private static final int EXPECTED_AGENT_ID = 101;
21 |
22 | @MockBean
23 | private AgentService mockAgentService;
24 |
25 | @Autowired
26 | private WebTestClient webTestClient;
27 |
28 | @Test
29 | void getAllAgents() {
30 | // GIVEN we have data
31 | final AgentRow a1 = new AgentRow();
32 | final AgentRow a2 = new AgentRow();
33 | when(mockAgentService.getAll()).thenReturn(Flux.just(a1, a2));
34 |
35 | // WHEN the endpoint is called
36 | final ResponseSpec response = webTestClient
37 | .get().uri(AGENTS_URI)
38 | .exchange();
39 |
40 | // THEN we get an OK response with list content
41 | checkAgentList(response, 2);
42 | }
43 |
44 | @Test
45 | void getAllAgentsIds() {
46 | // GIVEN we have data
47 | final AgentRow a1 = new AgentRow();
48 | a1.setAgentId(303);
49 | final AgentRow a2 = new AgentRow();
50 | a2.setAgentId(808);
51 | when(mockAgentService.getAll()).thenReturn(Flux.just(a1, a2));
52 |
53 | // WHEN the endpoint is called
54 | final ResponseSpec response = webTestClient
55 | .get().uri(AGENTS_URI + "/ids")
56 | .exchange();
57 |
58 | // THEN we get an OK response with list content
59 | final Flux flux = response
60 | .expectStatus().isOk()
61 | .returnResult(Integer.class)
62 | .getResponseBody();
63 | StepVerifier.create(flux.collectList())
64 | .expectNextMatches(list -> list.size() == 2)
65 | .verifyComplete();
66 | }
67 |
68 | @Test
69 | void getAgentsForCorp() {
70 | // GIVEN we have data
71 | final AgentRow a1 = new AgentRow();
72 | final AgentRow a2 = new AgentRow();
73 | when(mockAgentService.getForCorp(101)).thenReturn(Flux.just(a1, a2));
74 |
75 | // WHEN the endpoint is called
76 | final ResponseSpec response = webTestClient
77 | .get().uri(AGENTS_URI + "/corp/101")
78 | .exchange();
79 |
80 | // THEN we get an OK response with list content
81 | checkAgentList(response, 2);
82 | }
83 |
84 | @Test
85 | void getAgentsForLocation() {
86 | // GIVEN we have data
87 | final AgentRow a1 = new AgentRow();
88 | final AgentRow a2 = new AgentRow();
89 | when(mockAgentService.getForLocation(111)).thenReturn(Flux.just(a1, a2));
90 |
91 | // WHEN the endpoint is called
92 | final ResponseSpec response = webTestClient
93 | .get().uri(AGENTS_URI + "/location/111")
94 | .exchange();
95 |
96 | // THEN we get an OK response with list content
97 | checkAgentList(response, 2);
98 | }
99 |
100 | @Test
101 | void getSingleAgent() {
102 | // GIVEN we have data
103 | final AgentRow agentRow = new AgentRow();
104 | agentRow.setAgentId(EXPECTED_AGENT_ID);
105 | when(mockAgentService.getAgent(EXPECTED_AGENT_ID)).thenReturn(Mono.just(agentRow));
106 |
107 | // WHEN the endpoint is called
108 | final ResponseSpec response = webTestClient
109 | .get().uri(AGENTS_URI + "/" + EXPECTED_AGENT_ID)
110 | .exchange();
111 |
112 | // THEN we get the agent
113 | final Mono mono = response
114 | .expectStatus().isOk()
115 | .returnResult(AgentRow.class)
116 | .getResponseBody()
117 | .single();
118 | StepVerifier.create(mono)
119 | .expectNextMatches(agent -> agent.getAgentId() == EXPECTED_AGENT_ID)
120 | .verifyComplete();
121 | }
122 |
123 | @Test
124 | void getUnknownAgent() {
125 | // GIVEN we have no data
126 | when(mockAgentService.getAgent(EXPECTED_AGENT_ID)).thenReturn(Mono.empty());
127 |
128 | // WHEN the endpoint is called
129 | final ResponseSpec response = webTestClient
130 | .get().uri(AGENTS_URI + "/" + EXPECTED_AGENT_ID)
131 | .exchange();
132 |
133 | // THEN we get a 404
134 | response.expectStatus().isNotFound();
135 | }
136 |
137 | private void checkAgentList(final ResponseSpec response, int expectedCount) {
138 | final Flux flux = response
139 | .expectStatus().isOk()
140 | .returnResult(AgentRow.class)
141 | .getResponseBody();
142 | StepVerifier.create(flux.collectList())
143 | .expectNextMatches(list -> list.size() == expectedCount)
144 | .verifyComplete();
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/test/java/com/ill/test/sqltx/repository/AgentRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.ill.test.sqltx.repository;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 |
7 | import reactor.test.StepVerifier;
8 |
9 | @SpringBootTest
10 | class AgentRepositoryTest {
11 |
12 | @Autowired
13 | private AgentRepository repo;
14 |
15 | @Test
16 | void checkCount_direct() {
17 | StepVerifier
18 | .create(repo.count())
19 | .expectNext(10871L)
20 | .verifyComplete();
21 | }
22 |
23 | @Test
24 | void checkCount_list() {
25 | StepVerifier
26 | .create(repo.findAll().collectList())
27 | .expectNextMatches(list -> list.size() == 10871)
28 | .verifyComplete();
29 | }
30 |
31 | @Test
32 | void checkCorpFedNavy() {
33 | StepVerifier
34 | .create(repo.findAllByCorporationId(1000120).collectList())
35 | .expectNextMatches(list -> list.size() == 144)
36 | .verifyComplete();
37 | }
38 |
39 | @Test
40 | void checkLocation() {
41 | StepVerifier
42 | .create(repo.findAllByLocationId(60008368).collectList())
43 | .expectNextMatches(list -> list.size() == 18)
44 | .verifyComplete();
45 | }
46 |
47 | @Test
48 | void checkSingleAgent() {
49 | StepVerifier
50 | .create(repo.findById(3015958))
51 | .expectNextMatches(agent -> agent.getCorporationId() == 1000148 && agent.getLevel() == 4)
52 | .verifyComplete();
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/resources/example.sh:
--------------------------------------------------------------------------------
1 | curl 'http://localhost:8080/agents/ids' ;
2 |
3 | curl 'http://localhost:8080/agents' ;
4 |
5 | # caldari business tribunal
6 | curl 'http://localhost:8080/agents/corp/1000033';
7 |
8 | # location
9 | curl 'http://localhost:8080/agents/location/60008368';
10 |
--------------------------------------------------------------------------------