SlideShare a Scribd company logo
1 of 211
Download to read offline
CSU33012 Software Engineering
J. Introduction to Spring Boot
Goetz Botterweck
Change Log
2023-10-09 First version
2023-10-11 Added Section 5 “Library (More Complex Example using Spring MVC Web Controller, JPA,
Repositories, H2)”
2023-10-18 Using external services, first steps in GitLab
2
CSU33012 Goetz Botterweck
J. Introduction to
Spring Boot
CSU33012 Goetz Botterweck 3
References
Online Courses
(Bowman 2019) Bowman, Mary Ellen. Creating Your First Spring Boot Microservice, 1h
48min, Linked-in Learning, 2019.
https://www.linkedin.com/learning/creating-your-first-spring-boot-microservice/
4
CSU33012 Goetz Botterweck
1. Introduction to Spring
Boot
CSU33012 Goetz Botterweck 5
1.1 Prerequisites
CSU33012 Goetz Botterweck 6
Prerequisites
• Java
• Version 17
• java --version
should print a version number like 17.x.x
• Maven
• mvn --version
should print a version number like 3.9.x
• IDE
• IntelliJ IDEA Ultimate or
• Visual Studio Code
• Spring Tools for VSCode, https://spring.io/tools
CSU33012 Goetz Botterweck 7
Also useful
• Tools for Working with REST APIs
• postman
• https://www.postman.com/downloads/
• mostly app, there is also online version at https://www.postman.com/
• httpie
• https://httpie.io/
• mostly for terminal, there is also beta version of app
• Account on the local GitLab installation
• http://gitlab.scss.tcd.ie/
CSU33012 Goetz Botterweck 8
1.2 Apache Maven
CSU33012 Goetz Botterweck 9
Apache Maven
• Download, https://maven.apache.org/download.cgi
• Install, https://maven.apache.org/install.html
• Unzip
• Make sure that Maven’s bin directory is in your path
• mvn --version
should print a version number like 3.9.x
• Apache in 5 minutes (see below)
https://maven.apache.org/guides/getting-started/maven-in-five-
minutes.html
CSU33012 Goetz Botterweck 10
Image: https://en.m.wikipedia.org/wiki/File:Apache_Maven_logo.svg
Apache Maven
• Tool to build and manage software projects
• Key Features
• Dependency Management
• Building Projects
• Plugin-based Architecture
• Benefits
• Standardized Build Process
• Consistent Project Structures
• Alternative: Gradle
CSU33012 Goetz Botterweck 11
Maven Standard Directory Structure
CSU33012 Goetz Botterweck 12
https://www.baeldung.com/maven-directory-structure
Maven in 5min
CSU33012 Goetz Botterweck 13
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -
DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -
DinteractiveMode=false
If running in Windows Powershell, Maven will raise a false error
(pom.xml missing). Use cmd.exe or enclose parameters in quotes.
https://stackoverflow.com/questions/16348459/error-the-goal-
you-specified-requires-a-project-to-execute-but-there-is-no-pom
Maven in 5min
CSU33012 Goetz Botterweck 14
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
cd my-app
mvn package
Maven in 5min – pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
CSU33012 Goetz Botterweck 15
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
Maven in 5min
CSU33012 Goetz Botterweck 16
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Later in Spring Boot you can start the jar directly
without classpath, somewhat like
java -jar target/my-app-1.0-SNAPSHOT.jar
Here that does not work.
1.3 Basic Concepts Used in
Spring Boot
CSU33012 Goetz Botterweck 17
Inversion of Control
Previously
• Main program (our program)
• Controls flow of data and
execution
• Calls components
• Sets up references between
components
CSU33012 Goetz Botterweck 18
With Inversion of Control
• Framework (or Container)
• Controls flow of data and
execution
• Is responsible for„plumbing“
everything together
• Our Program
• We only have to specify what we
need
• In Spring Boot
• through annotating parts of a Java
program with tags like
@Service
@SpringBootApplication
• Uses Dependency Injection
Key Concepts in Inversion of Control
• Inversion of Control (IoC)
• Framework (a.k.a. the container) is responsible for handling the start-up,
including the instantiation of components (Beans), connecting them
• Dependency Injection
• Mechanism to implement IoC
• Component does not set up dependency, instead the framework/container
„injects“ them into the component.
• Application Context
• Spring Boot‘s version of framework/container
CSU33012 Goetz Botterweck 19
Inversion of Control Examples
CSU33012 Goetz Botterweck 20
package ie.tcd.scss.learningspring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LearningSpringApplication {
public static void main(String[] args) {
SpringApplication.run(LearningSpringApplication.class, args);
}
}
Upon starting the
application Spring Boot will
scan for annotations and
instantiate and connect the
necessary components.
Inversion of Control Examples
CSU33012 Goetz Botterweck 21
(Turnquist, p. 6-7)
...
class BookExample {
@Bean
public BookRepository bookRepository(DataSource dataSource) {
return new BookRepository();
}
...
}
• bookRepository needs a DataSource.
• Application context needs to provide one, either has it or will go create one and return it.
• Application context is responsible for management during execution
• bookRepository executes its code while referencing the app context’s DataSource.
• BookRepository is registered in the application context under the name
bookRepository.
1.4 What is Spring Boot?
CSU33012 Goetz Botterweck 22
Spring Boot
• Open-source, Java-based framework
• “Beginners version” of Spring
(based on Spring, makes it easier to get started)
• Conventions over Configuration
• Autoconfiguration
• Upon start application examines the parts of that application
• Adds additional Spring beans to application context
• Standalone
• No deployment, instead web server embedded in the application
CSU33012 Goetz Botterweck 23
1.5 Spring Boot Starters
CSU33012 Goetz Botterweck 24
Spring Boot Starters
CSU33012 Goetz Botterweck 25
https://www.baeldung.com/spring-boot-starters
<?xml version="1.0" encoding="UTF-8"?>
<project …>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
...
</project>
Adding Starter to pom.xml as dependency … … ensures additional libraries are
included and annotations are available
These are what is added to your project,
when you select „Dependencies“ in
Spring Initializr (more later)
Spring Boot Starters – Examples
• Spring Web – applications that offer REST-based APIs, uses Spring
MVC, uses Apache Tomcat as the default embedded container.
• Spring WebFlux – alternative to Spring Web, based on reactive
streams
• See examples in (Larsson 2023)
• See https://www.baeldung.com/spring-mvc-async-vs-webflux
• Spring Data JPA – accessing data via Java Persistence API (JPA)
• Spring Test – testing your application
• Spring Mail – sending emails
Source: https://www.baeldung.com/spring-boot-starters
CSU33012 Goetz Botterweck 26
2. Creating a Web
Application with Spring
Boot
CSU33012 Goetz Botterweck 27
So, what do we need?
• We want to setup a basic project and its file structure
• Based on Java, Maven, Spring Boot
• We want to build a simple web application
(that responds to a HTTP request with a response)
/hello → Hello World!
CSU33012 Goetz Botterweck 28
2.1 Spring Initializr
CSU33012 Goetz Botterweck 29
Spring Initializr
CSU33012 Goetz Botterweck 30
• Fast way to set up a Spring Boot
project
• Build management
• Maven
• Gradle
• Language
• Java
• Kotlin
• Groovy
• Spring Boot Version
• Project Metadata
• Packaging
• Java Version
• Dependencies (to add
functionality)
Spring Initializr
CSU33012 Goetz Botterweck 31
• Fast way to set up a Spring Boot
project
• Build management
• Maven
• Gradle
• Language
• Java
• Kotlin
• Groovy
• Spring Boot Version
• Project Metadata
• Packaging
• Java Version
• Dependencies (to add
functionality)
Spring Initializr
API
start.spring.io
Website
Spring Initializr
Support in
IntelliJ IDEA
„Spring Initializr
Java Support“
vscode-spring-initializr
In VS Code
Spring Initializr
CSU33012 Goetz Botterweck 32
• http://start.spring.io
• Project = Maven
• Language = Java
• Spring Boot = default (3.x)
• Group = ie.tcd.scss
• Artifact = learningspring
• Packaging = Jar
• Java = 17
• Dependencies
• Spring Web
• Generate
• Download and unpack
• Open project in your IDE
Resulting Project in IntelliJ IDEA
CSU33012 Goetz Botterweck 33
Resulting Project in IntelliJ IDEA
CSU33012 Goetz Botterweck 34
HELP.md contains some helpful references
CSU33012 Goetz Botterweck 35
The New App
CSU33012 Goetz Botterweck 36
2.2 Running the Application
CSU33012 Goetz Botterweck 37
Running the Application
CSU33012 Goetz Botterweck 38
Alternative
http://localhost:8080/
CSU33012 Goetz Botterweck 39
Toolbar
“Run Actions”
More run and
debug actions
http://localhost:8080/
CSU33012 Goetz Botterweck 40
Same in Postman
CSU33012 Goetz Botterweck 41
https://www.postman.com/
3. Hello World
(Minimal Spring MVC Web Controller)
CSU33012 Goetz Botterweck 42
3.1 Creating a Test
CSU33012 Goetz Botterweck 43
• Go to src/test/java/ie.tcd.scss.learningspring
• Create new class HelloControllerTest
• Add content on next slide
• Right click src/test/java > Run all tests (Ctrl-Shift-F10)
• Inspect test results
CSU33012 Goetz Botterweck 44
HelloControllerTest
package ie.tcd.scss.learningspring;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void helloShouldReturnDefaultMessage() {
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/hello", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("Hello, World!");
}
@Test
public void helloShouldReturnDefaultMessageDetailed() {
String url = "http://localhost:" + port + "/hello";
String expectedResult = "Hello, World!";
Class<String> returnType = String.class;
ResponseEntity<String> response = this.restTemplate.getForEntity(url, returnType);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains(expectedResult);
}
}
CSU33012 Goetz Botterweck 45
HelloControllerTest
package ie.tcd.scss.learningspring;
import ...
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
...
@Test
public void helloShouldReturnDefaultMessageDetailed() {
String url = "http://localhost:" + port + "/hello";
String expectedResult = "Hello, World!";
Class<String> returnType = String.class;
ResponseEntity<String> response = this.restTemplate.getForEntity(url, returnType);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains(expectedResult);
}
}
CSU33012 Goetz Botterweck 46
Step-by-step
HelloControllerTest
package ie.tcd.scss.learningspring;
import ...
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void helloShouldReturnDefaultMessage() {
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/hello", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("Hello, World!");
}
...
}
CSU33012 Goetz Botterweck 47
Same test, but less
intermediate steps
Creating HelloControllerTest
CSU33012 Goetz Botterweck 48
Copying Text from Slides PDF
CSU33012 Goetz Botterweck 49
If text copies from PDF looks garbled
Edit > Find > Replace (Ctrl + R)
Search for: u000B (or select the weird VT character)
Replace with: n
Activate regular expressions .* (Alt + X)
Replace all (Alt + A)
HelloControllerTest in IntelliJ IDEA
CSU33012 Goetz Botterweck 50
3.2 Running the Test
CSU33012 Goetz Botterweck 51
CSU33012 Goetz Botterweck 52
CSU33012 Goetz Botterweck 53
CSU33012 Goetz Botterweck 54
Command Line
CSU33012 Goetz Botterweck 55
mvn test
Running Tests on Command Line
CSU33012 Goetz Botterweck 56
Enter = Just run the command
Ctrl + Enter = Run with IDE integration
Running Tests on Command Line (plain)
CSU33012 Goetz Botterweck 57
Running Tests on Command Line (with Integration)
CSU33012 Goetz Botterweck 58
3.3 Creating the MVC Web
Controller
CSU33012 Goetz Botterweck 59
Create New Class HelloController
CSU33012 Goetz Botterweck 60
Create New Class HelloController
CSU33012 Goetz Botterweck 61
HelloController
CSU33012 Goetz Botterweck 62
package ie.tcd.scss.learningspring;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
HelloController
CSU33012 Goetz Botterweck 63
Run All Tests Again
CSU33012 Goetz Botterweck 64
CSU33012 Goetz Botterweck 65
CSU33012 Goetz Botterweck 66
4. Music Styles
(Storing and Retrieving Info with Spring MVC Web
Controller)
CSU33012 Goetz Botterweck 67
Music Styles
• Can store mapping artist->genre
• The Weeknd → R&B
• Eminem → Hip Hop
• Michael Jackson → Pop
• Adele → Pop
• Can retrieve info on given artist
• In: Eminem
Out: Hip Hop
• Can list all artists for a given genre
• In: Pop
Out: Michael Jackson, Adele
CSU33012 Goetz Botterweck 68
class ArtistInfo
package com.example.musicstyles;
public class ArtistInfo {
private String artist;
private String musicStyle;
public ArtistInfo(String artist, String musicStyle) {
this.artist = artist;
this.musicStyle = musicStyle;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getMusicStyle() {
return musicStyle;
}
public void setMusicStyle(String musicStyle) {
this.musicStyle = musicStyle;
}
}
CSU33012 Goetz Botterweck 69
Image: https://unsplash.com/photos/Km5J-KCP1Mw
Spring Initializr
CSU33012 Goetz Botterweck 70
• http://start.spring.io
• Project = Maven
• Language = Java
• Spring Boot = default (3.x)
• Group = ie.tcd.scss
• Artifact = musicstyles
• Packaging = Jar
• Java = 17
• Dependencies
• Spring Web
• Generate
• Download and unpack
• Open project in your IDE
4.1 Creating and Running a
Test
CSU33012 Goetz Botterweck 71
class MusicstylesControllerTest (1 of 2)
CSU33012 Goetz Botterweck 72
package ie.tcd.scss.musicstyles;
import ...
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MusicstylesControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void addArtistTest() {
ArtistInfo artistInfo = new ArtistInfo("The Weeknd", "RnB");
ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("Artist added successfully!", response.getBody());
}
@Test
void getMusicStyleTest() {
ArtistInfo artistInfo = new ArtistInfo("Eminem", "Hip Hop");
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class);
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/Eminem", String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("Hip Hop", response.getBody());
}
...
}
class MusicstylesControllerTest (2 of 2)
CSU33012 Goetz Botterweck 73
package ie.tcd.scss.musicstyles;
import …
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MusicstylesControllerTest {
...
@Test
void getArtistsByMusicStyleTest() {
ArtistInfo artistInfo1 = new ArtistInfo("Adele", "Pop");
ArtistInfo artistInfo2 = new ArtistInfo("Taylor Swift", "Pop");
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo1, String.class);
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo2, String.class);
ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/Pop/artists", List.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody().contains("Adele"));
assertTrue(response.getBody().contains("Taylor Swift"));
}
@Test
void getNonExistentArtistTest() {
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/NonExistentArtist", String.class);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@Test
void getArtistsByNonExistentMusicStyleTest() {
ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/NonExistentStyle/artists", List.class);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
}
class MusicstylesControllerTest (full source)
CSU33012 Goetz Botterweck 74
package ie.tcd.scss.musicstyles;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MusicstylesControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void addArtistTest() {
ArtistInfo artistInfo = new ArtistInfo("The Weeknd", "RnB");
ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("Artist added successfully!", response.getBody());
}
@Test
void getMusicStyleTest() {
ArtistInfo artistInfo = new ArtistInfo("Eminem", "Hip Hop");
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class);
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/Eminem", String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("Hip Hop", response.getBody());
}
@Test
void getArtistsByMusicStyleTest() {
ArtistInfo artistInfo1 = new ArtistInfo("Adele", "Pop");
ArtistInfo artistInfo2 = new ArtistInfo("Taylor Swift", "Pop");
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo1, String.class);
restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo2, String.class);
ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/Pop/artists", List.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody().contains("Adele"));
assertTrue(response.getBody().contains("Taylor Swift"));
}
@Test
void getNonExistentArtistTest() {
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/NonExistentArtist", String.class);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@Test
void getArtistsByNonExistentMusicStyleTest() {
ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/NonExistentStyle/artists", List.class);
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
}
CSU33012 Goetz Botterweck 75
CSU33012 Goetz Botterweck 76
4.2 Testing a REST API with
Postman
CSU33012 Goetz Botterweck 77
Testing a REST API with Postman
CSU33012 Goetz Botterweck 78
Testing a REST API with Postman
CSU33012 Goetz Botterweck 79
Testing a REST API with Postman
CSU33012 Goetz Botterweck 80
4.3 Implementing the
Controller
CSU33012 Goetz Botterweck 81
class MusicstylesController (1 of 2)
CSU33012 Goetz Botterweck 82
package ie.tcd.scss.musicstyles;
import ...
@RestController
@RequestMapping("/api")
public class MusicstylesController {
private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>();
@PostMapping("/artists")
public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) {
artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle());
return ResponseEntity.ok("Artist added successfully!");
}
@GetMapping("/artists/{artistName}")
public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) {
String musicStyle = artistStyleMap.get(artistName);
if (musicStyle == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!");
}
return ResponseEntity.ok(musicStyle);
}
...
}
class MusicstylesController (1 of 2)
CSU33012 Goetz Botterweck 83
package ie.tcd.scss.musicstyles;
import ...
@RestController
@RequestMapping("/api")
public class MusicstylesController {
private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>();
@PostMapping("/artists")
public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) {
artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle());
return ResponseEntity.ok("Artist added successfully!");
}
@GetMapping("/artists/{artistName}")
public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) {
String musicStyle = artistStyleMap.get(artistName);
if (musicStyle == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!");
}
return ResponseEntity.ok(musicStyle);
}
...
}
URL path pattern with path
variable “artistName”
class MusicstylesController (2 of 2)
CSU33012 Goetz Botterweck 84
package ie.tcd.scss.musicstyles;
import ...
@RestController
@RequestMapping("/api")
public class MusicstylesController {
...
@GetMapping("/musicstyles/{musicStyleName}/artists")
public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) {
List<String> artists = artistStyleMap.entrySet().stream()
.filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (artists.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList());
}
return ResponseEntity.ok(artists);
}
}
class MusicstylesController (2 of 2)
CSU33012 Goetz Botterweck 85
package ie.tcd.scss.musicstyles;
import ...
@RestController
@RequestMapping("/api")
public class MusicstylesController {
...
@GetMapping("/musicstyles/{musicStyleName}/artists")
public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) {
List<String> artists = artistStyleMap.entrySet().stream()
.filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (artists.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList());
}
return ResponseEntity.ok(artists);
}
}
Converts artistStyleMap into
stream of its entries
Filter for entries where the
value is <musicStyleName>
ignoring case
Transform each remaining
entry into its key (the artist
name in this case)
Collect the results into a
List<Strings>
Lambda
expression
URL path pattern with path
variable “musicStyleName”
class MusicstylesController (full source)
CSU33012 Goetz Botterweck 86
package ie.tcd.scss.musicstyles;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class MusicstylesController {
private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>();
@PostMapping("/artists")
public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) {
artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle());
return ResponseEntity.ok("Artist added successfully!");
}
@GetMapping("/artists/{artistName}")
public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) {
String musicStyle = artistStyleMap.get(artistName);
if (musicStyle == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!");
}
return ResponseEntity.ok(musicStyle);
}
@GetMapping("/musicstyles/{musicStyleName}/artists")
public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) {
List<String> artists = artistStyleMap.entrySet().stream()
.filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (artists.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList());
}
return ResponseEntity.ok(artists);
}
}
4.4 Testing the Controller
CSU33012 Goetz Botterweck 87
CSU33012 Goetz Botterweck 88
Testing the Controller
CSU33012 Goetz Botterweck 89
Testing the Controller
CSU33012 Goetz Botterweck 90
Testing the Controller With Postman
CSU33012 Goetz Botterweck 91
Testing the Controller With Postman
CSU33012 Goetz Botterweck 92
Testing the Controller With Postman
CSU33012 Goetz Botterweck 93
CSU33012 Goetz Botterweck 94
CSU33012 Goetz Botterweck 95
CSU33012 Goetz Botterweck 96
CSU33012 Goetz Botterweck 97
CSU33012 Goetz Botterweck 98
5. Library
(More Complex Example using Spring MVC Web
Controller, JPA, Repositories, H2)
CSU33012 Goetz Botterweck 99
5.1 Setting up the Initial
Project
CSU33012 Goetz Botterweck 100
• Based on „Explore California“ in (Bowman 2019)
• Simplified and added explanations
• Updated to Spring Boot 3
• Changed namespaces
javax.somepackage → jakarta.somepackage
CSU33012 Goetz Botterweck 101
5.1.1 Creating Project with
start.spring.io
CSU33012 Goetz Botterweck 102
Spring Initializr
CSU33012 Goetz Botterweck 103
• http://start.spring.io
• Project = Maven
• Language = Java
• Spring Boot = default (3.x)
• Group = ie.tcd.scss
• Artifact = library
• Packaging = Jar
• Java = 17
• Dependencies
• Spring Web
• Rest Repositories
• Spring Data JPA
• H2 Database
• Generate
• Download and unpack
• Open project in your IDE
CSU33012 Goetz Botterweck 104
CSU33012 Goetz Botterweck 105
Creating Remote Repository (GitHub or GitLab)
CSU33012 Goetz Botterweck 106
If you want to
push an existing
repository later
do NOT create a
README
CSU33012 Goetz Botterweck 107
CSU33012 Goetz Botterweck 108
CSU33012 Goetz Botterweck 109
CSU33012 Goetz Botterweck 110
5.1.3 Creating Project with IntelliJ
Wizard
CSU33012 Goetz Botterweck 111
Spring Initializr Support in IntelliJ New Project Wizard
CSU33012 Goetz Botterweck 112
New Project
CSU33012 Goetz Botterweck 113
Initial Commit
CSU33012 Goetz Botterweck 114
Initial Commit
CSU33012 Goetz Botterweck 115
5.1.4 Adding Remote Repository
CSU33012 Goetz Botterweck 116
CSU33012 Goetz Botterweck 117
CSU33012 Goetz Botterweck 118
CSU33012 Goetz Botterweck 119
CSU33012 Goetz Botterweck 120
CSU33012 Goetz Botterweck 121
CSU33012 Goetz Botterweck 122
CSU33012 Goetz Botterweck 123
5.2 Creating JPA Entities
(Classes to Handle Data)
CSU33012 Goetz Botterweck 124
Domain Model
• Book
• Author
• Library Section
CSU33012 Goetz Botterweck 125
Simplified Domain Model
• Book
• Authors represented as simple
string
• Identifier “id” of type Integer
• Library Section
• Identifier “code” of type String
CSU33012 Goetz Botterweck 126
Jakarta Persistence API (JPA)
• Formerly known as Java Persistence API
• Application Programming Interface (set of interfaces) to
• handle data in Java programs
• manage the mapping of Java classes to tables in databases
• Query Language JPQL
• Implementations
• Eclipse Link (reference implementation)
• Hibernate
• Spring Data JPA uses JPA, expects a JPA implementation
CSU33012 Goetz Botterweck 127
CSU33012 Goetz Botterweck 128
https://www.youtube.com/watch?v=4Py9RTVWyvE
H2 Database
• Minimal, fast in-memory database
• Good for early experiments, proof-of-concept implementations
• https://www.h2database.com/
• No need to install!
• https://www.baeldung.com/spring-boot-h2-database
CSU33012 Goetz Botterweck 129
New Package
CSU33012 Goetz Botterweck 130
New Package
CSU33012 Goetz Botterweck 131
class Book
CSU33012 Goetz Botterweck 132
package ie.tcd.scss.library.domain;
import jakarta.persistence.*;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@Column
private String title;
@Column
private String authors;
@Column
private Integer publicationYear;
@Column
private Integer price;
@Column
private String isbn;
@Column(length = 500)
private String keywords;
@Column(length = 2000)
private String description;
@ManyToOne
private LibrarySection librarySection;
}
JPA Annotation
JPA Annotations
class LibrarySection
CSU33012 Goetz Botterweck 133
package ie.tcd.scss.library.domain;
import jakarta.persistence.*;
@Entity
public class LibrarySection {
@Id
private String code;
@Column
private String name;
@OneToMany
private Set<Book> books;
}
Generating Missing Elements
for classes, not needed for enumerations
• Constructors
• One without any arguments – make private, so it does not get used
• One with all arguments (except @GeneratedValue id)
• Getters
• Setters
• except for @GeneratedValue id
• toString()
• equals()
• hashCode()
(Can be avoided with lombok, but for now we use the standard approach.)
CSU33012 Goetz Botterweck 134
Generating Missing Elements
CSU33012 Goetz Botterweck 135
class Book (Full Source)
package ie.tcd.scss.library.domain;
import jakarta.persistence.*;
import java.util.Objects;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@Column
private String title;
@Column
private String authors;
@Column
private Integer publicationYear;
@Column
private Integer price;
@Column
private String isbn;
@Column(length = 500)
private String keywords;
@Column(length = 2000)
private String description;
@ManyToOne
private LibrarySection librarySection;
public Book(String title, String authors, Integer publicationYear, Integer price, String isbn, String keywords,
String description, LibrarySection librarySection) {
this.title = title;
this.authors = authors;
this.publicationYear = publicationYear;
this.price = price;
this.isbn = isbn;
this.keywords = keywords;
this.description = description;
this.librarySection = librarySection;
}
protected Book() {
}
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthors() {
return authors;
}
public void setAuthors(String authors) {
this.authors = authors;
}
public Integer getPublicationYear() {
return publicationYear;
}
CSU33012 Goetz Botterweck 136
public void setPublicationYear(Integer publicationYear) {
this.publicationYear = publicationYear;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LibrarySection getLibrarySection() {
return librarySection;
}
public void setLibrarySection(LibrarySection librarySection) {
this.librarySection = librarySection;
}
@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) && Objects.equals(title, book.title) && Objects.equals(authors, book.authors) && Objects.equals(publicationYear,
book.publicationYear) && Objects.equals(price, book.price) && Objects.equals(isbn, book.isbn) && Objects.equals(keywords, book.keywords) &&
Objects.equals(description, book.description) && Objects.equals(librarySection, book.librarySection);
}
@Override
public int hashCode() {
return Objects.hash(id, title, authors, publicationYear, price, isbn, keywords, description, librarySection);
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", title='" + title + ''' +
", authors='" + authors + ''' +
", publicationYear=" + publicationYear +
", price=" + price +
", isbn='" + isbn + ''' +
", keywords='" + keywords + ''' +
", description='" + description + ''' +
", librarySection=" + librarySection +
'}';
}
}
class LibrarySection (Full Source)
CSU33012 Goetz Botterweck 137
package ie.tcd.scss.library.domain;
import jakarta.persistence.*;
import java.util.Objects;
@Entity
public class LibrarySection {
@Id
private String code;
@Column
private String name;
public LibrarySection(String code, String name) {
this.code = code;
this.name = name;
}
protected LibrarySection() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LibrarySection that = (LibrarySection) o;
return Objects.equals(code, that.code) && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(code, name);
}
@Override
public String toString() {
return "LibrarySection{" +
"code='" + code + ''' +
", name='" + name + ''' +
'}';
}
}
CSU33012 Goetz Botterweck 138
5.3 Creating Spring Data
Repositories
CSU33012 Goetz Botterweck 139
Creating Spring Data Repositories
• We declare an interface that extends
public interface CrudRepository<T, I>
(CRUD = Create, Read, Update, Delete)
• T = Data type that the repository manages, e.g., Book
• I = Data type of the entity ID, e.g., Integer
• Spring Data automatically provides all necessary methods to operate
on that Repository
CSU33012 Goetz Botterweck 140
Creating Spring Data Repositories
For instance after declaring
public interface BookRepository extends
CrudRepository<Book, Integer>
We automatically get
Book save(Book b)
Iterable<Book> saveAll(Iterable<Book> iterable)
CSU33012 Goetz Botterweck 141
Creating Spring Data Repositories
CSU33012 Goetz Botterweck 142
Creating Spring Data Repositories
CSU33012 Goetz Botterweck 143
Creating Spring Data Repositories
CSU33012 Goetz Botterweck 144
Creating Spring Data Repositories
CSU33012 Goetz Botterweck 145
interface BookRepository
CSU33012 Goetz Botterweck 146
package ie.tcd.scss.library.repo;
import ie.tcd.scss.library.domain.Book;
import org.springframework.data.repository.CrudRepository;
public interface BookRepository extends CrudRepository<Book, Long> {
}
Basic default functionality
is available automatically.
If we need additional
methods, we need to
declare them as part of
the interface.
interface LibrarySectionRepository
CSU33012 Goetz Botterweck 147
package ie.tcd.scss.library.repo;
import ie.tcd.scss.library.domain.LibrarySection;
import org.springframework.data.repository.CrudRepository;
public interface LibrarySectionRepository extends CrudRepository<LibrarySection, String> {
}
Spring Data JPA Repository Interfaces
public interface BookRepository extends
CrudRepository<Book, Integer>
Book save(Book b)
Iterable<Book> saveAll(Iterable<Book> iterable)
void deleteById(Integer id)
void delete(Book book)
void delete(Iterable<Book> iterable)
Void deleteAll()
Optional<Book> findById(Integer id)
boolean existsById(Integer integer)
Iterable<Book> findAll()
Iterable<Book> findAllById(Iterable<Integer> iterable)
long count()
CSU33012 Goetz Botterweck 148
CSU33012 Goetz Botterweck 149
5.4 Creating Services (with
Injected Repositories)
CSU33012 Goetz Botterweck 150
CSU33012 Goetz Botterweck 151
CSU33012 Goetz Botterweck 152
CSU33012 Goetz Botterweck 153
class BookService
CSU33012 Goetz Botterweck 154
package ie.tcd.scss.library.service;
public class BookService {
}
Details of Service
• Service class tagged @Service
• Private variable(s) holding reference(s) to all needed repositories
• Final
• Constructor
• expects references to repositories
• is „autowired“ by tag @Autowired
• Methods
• createBook()
• Create a new book
• lookup() – return (an Iterable with) all books
• total()– return total number of books
CSU33012 Goetz Botterweck 155
class BookService (Overview)
CSU33012 Goetz Botterweck 156
package ie.tcd.scss.library.service;
import ...
@Service
public class BookService {
private final BookRepository bookRepository;
private final LibrarySectionRepository librarySectionRepository;
@Autowired
public BookService(BookRepository bookRepository, LibrarySectionRepository librarySectionRepository) {
this.bookRepository = bookRepository;
this.librarySectionRepository = librarySectionRepository;
}
public Book createBook(String title, String authors, Integer publicationYear, Integer price, String isbn,
String keywords, String description, String librarySectionCode) {
... find librarySection
return bookRepository.save(new Book(...));
}
public Iterable<Book> lookup() {...}
public long total() {...}
}
How Does Autowiring and Depdendency Injection Work?
• Defining Beans
• Classes eligible for autowiring must be marked with tags like
@Component, @Service, @Repository, @Controller
• @Autowired annotation
• Instructs Spring to “inject” the dependency automatically
• Field Injection
• Constructor Injection – recommend, what we used above
• Setter Injection
CSU33012 Goetz Botterweck 157
How Does Autowiring and Depdendency Injection Work?
• Component Scanning
• Dependency Injection
• When a bean (e.g., BookService) is created, Spring checks (e.g. by looking
at constructor) if there are any dependencies that need to be injected.
• In the case of our BookService, it needs an instance of BookRepository
and an instance of LibrarySectionRepository
• Spring will search among the beans in its context for one that matches the
required type (e.g., BookRepository). If it finds a match, it will inject that
bean into the dependent one (BookService). Otherwise exception.
CSU33012 Goetz Botterweck 158
How Does Autowiring and Depdendency Injection Work?
Much more details at https://www.baeldung.com/spring-dependency-
injection
CSU33012 Goetz Botterweck 159
class BookService (Full Source)
CSU33012 Goetz Botterweck 160
package ie.tcd.scss.library.service;
import ie.tcd.scss.library.domain.Book;
import ie.tcd.scss.library.domain.LibrarySection;
import ie.tcd.scss.library.repo.BookRepository;
import ie.tcd.scss.library.repo.LibrarySectionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class BookService {
private final BookRepository bookRepository;
private final LibrarySectionRepository librarySectionRepository;
@Autowired
public BookService(BookRepository bookRepository, LibrarySectionRepository librarySectionRepository) {
this.bookRepository = bookRepository;
this.librarySectionRepository = librarySectionRepository;
}
public Book createBook(String title, String authors, Integer publicationYear, Integer price, String isbn,
String keywords, String description, String librarySectionCode) {
LibrarySection librarySection = librarySectionRepository
.findById(librarySectionCode)
.orElseThrow(() -> new RuntimeException("Library Section does not exist: " + librarySectionCode));
return bookRepository.save(new Book(title, authors, publicationYear, price, isbn, keywords, description,
librarySection));
}
public Iterable<Book> lookup() {
return bookRepository.findAll();
}
public long total() {
return bookRepository.count();
}
}
class LibrarySectionService
CSU33012 Goetz Botterweck 161
package ie.tcd.scss.library.service;
import ie.tcd.scss.library.domain.LibrarySection;
import ie.tcd.scss.library.repo.LibrarySectionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LibrarySectionService {
private final LibrarySectionRepository librarySectionRepository;
@Autowired LibrarySectionService(LibrarySectionRepository librarySectionRepository) {
this.librarySectionRepository = librarySectionRepository;
}
public LibrarySection createLibrarySection(String code, String name) {
return librarySectionRepository
.findById(code)
.orElse(librarySectionRepository.save(new LibrarySection(code, name)));
}
public Iterable<LibrarySection> lookup() {
return librarySectionRepository.findAll();
}
public long total() {
return librarySectionRepository.count();
}
}
CSU33012 Goetz Botterweck 162
5.5 Filling the Database
With Some Test Data
CSU33012 Goetz Botterweck 163
class LibraryApplication (Overview)
CSU33012 Goetz Botterweck 164
package ie.tcd.scss.library;
import ...
@SpringBootApplication
public class LibraryApplication implements CommandLineRunner {
@Autowired
private LibrarySectionService librarySectionService;
@Autowired
private BookService bookService;
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
createLibrarySections();
createBooks();
}
private void createLibrarySections() {
librarySectionService.createLibrarySection("Scifi", "Science Fiction Books");
librarySectionService.createLibrarySection("Fiction", "Fiction Books");
...
}
private void createBooks() {
bookService.createBook("The Hobbit", "J.R.R. Tolkien", 2023, 99, "ISBN 978-123456", "hobbit, tolkien, fantasy",
"A book about a hobbit", "Fiction");
bookService.createBook("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1978, 49, "ISBN 978-456789", "comedy, bbc, scifi",
"A book about life and all the rest", "Scifi");
}
}
Field injection
Required by CommandLineRunner
class LibraryApplication (Full Source)
CSU33012 Goetz Botterweck 165
package ie.tcd.scss.library;
import ie.tcd.scss.library.service.BookService;
import ie.tcd.scss.library.service.LibrarySectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LibraryApplication implements CommandLineRunner {
@Autowired
private LibrarySectionService librarySectionService;
@Autowired
private BookService bookService;
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
createLibrarySections();
createBooks();
}
private void createLibrarySections() {
librarySectionService.createLibrarySection("Scifi", "Science Fiction Books");
librarySectionService.createLibrarySection("Fiction", "Fiction Books");
librarySectionService.createLibrarySection("Non-fiction", "Non-Fiction Books");
librarySectionService.createLibrarySection("CS", "Computer Science Books");
}
private void createBooks() {
bookService.createBook("The Hobbit", "J.R.R. Tolkien", 2023, 99,
"ISBN 978-123456", "hobbit, tolkien, fantasy",
"A book about a hobbit", "Fiction");
bookService.createBook("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1978, 49,
"ISBN 978-456789", "comedy, bbc, scifi",
"A book about life and all the rest", "Scifi");
}
}
CSU33012 Goetz Botterweck 166
5.6 Adding Controller
(Using DTOs)
CSU33012 Goetz Botterweck 167
• Why Create BookController?
• To provide REST access to business logic, e.g.,
create Books and add them into a LibrarySection in one go
• We need to get extra data (code of LibrarySection)
• Two approaches
• Get extra data in request parameters
• Get extra data in Data Transfer Object (DTO)
• i.e., one class/object that can hold all data needed during the request
CSU33012 Goetz Botterweck 168
class BookController (using Request Parameter)
CSU33012 Goetz Botterweck 169
package ie.tcd.scss.library.controller;
import ie.tcd.scss.library.domain.Book;
import ie.tcd.scss.library.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(path = "/booksController")
public class BookController {
private BookService bookService;
@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
protected BookController() {
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void createBook(@RequestBody Book book, @RequestParam String librarySectionCode) {
bookService.createBook(book.getTitle(), book.getAuthors(), book.getPublicationYear(), book.getPrice(),
book.getIsbn(), book.getKeywords(), book.getDescription(), librarySectionCode);
}
}
class BookControllerWithDto (Using DTOs)
CSU33012 Goetz Botterweck 170
package ie.tcd.scss.library.controller;
import ie.tcd.scss.library.domain.Book;
import ie.tcd.scss.library.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(path = "/booksControllerWithDto")
public class BookControllerWithDto {
private BookService bookService;
@Autowired
public BookControllerWithDto(BookService bookService) {
this.bookService = bookService;
}
protected BookControllerWithDto() {
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void createBook(@RequestBody BookDto bookDto) {
Book book = bookDto.getBook();
String librarySectionCode = bookDto.getLibrarySectionCode();
bookService.createBook(book.getTitle(), book.getAuthors(), book.getPublicationYear(), book.getPrice(),
book.getIsbn(), book.getKeywords(), book.getDescription(), librarySectionCode);
}
}
class BookDto
CSU33012 Goetz Botterweck 171
package ie.tcd.scss.library.controller;
import ie.tcd.scss.library.domain.Book;
public class BookDto {
private Book book;
private String librarySectionCode;
public BookDto(Book book, String librarySectionCode) {
this.book = book;
this.librarySectionCode = librarySectionCode;
}
protected BookDto() {
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public String getLibrarySectionCode() {
return librarySectionCode;
}
public void setLibrarySectionCode(String librarySectionCode) {
this.librarySectionCode = librarySectionCode;
}
}
CSU33012 Goetz Botterweck 172
5.7 Testing the
Application and ist API
CSU33012 Goetz Botterweck 173
CSU33012 Goetz Botterweck 174
CSU33012 Goetz Botterweck 175
CSU33012 Goetz Botterweck 176
CSU33012 Goetz Botterweck 177
CSU33012 Goetz Botterweck 178
CSU33012 Goetz Botterweck 179
5.8 First Steps in GitLab
CI/CD
CSU33012 Goetz Botterweck 180
GitLab Pipelines
CSU33012 Goetz Botterweck 181
GitLab Pipelines
CSU33012 Goetz Botterweck 182
GitLab Pipelines
CSU33012 Goetz Botterweck 183
GitLab Pipelines
CSU33012 Goetz Botterweck 184
GitLab Pipelines
CSU33012 Goetz Botterweck 185
GitLab Pipelines
CSU33012 Goetz Botterweck 186
GitLab Pipelines
CSU33012 Goetz Botterweck 187
GitLab Pipelines
CSU33012 Goetz Botterweck 188
GitLab Pipelines
CSU33012 Goetz Botterweck 189
https://docs.gitlab.com/runner/install/
5.9 Accessing an External
Service via HTTP REST
CSU33012 Goetz Botterweck 190
• Source available at
https://gitlab.scss.tcd.ie/botterwg/demo-gitlabanalysis
CSU33012 Goetz Botterweck 191
CSU33012 Goetz Botterweck 192
CSU33012 Goetz Botterweck 193
CSU33012 Goetz Botterweck 194
CSU33012 Goetz Botterweck 195
5.8.2 Retrieving Data as String
CSU33012 Goetz Botterweck 196
class GitHubAnalysisApplication
CSU33012 Goetz Botterweck 197
package ie.tcd.scss.githubanalysis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class GitHubAnalysisApplication {
public static void main(String[] args) {
SpringApplication.run(GitHubAnalysisApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
class GitHubController
CSU33012 Goetz Botterweck 198
package ie.tcd.scss.githubanalysis.controller;
import ie.tcd.scss.githubanalysis.service.GitHubService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GitHubController {
private final GitHubService gitHubService;
public GitHubController(GitHubService gitHubService) {
this.gitHubService = gitHubService;
}
@GetMapping("/github/user/{username}")
public String getGitHubUserProfile(@PathVariable String username) {
return gitHubService.getGitHubUserProfile(username);
}
}
class GitHubService
CSU33012 Goetz Botterweck 199
package ie.tcd.scss.githubanalysis.service;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class GitHubService {
private final RestTemplate restTemplate;
public GitHubService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getGitHubUserProfile(String username) {
String apiUrl = "https://api.github.com/users/" + username;
return this.restTemplate.getForObject(apiUrl, String.class);
}
}
CSU33012 Goetz Botterweck 200
5.8.2 Retrieving Data in
Structured Form
CSU33012 Goetz Botterweck 201
class GitHubUser
CSU33012 Goetz Botterweck 202
package ie.tcd.scss.githubanalysis.model;
public class GitHubUser {
private String login;
private int id;
private String url;
private String name;
private String company;
private String blog;
private int followers;
private int following;
... getter/setter ...
}
class GitHubUser (full source)
CSU33012 Goetz Botterweck 203
package ie.tcd.scss.githubanalysis.model;
public class GitHubUser {
private String login;
private int id;
private String url;
private String name;
private String company;
private String blog;
private int followers;
private int following;
public GitHubUser() {
}
public GitHubUser(String login, int id, String url, String name, String company, String blog, int followers, int following) {
this.login = login;
this.id = id;
this.url = url;
this.name = name;
this.company = company;
this.blog = blog;
this.followers = followers;
this.following = following;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
public int getFollowers() {
return followers;
}
public void setFollowers(int followers) {
this.followers = followers;
}
public int getFollowing() {
return following;
}
public void setFollowing(int following) {
this.following = following;
}
}
class GitHubService
CSU33012 Goetz Botterweck 204
package ie.tcd.scss.githubanalysis.service;
import ie.tcd.scss.githubanalysis.model.GitHubUser;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class GitHubService {
private final RestTemplate restTemplate;
public GitHubService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getGitHubUserProfileString(String username) {
String apiUrl = "https://api.github.com/users/" + username;
return this.restTemplate.getForObject(apiUrl, String.class);
}
public GitHubUser getGitHubUserProfile(String username) {
String apiUrl = "https://api.github.com/users/" + username;
return this.restTemplate.getForObject(apiUrl, GitHubUser.class);
}
}
class GitHubController
CSU33012 Goetz Botterweck 205
package ie.tcd.scss.githubanalysis.controller;
import ie.tcd.scss.githubanalysis.service.GitHubService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import ie.tcd.scss.githubanalysis.model.GitHubUser;
@RestController
public class GitHubController {
private final GitHubService gitHubService;
public GitHubController(GitHubService gitHubService) {
this.gitHubService = gitHubService;
}
@GetMapping("/github/user/{username}")
public GitHubUser getGitHubUserProfile(@PathVariable String username) {
return gitHubService.getGitHubUserProfile(username);
}
@GetMapping("/github/userString/{username}")
public String getGitHubUserProfileString(@PathVariable String username) {
return gitHubService.getGitHubUserProfileString(username);
}
}
CSU33012 Goetz Botterweck 206
CSU33012 Goetz Botterweck 207
CSU33012 Goetz Botterweck 208
5.9 RESTful APIs with
Spring Data REST
CSU33012 Goetz Botterweck 209
RESTful APIs
• Hypermedia-driven APIs
• Roy Fielding (2000)
• Hypermedia as the Engine of the Application State (HATEOAS)
• Expose application state via API
• Expose the API‘s documentation
• Navigation between resources
• Spring Data REST provides that out of the box!
CSU33012 Goetz Botterweck 210
RESTful APIs
• Hypermedia-driven APIs
• Roy Fielding (2000)
• Hypermedia as the Engine of the Application State (HATEOAS)
• Expose application state via API
• Expose the API‘s documentation
• Navigation between resources
• Spring Data REST provides that out of the box!
CSU33012 Goetz Botterweck 211

More Related Content

Similar to CSU33012-J-springboot(3).pdf

Module 6 _ Spring Boot for java application to begin
Module 6 _ Spring Boot for java application to beginModule 6 _ Spring Boot for java application to begin
Module 6 _ Spring Boot for java application to begin
Deepakprasad838637
 

Similar to CSU33012-J-springboot(3).pdf (20)

Intro to SpringBatch NoSQL 2021
Intro to SpringBatch NoSQL 2021Intro to SpringBatch NoSQL 2021
Intro to SpringBatch NoSQL 2021
 
Spring data jpa are used to develop spring applications
Spring data jpa are used to develop spring applicationsSpring data jpa are used to develop spring applications
Spring data jpa are used to develop spring applications
 
Initiation the Java web application project in the Google App Engine
Initiation the Java web application project in the Google App EngineInitiation the Java web application project in the Google App Engine
Initiation the Java web application project in the Google App Engine
 
2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdf2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdf
 
Intelligent Projects with Maven - DevFest Istanbul
Intelligent Projects with Maven - DevFest IstanbulIntelligent Projects with Maven - DevFest Istanbul
Intelligent Projects with Maven - DevFest Istanbul
 
Spring boot jpa
Spring boot jpaSpring boot jpa
Spring boot jpa
 
Spring MVC 5 & Hibernate 5 Integration
Spring MVC 5 & Hibernate 5 IntegrationSpring MVC 5 & Hibernate 5 Integration
Spring MVC 5 & Hibernate 5 Integration
 
O365 Developer Bootcamp NJ 2018 - Material
O365 Developer Bootcamp NJ 2018 - MaterialO365 Developer Bootcamp NJ 2018 - Material
O365 Developer Bootcamp NJ 2018 - Material
 
Spring Boot Workshop - January w/ Women Who Code
Spring Boot Workshop - January w/ Women Who CodeSpring Boot Workshop - January w/ Women Who Code
Spring Boot Workshop - January w/ Women Who Code
 
BMO - Intelligent Projects with Maven
BMO - Intelligent Projects with MavenBMO - Intelligent Projects with Maven
BMO - Intelligent Projects with Maven
 
dokumen.tips_introduction-to-spring-boot-58bb649a21ce5.pptx
dokumen.tips_introduction-to-spring-boot-58bb649a21ce5.pptxdokumen.tips_introduction-to-spring-boot-58bb649a21ce5.pptx
dokumen.tips_introduction-to-spring-boot-58bb649a21ce5.pptx
 
Module 6 _ Spring Boot for java application to begin
Module 6 _ Spring Boot for java application to beginModule 6 _ Spring Boot for java application to begin
Module 6 _ Spring Boot for java application to begin
 
Joget Workflow v5 Training Slides - Module 16 - Preparing Development Environ...
Joget Workflow v5 Training Slides - Module 16 - Preparing Development Environ...Joget Workflow v5 Training Slides - Module 16 - Preparing Development Environ...
Joget Workflow v5 Training Slides - Module 16 - Preparing Development Environ...
 
Joget Workflow v6 Training Slides - 16 - Preparing Development Environment
Joget Workflow v6 Training Slides - 16 - Preparing Development EnvironmentJoget Workflow v6 Training Slides - 16 - Preparing Development Environment
Joget Workflow v6 Training Slides - 16 - Preparing Development Environment
 
Micronaut Deep Dive - Devoxx Belgium 2019
Micronaut Deep Dive - Devoxx Belgium 2019Micronaut Deep Dive - Devoxx Belgium 2019
Micronaut Deep Dive - Devoxx Belgium 2019
 
Spring boot
Spring bootSpring boot
Spring boot
 
Plone FSR
Plone FSRPlone FSR
Plone FSR
 
Spring boot Introduction
Spring boot IntroductionSpring boot Introduction
Spring boot Introduction
 
Python Automation With Gauge + Selenium + API + Jenkins
Python Automation With Gauge + Selenium + API + JenkinsPython Automation With Gauge + Selenium + API + Jenkins
Python Automation With Gauge + Selenium + API + Jenkins
 
Expanding XPages with Bootstrap Plugins for Ultimate Usability
Expanding XPages with Bootstrap Plugins for Ultimate UsabilityExpanding XPages with Bootstrap Plugins for Ultimate Usability
Expanding XPages with Bootstrap Plugins for Ultimate Usability
 

Recently uploaded

Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments""Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
mphochane1998
 
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Kandungan 087776558899
 

Recently uploaded (20)

Bhubaneswar🌹Call Girls Bhubaneswar ❤Komal 9777949614 💟 Full Trusted CALL GIRL...
Bhubaneswar🌹Call Girls Bhubaneswar ❤Komal 9777949614 💟 Full Trusted CALL GIRL...Bhubaneswar🌹Call Girls Bhubaneswar ❤Komal 9777949614 💟 Full Trusted CALL GIRL...
Bhubaneswar🌹Call Girls Bhubaneswar ❤Komal 9777949614 💟 Full Trusted CALL GIRL...
 
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced LoadsFEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
 
Thermal Engineering -unit - III & IV.ppt
Thermal Engineering -unit - III & IV.pptThermal Engineering -unit - III & IV.ppt
Thermal Engineering -unit - III & IV.ppt
 
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKARHAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
 
Air Compressor reciprocating single stage
Air Compressor reciprocating single stageAir Compressor reciprocating single stage
Air Compressor reciprocating single stage
 
Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
Call Girls in South Ex (delhi) call me [🔝9953056974🔝] escort service 24X7
 
Learn the concepts of Thermodynamics on Magic Marks
Learn the concepts of Thermodynamics on Magic MarksLearn the concepts of Thermodynamics on Magic Marks
Learn the concepts of Thermodynamics on Magic Marks
 
Thermal Engineering Unit - I & II . ppt
Thermal Engineering  Unit - I & II . pptThermal Engineering  Unit - I & II . ppt
Thermal Engineering Unit - I & II . ppt
 
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments""Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
 
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptxHOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
 
Hazard Identification (HAZID) vs. Hazard and Operability (HAZOP): A Comparati...
Hazard Identification (HAZID) vs. Hazard and Operability (HAZOP): A Comparati...Hazard Identification (HAZID) vs. Hazard and Operability (HAZOP): A Comparati...
Hazard Identification (HAZID) vs. Hazard and Operability (HAZOP): A Comparati...
 
COST-EFFETIVE and Energy Efficient BUILDINGS ptx
COST-EFFETIVE  and Energy Efficient BUILDINGS ptxCOST-EFFETIVE  and Energy Efficient BUILDINGS ptx
COST-EFFETIVE and Energy Efficient BUILDINGS ptx
 
S1S2 B.Arch MGU - HOA1&2 Module 3 -Temple Architecture of Kerala.pptx
S1S2 B.Arch MGU - HOA1&2 Module 3 -Temple Architecture of Kerala.pptxS1S2 B.Arch MGU - HOA1&2 Module 3 -Temple Architecture of Kerala.pptx
S1S2 B.Arch MGU - HOA1&2 Module 3 -Temple Architecture of Kerala.pptx
 
Computer Lecture 01.pptxIntroduction to Computers
Computer Lecture 01.pptxIntroduction to ComputersComputer Lecture 01.pptxIntroduction to Computers
Computer Lecture 01.pptxIntroduction to Computers
 
Double Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torqueDouble Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torque
 
data_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdfdata_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdf
 
Work-Permit-Receiver-in-Saudi-Aramco.pptx
Work-Permit-Receiver-in-Saudi-Aramco.pptxWork-Permit-Receiver-in-Saudi-Aramco.pptx
Work-Permit-Receiver-in-Saudi-Aramco.pptx
 
Unleashing the Power of the SORA AI lastest leap
Unleashing the Power of the SORA AI lastest leapUnleashing the Power of the SORA AI lastest leap
Unleashing the Power of the SORA AI lastest leap
 
Online electricity billing project report..pdf
Online electricity billing project report..pdfOnline electricity billing project report..pdf
Online electricity billing project report..pdf
 
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
 

CSU33012-J-springboot(3).pdf

  • 1. CSU33012 Software Engineering J. Introduction to Spring Boot Goetz Botterweck
  • 2. Change Log 2023-10-09 First version 2023-10-11 Added Section 5 “Library (More Complex Example using Spring MVC Web Controller, JPA, Repositories, H2)” 2023-10-18 Using external services, first steps in GitLab 2 CSU33012 Goetz Botterweck
  • 3. J. Introduction to Spring Boot CSU33012 Goetz Botterweck 3
  • 4. References Online Courses (Bowman 2019) Bowman, Mary Ellen. Creating Your First Spring Boot Microservice, 1h 48min, Linked-in Learning, 2019. https://www.linkedin.com/learning/creating-your-first-spring-boot-microservice/ 4 CSU33012 Goetz Botterweck
  • 5. 1. Introduction to Spring Boot CSU33012 Goetz Botterweck 5
  • 7. Prerequisites • Java • Version 17 • java --version should print a version number like 17.x.x • Maven • mvn --version should print a version number like 3.9.x • IDE • IntelliJ IDEA Ultimate or • Visual Studio Code • Spring Tools for VSCode, https://spring.io/tools CSU33012 Goetz Botterweck 7
  • 8. Also useful • Tools for Working with REST APIs • postman • https://www.postman.com/downloads/ • mostly app, there is also online version at https://www.postman.com/ • httpie • https://httpie.io/ • mostly for terminal, there is also beta version of app • Account on the local GitLab installation • http://gitlab.scss.tcd.ie/ CSU33012 Goetz Botterweck 8
  • 9. 1.2 Apache Maven CSU33012 Goetz Botterweck 9
  • 10. Apache Maven • Download, https://maven.apache.org/download.cgi • Install, https://maven.apache.org/install.html • Unzip • Make sure that Maven’s bin directory is in your path • mvn --version should print a version number like 3.9.x • Apache in 5 minutes (see below) https://maven.apache.org/guides/getting-started/maven-in-five- minutes.html CSU33012 Goetz Botterweck 10 Image: https://en.m.wikipedia.org/wiki/File:Apache_Maven_logo.svg
  • 11. Apache Maven • Tool to build and manage software projects • Key Features • Dependency Management • Building Projects • Plugin-based Architecture • Benefits • Standardized Build Process • Consistent Project Structures • Alternative: Gradle CSU33012 Goetz Botterweck 11
  • 12. Maven Standard Directory Structure CSU33012 Goetz Botterweck 12 https://www.baeldung.com/maven-directory-structure
  • 13. Maven in 5min CSU33012 Goetz Botterweck 13 https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app - DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 - DinteractiveMode=false If running in Windows Powershell, Maven will raise a false error (pom.xml missing). Use cmd.exe or enclose parameters in quotes. https://stackoverflow.com/questions/16348459/error-the-goal- you-specified-requires-a-project-to-execute-but-there-is-no-pom
  • 14. Maven in 5min CSU33012 Goetz Botterweck 14 https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html cd my-app mvn package
  • 15. Maven in 5min – pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project> CSU33012 Goetz Botterweck 15 https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
  • 16. Maven in 5min CSU33012 Goetz Botterweck 16 https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App Later in Spring Boot you can start the jar directly without classpath, somewhat like java -jar target/my-app-1.0-SNAPSHOT.jar Here that does not work.
  • 17. 1.3 Basic Concepts Used in Spring Boot CSU33012 Goetz Botterweck 17
  • 18. Inversion of Control Previously • Main program (our program) • Controls flow of data and execution • Calls components • Sets up references between components CSU33012 Goetz Botterweck 18 With Inversion of Control • Framework (or Container) • Controls flow of data and execution • Is responsible for„plumbing“ everything together • Our Program • We only have to specify what we need • In Spring Boot • through annotating parts of a Java program with tags like @Service @SpringBootApplication • Uses Dependency Injection
  • 19. Key Concepts in Inversion of Control • Inversion of Control (IoC) • Framework (a.k.a. the container) is responsible for handling the start-up, including the instantiation of components (Beans), connecting them • Dependency Injection • Mechanism to implement IoC • Component does not set up dependency, instead the framework/container „injects“ them into the component. • Application Context • Spring Boot‘s version of framework/container CSU33012 Goetz Botterweck 19
  • 20. Inversion of Control Examples CSU33012 Goetz Botterweck 20 package ie.tcd.scss.learningspring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LearningSpringApplication { public static void main(String[] args) { SpringApplication.run(LearningSpringApplication.class, args); } } Upon starting the application Spring Boot will scan for annotations and instantiate and connect the necessary components.
  • 21. Inversion of Control Examples CSU33012 Goetz Botterweck 21 (Turnquist, p. 6-7) ... class BookExample { @Bean public BookRepository bookRepository(DataSource dataSource) { return new BookRepository(); } ... } • bookRepository needs a DataSource. • Application context needs to provide one, either has it or will go create one and return it. • Application context is responsible for management during execution • bookRepository executes its code while referencing the app context’s DataSource. • BookRepository is registered in the application context under the name bookRepository.
  • 22. 1.4 What is Spring Boot? CSU33012 Goetz Botterweck 22
  • 23. Spring Boot • Open-source, Java-based framework • “Beginners version” of Spring (based on Spring, makes it easier to get started) • Conventions over Configuration • Autoconfiguration • Upon start application examines the parts of that application • Adds additional Spring beans to application context • Standalone • No deployment, instead web server embedded in the application CSU33012 Goetz Botterweck 23
  • 24. 1.5 Spring Boot Starters CSU33012 Goetz Botterweck 24
  • 25. Spring Boot Starters CSU33012 Goetz Botterweck 25 https://www.baeldung.com/spring-boot-starters <?xml version="1.0" encoding="UTF-8"?> <project …> ... <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> ... </project> Adding Starter to pom.xml as dependency … … ensures additional libraries are included and annotations are available These are what is added to your project, when you select „Dependencies“ in Spring Initializr (more later)
  • 26. Spring Boot Starters – Examples • Spring Web – applications that offer REST-based APIs, uses Spring MVC, uses Apache Tomcat as the default embedded container. • Spring WebFlux – alternative to Spring Web, based on reactive streams • See examples in (Larsson 2023) • See https://www.baeldung.com/spring-mvc-async-vs-webflux • Spring Data JPA – accessing data via Java Persistence API (JPA) • Spring Test – testing your application • Spring Mail – sending emails Source: https://www.baeldung.com/spring-boot-starters CSU33012 Goetz Botterweck 26
  • 27. 2. Creating a Web Application with Spring Boot CSU33012 Goetz Botterweck 27
  • 28. So, what do we need? • We want to setup a basic project and its file structure • Based on Java, Maven, Spring Boot • We want to build a simple web application (that responds to a HTTP request with a response) /hello → Hello World! CSU33012 Goetz Botterweck 28
  • 29. 2.1 Spring Initializr CSU33012 Goetz Botterweck 29
  • 30. Spring Initializr CSU33012 Goetz Botterweck 30 • Fast way to set up a Spring Boot project • Build management • Maven • Gradle • Language • Java • Kotlin • Groovy • Spring Boot Version • Project Metadata • Packaging • Java Version • Dependencies (to add functionality)
  • 31. Spring Initializr CSU33012 Goetz Botterweck 31 • Fast way to set up a Spring Boot project • Build management • Maven • Gradle • Language • Java • Kotlin • Groovy • Spring Boot Version • Project Metadata • Packaging • Java Version • Dependencies (to add functionality) Spring Initializr API start.spring.io Website Spring Initializr Support in IntelliJ IDEA „Spring Initializr Java Support“ vscode-spring-initializr In VS Code
  • 32. Spring Initializr CSU33012 Goetz Botterweck 32 • http://start.spring.io • Project = Maven • Language = Java • Spring Boot = default (3.x) • Group = ie.tcd.scss • Artifact = learningspring • Packaging = Jar • Java = 17 • Dependencies • Spring Web • Generate • Download and unpack • Open project in your IDE
  • 33. Resulting Project in IntelliJ IDEA CSU33012 Goetz Botterweck 33
  • 34. Resulting Project in IntelliJ IDEA CSU33012 Goetz Botterweck 34
  • 35. HELP.md contains some helpful references CSU33012 Goetz Botterweck 35
  • 36. The New App CSU33012 Goetz Botterweck 36
  • 37. 2.2 Running the Application CSU33012 Goetz Botterweck 37
  • 38. Running the Application CSU33012 Goetz Botterweck 38 Alternative
  • 39. http://localhost:8080/ CSU33012 Goetz Botterweck 39 Toolbar “Run Actions” More run and debug actions
  • 41. Same in Postman CSU33012 Goetz Botterweck 41 https://www.postman.com/
  • 42. 3. Hello World (Minimal Spring MVC Web Controller) CSU33012 Goetz Botterweck 42
  • 43. 3.1 Creating a Test CSU33012 Goetz Botterweck 43
  • 44. • Go to src/test/java/ie.tcd.scss.learningspring • Create new class HelloControllerTest • Add content on next slide • Right click src/test/java > Run all tests (Ctrl-Shift-F10) • Inspect test results CSU33012 Goetz Botterweck 44
  • 45. HelloControllerTest package ie.tcd.scss.learningspring; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test public void helloShouldReturnDefaultMessage() { ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/hello", String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("Hello, World!"); } @Test public void helloShouldReturnDefaultMessageDetailed() { String url = "http://localhost:" + port + "/hello"; String expectedResult = "Hello, World!"; Class<String> returnType = String.class; ResponseEntity<String> response = this.restTemplate.getForEntity(url, returnType); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains(expectedResult); } } CSU33012 Goetz Botterweck 45
  • 46. HelloControllerTest package ie.tcd.scss.learningspring; import ... @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; ... @Test public void helloShouldReturnDefaultMessageDetailed() { String url = "http://localhost:" + port + "/hello"; String expectedResult = "Hello, World!"; Class<String> returnType = String.class; ResponseEntity<String> response = this.restTemplate.getForEntity(url, returnType); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains(expectedResult); } } CSU33012 Goetz Botterweck 46 Step-by-step
  • 47. HelloControllerTest package ie.tcd.scss.learningspring; import ... @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test public void helloShouldReturnDefaultMessage() { ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/hello", String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("Hello, World!"); } ... } CSU33012 Goetz Botterweck 47 Same test, but less intermediate steps
  • 49. Copying Text from Slides PDF CSU33012 Goetz Botterweck 49 If text copies from PDF looks garbled Edit > Find > Replace (Ctrl + R) Search for: u000B (or select the weird VT character) Replace with: n Activate regular expressions .* (Alt + X) Replace all (Alt + A)
  • 50. HelloControllerTest in IntelliJ IDEA CSU33012 Goetz Botterweck 50
  • 51. 3.2 Running the Test CSU33012 Goetz Botterweck 51
  • 55. Command Line CSU33012 Goetz Botterweck 55 mvn test
  • 56. Running Tests on Command Line CSU33012 Goetz Botterweck 56 Enter = Just run the command Ctrl + Enter = Run with IDE integration
  • 57. Running Tests on Command Line (plain) CSU33012 Goetz Botterweck 57
  • 58. Running Tests on Command Line (with Integration) CSU33012 Goetz Botterweck 58
  • 59. 3.3 Creating the MVC Web Controller CSU33012 Goetz Botterweck 59
  • 60. Create New Class HelloController CSU33012 Goetz Botterweck 60
  • 61. Create New Class HelloController CSU33012 Goetz Botterweck 61
  • 62. HelloController CSU33012 Goetz Botterweck 62 package ie.tcd.scss.learningspring; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, World!"; } }
  • 64. Run All Tests Again CSU33012 Goetz Botterweck 64
  • 67. 4. Music Styles (Storing and Retrieving Info with Spring MVC Web Controller) CSU33012 Goetz Botterweck 67
  • 68. Music Styles • Can store mapping artist->genre • The Weeknd → R&B • Eminem → Hip Hop • Michael Jackson → Pop • Adele → Pop • Can retrieve info on given artist • In: Eminem Out: Hip Hop • Can list all artists for a given genre • In: Pop Out: Michael Jackson, Adele CSU33012 Goetz Botterweck 68
  • 69. class ArtistInfo package com.example.musicstyles; public class ArtistInfo { private String artist; private String musicStyle; public ArtistInfo(String artist, String musicStyle) { this.artist = artist; this.musicStyle = musicStyle; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } public String getMusicStyle() { return musicStyle; } public void setMusicStyle(String musicStyle) { this.musicStyle = musicStyle; } } CSU33012 Goetz Botterweck 69 Image: https://unsplash.com/photos/Km5J-KCP1Mw
  • 70. Spring Initializr CSU33012 Goetz Botterweck 70 • http://start.spring.io • Project = Maven • Language = Java • Spring Boot = default (3.x) • Group = ie.tcd.scss • Artifact = musicstyles • Packaging = Jar • Java = 17 • Dependencies • Spring Web • Generate • Download and unpack • Open project in your IDE
  • 71. 4.1 Creating and Running a Test CSU33012 Goetz Botterweck 71
  • 72. class MusicstylesControllerTest (1 of 2) CSU33012 Goetz Botterweck 72 package ie.tcd.scss.musicstyles; import ... @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MusicstylesControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test void addArtistTest() { ArtistInfo artistInfo = new ArtistInfo("The Weeknd", "RnB"); ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("Artist added successfully!", response.getBody()); } @Test void getMusicStyleTest() { ArtistInfo artistInfo = new ArtistInfo("Eminem", "Hip Hop"); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class); ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/Eminem", String.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("Hip Hop", response.getBody()); } ... }
  • 73. class MusicstylesControllerTest (2 of 2) CSU33012 Goetz Botterweck 73 package ie.tcd.scss.musicstyles; import … @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MusicstylesControllerTest { ... @Test void getArtistsByMusicStyleTest() { ArtistInfo artistInfo1 = new ArtistInfo("Adele", "Pop"); ArtistInfo artistInfo2 = new ArtistInfo("Taylor Swift", "Pop"); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo1, String.class); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo2, String.class); ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/Pop/artists", List.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(response.getBody().contains("Adele")); assertTrue(response.getBody().contains("Taylor Swift")); } @Test void getNonExistentArtistTest() { ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/NonExistentArtist", String.class); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } @Test void getArtistsByNonExistentMusicStyleTest() { ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/NonExistentStyle/artists", List.class); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } }
  • 74. class MusicstylesControllerTest (full source) CSU33012 Goetz Botterweck 74 package ie.tcd.scss.musicstyles; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MusicstylesControllerTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test void addArtistTest() { ArtistInfo artistInfo = new ArtistInfo("The Weeknd", "RnB"); ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("Artist added successfully!", response.getBody()); } @Test void getMusicStyleTest() { ArtistInfo artistInfo = new ArtistInfo("Eminem", "Hip Hop"); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo, String.class); ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/Eminem", String.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("Hip Hop", response.getBody()); } @Test void getArtistsByMusicStyleTest() { ArtistInfo artistInfo1 = new ArtistInfo("Adele", "Pop"); ArtistInfo artistInfo2 = new ArtistInfo("Taylor Swift", "Pop"); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo1, String.class); restTemplate.postForEntity("http://localhost:" + port + "/api/artists", artistInfo2, String.class); ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/Pop/artists", List.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(response.getBody().contains("Adele")); assertTrue(response.getBody().contains("Taylor Swift")); } @Test void getNonExistentArtistTest() { ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/api/artists/NonExistentArtist", String.class); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } @Test void getArtistsByNonExistentMusicStyleTest() { ResponseEntity<List> response = restTemplate.getForEntity("http://localhost:" + port + "/api/musicstyles/NonExistentStyle/artists", List.class); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } }
  • 77. 4.2 Testing a REST API with Postman CSU33012 Goetz Botterweck 77
  • 78. Testing a REST API with Postman CSU33012 Goetz Botterweck 78
  • 79. Testing a REST API with Postman CSU33012 Goetz Botterweck 79
  • 80. Testing a REST API with Postman CSU33012 Goetz Botterweck 80
  • 82. class MusicstylesController (1 of 2) CSU33012 Goetz Botterweck 82 package ie.tcd.scss.musicstyles; import ... @RestController @RequestMapping("/api") public class MusicstylesController { private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>(); @PostMapping("/artists") public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) { artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle()); return ResponseEntity.ok("Artist added successfully!"); } @GetMapping("/artists/{artistName}") public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) { String musicStyle = artistStyleMap.get(artistName); if (musicStyle == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!"); } return ResponseEntity.ok(musicStyle); } ... }
  • 83. class MusicstylesController (1 of 2) CSU33012 Goetz Botterweck 83 package ie.tcd.scss.musicstyles; import ... @RestController @RequestMapping("/api") public class MusicstylesController { private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>(); @PostMapping("/artists") public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) { artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle()); return ResponseEntity.ok("Artist added successfully!"); } @GetMapping("/artists/{artistName}") public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) { String musicStyle = artistStyleMap.get(artistName); if (musicStyle == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!"); } return ResponseEntity.ok(musicStyle); } ... } URL path pattern with path variable “artistName”
  • 84. class MusicstylesController (2 of 2) CSU33012 Goetz Botterweck 84 package ie.tcd.scss.musicstyles; import ... @RestController @RequestMapping("/api") public class MusicstylesController { ... @GetMapping("/musicstyles/{musicStyleName}/artists") public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) { List<String> artists = artistStyleMap.entrySet().stream() .filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName)) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (artists.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList()); } return ResponseEntity.ok(artists); } }
  • 85. class MusicstylesController (2 of 2) CSU33012 Goetz Botterweck 85 package ie.tcd.scss.musicstyles; import ... @RestController @RequestMapping("/api") public class MusicstylesController { ... @GetMapping("/musicstyles/{musicStyleName}/artists") public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) { List<String> artists = artistStyleMap.entrySet().stream() .filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName)) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (artists.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList()); } return ResponseEntity.ok(artists); } } Converts artistStyleMap into stream of its entries Filter for entries where the value is <musicStyleName> ignoring case Transform each remaining entry into its key (the artist name in this case) Collect the results into a List<Strings> Lambda expression URL path pattern with path variable “musicStyleName”
  • 86. class MusicstylesController (full source) CSU33012 Goetz Botterweck 86 package ie.tcd.scss.musicstyles; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @RestController @RequestMapping("/api") public class MusicstylesController { private final Map<String, String> artistStyleMap = new ConcurrentHashMap<>(); @PostMapping("/artists") public ResponseEntity<String> addArtist(@RequestBody ArtistInfo artistInfo) { artistStyleMap.put(artistInfo.getArtist(), artistInfo.getMusicStyle()); return ResponseEntity.ok("Artist added successfully!"); } @GetMapping("/artists/{artistName}") public ResponseEntity<String> getMusicStyle(@PathVariable String artistName) { String musicStyle = artistStyleMap.get(artistName); if (musicStyle == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Artist not found!"); } return ResponseEntity.ok(musicStyle); } @GetMapping("/musicstyles/{musicStyleName}/artists") public ResponseEntity<List<String>> getArtists(@PathVariable String musicStyleName) { List<String> artists = artistStyleMap.entrySet().stream() .filter(entry -> entry.getValue().equalsIgnoreCase(musicStyleName)) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (artists.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.emptyList()); } return ResponseEntity.ok(artists); } }
  • 87. 4.4 Testing the Controller CSU33012 Goetz Botterweck 87
  • 89. Testing the Controller CSU33012 Goetz Botterweck 89
  • 90. Testing the Controller CSU33012 Goetz Botterweck 90
  • 91. Testing the Controller With Postman CSU33012 Goetz Botterweck 91
  • 92. Testing the Controller With Postman CSU33012 Goetz Botterweck 92
  • 93. Testing the Controller With Postman CSU33012 Goetz Botterweck 93
  • 99. 5. Library (More Complex Example using Spring MVC Web Controller, JPA, Repositories, H2) CSU33012 Goetz Botterweck 99
  • 100. 5.1 Setting up the Initial Project CSU33012 Goetz Botterweck 100
  • 101. • Based on „Explore California“ in (Bowman 2019) • Simplified and added explanations • Updated to Spring Boot 3 • Changed namespaces javax.somepackage → jakarta.somepackage CSU33012 Goetz Botterweck 101
  • 102. 5.1.1 Creating Project with start.spring.io CSU33012 Goetz Botterweck 102
  • 103. Spring Initializr CSU33012 Goetz Botterweck 103 • http://start.spring.io • Project = Maven • Language = Java • Spring Boot = default (3.x) • Group = ie.tcd.scss • Artifact = library • Packaging = Jar • Java = 17 • Dependencies • Spring Web • Rest Repositories • Spring Data JPA • H2 Database • Generate • Download and unpack • Open project in your IDE
  • 106. Creating Remote Repository (GitHub or GitLab) CSU33012 Goetz Botterweck 106 If you want to push an existing repository later do NOT create a README
  • 111. 5.1.3 Creating Project with IntelliJ Wizard CSU33012 Goetz Botterweck 111
  • 112. Spring Initializr Support in IntelliJ New Project Wizard CSU33012 Goetz Botterweck 112
  • 113. New Project CSU33012 Goetz Botterweck 113
  • 114. Initial Commit CSU33012 Goetz Botterweck 114
  • 115. Initial Commit CSU33012 Goetz Botterweck 115
  • 116. 5.1.4 Adding Remote Repository CSU33012 Goetz Botterweck 116
  • 124. 5.2 Creating JPA Entities (Classes to Handle Data) CSU33012 Goetz Botterweck 124
  • 125. Domain Model • Book • Author • Library Section CSU33012 Goetz Botterweck 125
  • 126. Simplified Domain Model • Book • Authors represented as simple string • Identifier “id” of type Integer • Library Section • Identifier “code” of type String CSU33012 Goetz Botterweck 126
  • 127. Jakarta Persistence API (JPA) • Formerly known as Java Persistence API • Application Programming Interface (set of interfaces) to • handle data in Java programs • manage the mapping of Java classes to tables in databases • Query Language JPQL • Implementations • Eclipse Link (reference implementation) • Hibernate • Spring Data JPA uses JPA, expects a JPA implementation CSU33012 Goetz Botterweck 127
  • 128. CSU33012 Goetz Botterweck 128 https://www.youtube.com/watch?v=4Py9RTVWyvE
  • 129. H2 Database • Minimal, fast in-memory database • Good for early experiments, proof-of-concept implementations • https://www.h2database.com/ • No need to install! • https://www.baeldung.com/spring-boot-h2-database CSU33012 Goetz Botterweck 129
  • 130. New Package CSU33012 Goetz Botterweck 130
  • 131. New Package CSU33012 Goetz Botterweck 131
  • 132. class Book CSU33012 Goetz Botterweck 132 package ie.tcd.scss.library.domain; import jakarta.persistence.*; @Entity public class Book { @Id @GeneratedValue private Long id; @Column private String title; @Column private String authors; @Column private Integer publicationYear; @Column private Integer price; @Column private String isbn; @Column(length = 500) private String keywords; @Column(length = 2000) private String description; @ManyToOne private LibrarySection librarySection; } JPA Annotation JPA Annotations
  • 133. class LibrarySection CSU33012 Goetz Botterweck 133 package ie.tcd.scss.library.domain; import jakarta.persistence.*; @Entity public class LibrarySection { @Id private String code; @Column private String name; @OneToMany private Set<Book> books; }
  • 134. Generating Missing Elements for classes, not needed for enumerations • Constructors • One without any arguments – make private, so it does not get used • One with all arguments (except @GeneratedValue id) • Getters • Setters • except for @GeneratedValue id • toString() • equals() • hashCode() (Can be avoided with lombok, but for now we use the standard approach.) CSU33012 Goetz Botterweck 134
  • 135. Generating Missing Elements CSU33012 Goetz Botterweck 135
  • 136. class Book (Full Source) package ie.tcd.scss.library.domain; import jakarta.persistence.*; import java.util.Objects; @Entity public class Book { @Id @GeneratedValue private Long id; @Column private String title; @Column private String authors; @Column private Integer publicationYear; @Column private Integer price; @Column private String isbn; @Column(length = 500) private String keywords; @Column(length = 2000) private String description; @ManyToOne private LibrarySection librarySection; public Book(String title, String authors, Integer publicationYear, Integer price, String isbn, String keywords, String description, LibrarySection librarySection) { this.title = title; this.authors = authors; this.publicationYear = publicationYear; this.price = price; this.isbn = isbn; this.keywords = keywords; this.description = description; this.librarySection = librarySection; } protected Book() { } public Long getId() { return id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthors() { return authors; } public void setAuthors(String authors) { this.authors = authors; } public Integer getPublicationYear() { return publicationYear; } CSU33012 Goetz Botterweck 136 public void setPublicationYear(Integer publicationYear) { this.publicationYear = publicationYear; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getKeywords() { return keywords; } public void setKeywords(String keywords) { this.keywords = keywords; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public LibrarySection getLibrarySection() { return librarySection; } public void setLibrarySection(LibrarySection librarySection) { this.librarySection = librarySection; } @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) && Objects.equals(title, book.title) && Objects.equals(authors, book.authors) && Objects.equals(publicationYear, book.publicationYear) && Objects.equals(price, book.price) && Objects.equals(isbn, book.isbn) && Objects.equals(keywords, book.keywords) && Objects.equals(description, book.description) && Objects.equals(librarySection, book.librarySection); } @Override public int hashCode() { return Objects.hash(id, title, authors, publicationYear, price, isbn, keywords, description, librarySection); } @Override public String toString() { return "Book{" + "id=" + id + ", title='" + title + ''' + ", authors='" + authors + ''' + ", publicationYear=" + publicationYear + ", price=" + price + ", isbn='" + isbn + ''' + ", keywords='" + keywords + ''' + ", description='" + description + ''' + ", librarySection=" + librarySection + '}'; } }
  • 137. class LibrarySection (Full Source) CSU33012 Goetz Botterweck 137 package ie.tcd.scss.library.domain; import jakarta.persistence.*; import java.util.Objects; @Entity public class LibrarySection { @Id private String code; @Column private String name; public LibrarySection(String code, String name) { this.code = code; this.name = name; } protected LibrarySection() { } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LibrarySection that = (LibrarySection) o; return Objects.equals(code, that.code) && Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(code, name); } @Override public String toString() { return "LibrarySection{" + "code='" + code + ''' + ", name='" + name + ''' + '}'; } }
  • 139. 5.3 Creating Spring Data Repositories CSU33012 Goetz Botterweck 139
  • 140. Creating Spring Data Repositories • We declare an interface that extends public interface CrudRepository<T, I> (CRUD = Create, Read, Update, Delete) • T = Data type that the repository manages, e.g., Book • I = Data type of the entity ID, e.g., Integer • Spring Data automatically provides all necessary methods to operate on that Repository CSU33012 Goetz Botterweck 140
  • 141. Creating Spring Data Repositories For instance after declaring public interface BookRepository extends CrudRepository<Book, Integer> We automatically get Book save(Book b) Iterable<Book> saveAll(Iterable<Book> iterable) CSU33012 Goetz Botterweck 141
  • 142. Creating Spring Data Repositories CSU33012 Goetz Botterweck 142
  • 143. Creating Spring Data Repositories CSU33012 Goetz Botterweck 143
  • 144. Creating Spring Data Repositories CSU33012 Goetz Botterweck 144
  • 145. Creating Spring Data Repositories CSU33012 Goetz Botterweck 145
  • 146. interface BookRepository CSU33012 Goetz Botterweck 146 package ie.tcd.scss.library.repo; import ie.tcd.scss.library.domain.Book; import org.springframework.data.repository.CrudRepository; public interface BookRepository extends CrudRepository<Book, Long> { } Basic default functionality is available automatically. If we need additional methods, we need to declare them as part of the interface.
  • 147. interface LibrarySectionRepository CSU33012 Goetz Botterweck 147 package ie.tcd.scss.library.repo; import ie.tcd.scss.library.domain.LibrarySection; import org.springframework.data.repository.CrudRepository; public interface LibrarySectionRepository extends CrudRepository<LibrarySection, String> { }
  • 148. Spring Data JPA Repository Interfaces public interface BookRepository extends CrudRepository<Book, Integer> Book save(Book b) Iterable<Book> saveAll(Iterable<Book> iterable) void deleteById(Integer id) void delete(Book book) void delete(Iterable<Book> iterable) Void deleteAll() Optional<Book> findById(Integer id) boolean existsById(Integer integer) Iterable<Book> findAll() Iterable<Book> findAllById(Iterable<Integer> iterable) long count() CSU33012 Goetz Botterweck 148
  • 150. 5.4 Creating Services (with Injected Repositories) CSU33012 Goetz Botterweck 150
  • 154. class BookService CSU33012 Goetz Botterweck 154 package ie.tcd.scss.library.service; public class BookService { }
  • 155. Details of Service • Service class tagged @Service • Private variable(s) holding reference(s) to all needed repositories • Final • Constructor • expects references to repositories • is „autowired“ by tag @Autowired • Methods • createBook() • Create a new book • lookup() – return (an Iterable with) all books • total()– return total number of books CSU33012 Goetz Botterweck 155
  • 156. class BookService (Overview) CSU33012 Goetz Botterweck 156 package ie.tcd.scss.library.service; import ... @Service public class BookService { private final BookRepository bookRepository; private final LibrarySectionRepository librarySectionRepository; @Autowired public BookService(BookRepository bookRepository, LibrarySectionRepository librarySectionRepository) { this.bookRepository = bookRepository; this.librarySectionRepository = librarySectionRepository; } public Book createBook(String title, String authors, Integer publicationYear, Integer price, String isbn, String keywords, String description, String librarySectionCode) { ... find librarySection return bookRepository.save(new Book(...)); } public Iterable<Book> lookup() {...} public long total() {...} }
  • 157. How Does Autowiring and Depdendency Injection Work? • Defining Beans • Classes eligible for autowiring must be marked with tags like @Component, @Service, @Repository, @Controller • @Autowired annotation • Instructs Spring to “inject” the dependency automatically • Field Injection • Constructor Injection – recommend, what we used above • Setter Injection CSU33012 Goetz Botterweck 157
  • 158. How Does Autowiring and Depdendency Injection Work? • Component Scanning • Dependency Injection • When a bean (e.g., BookService) is created, Spring checks (e.g. by looking at constructor) if there are any dependencies that need to be injected. • In the case of our BookService, it needs an instance of BookRepository and an instance of LibrarySectionRepository • Spring will search among the beans in its context for one that matches the required type (e.g., BookRepository). If it finds a match, it will inject that bean into the dependent one (BookService). Otherwise exception. CSU33012 Goetz Botterweck 158
  • 159. How Does Autowiring and Depdendency Injection Work? Much more details at https://www.baeldung.com/spring-dependency- injection CSU33012 Goetz Botterweck 159
  • 160. class BookService (Full Source) CSU33012 Goetz Botterweck 160 package ie.tcd.scss.library.service; import ie.tcd.scss.library.domain.Book; import ie.tcd.scss.library.domain.LibrarySection; import ie.tcd.scss.library.repo.BookRepository; import ie.tcd.scss.library.repo.LibrarySectionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Optional; @Service public class BookService { private final BookRepository bookRepository; private final LibrarySectionRepository librarySectionRepository; @Autowired public BookService(BookRepository bookRepository, LibrarySectionRepository librarySectionRepository) { this.bookRepository = bookRepository; this.librarySectionRepository = librarySectionRepository; } public Book createBook(String title, String authors, Integer publicationYear, Integer price, String isbn, String keywords, String description, String librarySectionCode) { LibrarySection librarySection = librarySectionRepository .findById(librarySectionCode) .orElseThrow(() -> new RuntimeException("Library Section does not exist: " + librarySectionCode)); return bookRepository.save(new Book(title, authors, publicationYear, price, isbn, keywords, description, librarySection)); } public Iterable<Book> lookup() { return bookRepository.findAll(); } public long total() { return bookRepository.count(); } }
  • 161. class LibrarySectionService CSU33012 Goetz Botterweck 161 package ie.tcd.scss.library.service; import ie.tcd.scss.library.domain.LibrarySection; import ie.tcd.scss.library.repo.LibrarySectionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class LibrarySectionService { private final LibrarySectionRepository librarySectionRepository; @Autowired LibrarySectionService(LibrarySectionRepository librarySectionRepository) { this.librarySectionRepository = librarySectionRepository; } public LibrarySection createLibrarySection(String code, String name) { return librarySectionRepository .findById(code) .orElse(librarySectionRepository.save(new LibrarySection(code, name))); } public Iterable<LibrarySection> lookup() { return librarySectionRepository.findAll(); } public long total() { return librarySectionRepository.count(); } }
  • 163. 5.5 Filling the Database With Some Test Data CSU33012 Goetz Botterweck 163
  • 164. class LibraryApplication (Overview) CSU33012 Goetz Botterweck 164 package ie.tcd.scss.library; import ... @SpringBootApplication public class LibraryApplication implements CommandLineRunner { @Autowired private LibrarySectionService librarySectionService; @Autowired private BookService bookService; public static void main(String[] args) { SpringApplication.run(LibraryApplication.class, args); } @Override public void run(String... args) throws Exception { createLibrarySections(); createBooks(); } private void createLibrarySections() { librarySectionService.createLibrarySection("Scifi", "Science Fiction Books"); librarySectionService.createLibrarySection("Fiction", "Fiction Books"); ... } private void createBooks() { bookService.createBook("The Hobbit", "J.R.R. Tolkien", 2023, 99, "ISBN 978-123456", "hobbit, tolkien, fantasy", "A book about a hobbit", "Fiction"); bookService.createBook("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1978, 49, "ISBN 978-456789", "comedy, bbc, scifi", "A book about life and all the rest", "Scifi"); } } Field injection Required by CommandLineRunner
  • 165. class LibraryApplication (Full Source) CSU33012 Goetz Botterweck 165 package ie.tcd.scss.library; import ie.tcd.scss.library.service.BookService; import ie.tcd.scss.library.service.LibrarySectionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LibraryApplication implements CommandLineRunner { @Autowired private LibrarySectionService librarySectionService; @Autowired private BookService bookService; public static void main(String[] args) { SpringApplication.run(LibraryApplication.class, args); } @Override public void run(String... args) throws Exception { createLibrarySections(); createBooks(); } private void createLibrarySections() { librarySectionService.createLibrarySection("Scifi", "Science Fiction Books"); librarySectionService.createLibrarySection("Fiction", "Fiction Books"); librarySectionService.createLibrarySection("Non-fiction", "Non-Fiction Books"); librarySectionService.createLibrarySection("CS", "Computer Science Books"); } private void createBooks() { bookService.createBook("The Hobbit", "J.R.R. Tolkien", 2023, 99, "ISBN 978-123456", "hobbit, tolkien, fantasy", "A book about a hobbit", "Fiction"); bookService.createBook("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1978, 49, "ISBN 978-456789", "comedy, bbc, scifi", "A book about life and all the rest", "Scifi"); } }
  • 167. 5.6 Adding Controller (Using DTOs) CSU33012 Goetz Botterweck 167
  • 168. • Why Create BookController? • To provide REST access to business logic, e.g., create Books and add them into a LibrarySection in one go • We need to get extra data (code of LibrarySection) • Two approaches • Get extra data in request parameters • Get extra data in Data Transfer Object (DTO) • i.e., one class/object that can hold all data needed during the request CSU33012 Goetz Botterweck 168
  • 169. class BookController (using Request Parameter) CSU33012 Goetz Botterweck 169 package ie.tcd.scss.library.controller; import ie.tcd.scss.library.domain.Book; import ie.tcd.scss.library.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(path = "/booksController") public class BookController { private BookService bookService; @Autowired public BookController(BookService bookService) { this.bookService = bookService; } protected BookController() { } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void createBook(@RequestBody Book book, @RequestParam String librarySectionCode) { bookService.createBook(book.getTitle(), book.getAuthors(), book.getPublicationYear(), book.getPrice(), book.getIsbn(), book.getKeywords(), book.getDescription(), librarySectionCode); } }
  • 170. class BookControllerWithDto (Using DTOs) CSU33012 Goetz Botterweck 170 package ie.tcd.scss.library.controller; import ie.tcd.scss.library.domain.Book; import ie.tcd.scss.library.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(path = "/booksControllerWithDto") public class BookControllerWithDto { private BookService bookService; @Autowired public BookControllerWithDto(BookService bookService) { this.bookService = bookService; } protected BookControllerWithDto() { } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void createBook(@RequestBody BookDto bookDto) { Book book = bookDto.getBook(); String librarySectionCode = bookDto.getLibrarySectionCode(); bookService.createBook(book.getTitle(), book.getAuthors(), book.getPublicationYear(), book.getPrice(), book.getIsbn(), book.getKeywords(), book.getDescription(), librarySectionCode); } }
  • 171. class BookDto CSU33012 Goetz Botterweck 171 package ie.tcd.scss.library.controller; import ie.tcd.scss.library.domain.Book; public class BookDto { private Book book; private String librarySectionCode; public BookDto(Book book, String librarySectionCode) { this.book = book; this.librarySectionCode = librarySectionCode; } protected BookDto() { } public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } public String getLibrarySectionCode() { return librarySectionCode; } public void setLibrarySectionCode(String librarySectionCode) { this.librarySectionCode = librarySectionCode; } }
  • 173. 5.7 Testing the Application and ist API CSU33012 Goetz Botterweck 173
  • 180. 5.8 First Steps in GitLab CI/CD CSU33012 Goetz Botterweck 180
  • 189. GitLab Pipelines CSU33012 Goetz Botterweck 189 https://docs.gitlab.com/runner/install/
  • 190. 5.9 Accessing an External Service via HTTP REST CSU33012 Goetz Botterweck 190
  • 191. • Source available at https://gitlab.scss.tcd.ie/botterwg/demo-gitlabanalysis CSU33012 Goetz Botterweck 191
  • 196. 5.8.2 Retrieving Data as String CSU33012 Goetz Botterweck 196
  • 197. class GitHubAnalysisApplication CSU33012 Goetz Botterweck 197 package ie.tcd.scss.githubanalysis; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class GitHubAnalysisApplication { public static void main(String[] args) { SpringApplication.run(GitHubAnalysisApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
  • 198. class GitHubController CSU33012 Goetz Botterweck 198 package ie.tcd.scss.githubanalysis.controller; import ie.tcd.scss.githubanalysis.service.GitHubService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class GitHubController { private final GitHubService gitHubService; public GitHubController(GitHubService gitHubService) { this.gitHubService = gitHubService; } @GetMapping("/github/user/{username}") public String getGitHubUserProfile(@PathVariable String username) { return gitHubService.getGitHubUserProfile(username); } }
  • 199. class GitHubService CSU33012 Goetz Botterweck 199 package ie.tcd.scss.githubanalysis.service; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class GitHubService { private final RestTemplate restTemplate; public GitHubService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public String getGitHubUserProfile(String username) { String apiUrl = "https://api.github.com/users/" + username; return this.restTemplate.getForObject(apiUrl, String.class); } }
  • 201. 5.8.2 Retrieving Data in Structured Form CSU33012 Goetz Botterweck 201
  • 202. class GitHubUser CSU33012 Goetz Botterweck 202 package ie.tcd.scss.githubanalysis.model; public class GitHubUser { private String login; private int id; private String url; private String name; private String company; private String blog; private int followers; private int following; ... getter/setter ... }
  • 203. class GitHubUser (full source) CSU33012 Goetz Botterweck 203 package ie.tcd.scss.githubanalysis.model; public class GitHubUser { private String login; private int id; private String url; private String name; private String company; private String blog; private int followers; private int following; public GitHubUser() { } public GitHubUser(String login, int id, String url, String name, String company, String blog, int followers, int following) { this.login = login; this.id = id; this.url = url; this.name = name; this.company = company; this.blog = blog; this.followers = followers; this.following = following; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } public String getBlog() { return blog; } public void setBlog(String blog) { this.blog = blog; } public int getFollowers() { return followers; } public void setFollowers(int followers) { this.followers = followers; } public int getFollowing() { return following; } public void setFollowing(int following) { this.following = following; } }
  • 204. class GitHubService CSU33012 Goetz Botterweck 204 package ie.tcd.scss.githubanalysis.service; import ie.tcd.scss.githubanalysis.model.GitHubUser; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class GitHubService { private final RestTemplate restTemplate; public GitHubService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public String getGitHubUserProfileString(String username) { String apiUrl = "https://api.github.com/users/" + username; return this.restTemplate.getForObject(apiUrl, String.class); } public GitHubUser getGitHubUserProfile(String username) { String apiUrl = "https://api.github.com/users/" + username; return this.restTemplate.getForObject(apiUrl, GitHubUser.class); } }
  • 205. class GitHubController CSU33012 Goetz Botterweck 205 package ie.tcd.scss.githubanalysis.controller; import ie.tcd.scss.githubanalysis.service.GitHubService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import ie.tcd.scss.githubanalysis.model.GitHubUser; @RestController public class GitHubController { private final GitHubService gitHubService; public GitHubController(GitHubService gitHubService) { this.gitHubService = gitHubService; } @GetMapping("/github/user/{username}") public GitHubUser getGitHubUserProfile(@PathVariable String username) { return gitHubService.getGitHubUserProfile(username); } @GetMapping("/github/userString/{username}") public String getGitHubUserProfileString(@PathVariable String username) { return gitHubService.getGitHubUserProfileString(username); } }
  • 209. 5.9 RESTful APIs with Spring Data REST CSU33012 Goetz Botterweck 209
  • 210. RESTful APIs • Hypermedia-driven APIs • Roy Fielding (2000) • Hypermedia as the Engine of the Application State (HATEOAS) • Expose application state via API • Expose the API‘s documentation • Navigation between resources • Spring Data REST provides that out of the box! CSU33012 Goetz Botterweck 210
  • 211. RESTful APIs • Hypermedia-driven APIs • Roy Fielding (2000) • Hypermedia as the Engine of the Application State (HATEOAS) • Expose application state via API • Expose the API‘s documentation • Navigation between resources • Spring Data REST provides that out of the box! CSU33012 Goetz Botterweck 211