ECE366 - Lesson 5
Spring Boot and JPA
Instructor: Professor Hong
## What is the Spring Framework?
- Framework for providing comprehensive infrastructural support for developing Java Apps
- OOP (Object Oriented Programming) Best practices built in
- DRY (Don't Repeat Yourself) Principles
## What is Spring Boot?
- A tool that supports rapid development of web APIs
- Auto-configuration of Application Context
- Automatic Servlet Mappings
- Database support
- Automatic Controller Mappings
## Spring Initializr
- start.spring.io
- Project: Maven
- Language: Java
- Spring Boot: 4.0.3
- Group: com.ece366
- Artifact: rps
- Java 25
- Add spring web dependency
- Download the zip file and put it in your workspace
## Copy over JDBC libraries
- util - DataAccessObject, DataTransferObject
- DatabaseConnectionManager
- Player
- PlayerDAO
## Main
```
package com.ece366.rps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootApplication
@RestController
public class RpsApplication {
public static void main(String[] args) {
System.out.println("Hello Spring Boot");
SpringApplication.run(RpsApplication.class, args);
}
// Sample hello world API
@GetMapping("/helloClass")
public String helloClass() {
System.out.println("HELLO");
return "Hello Class";
}
@GetMapping("/getPlayerById/{id}")
public Player create(@PathVariable("id") String id) {
System.out.println(id);
DatabaseConnectionManager dcm = new DatabaseConnectionManager("localhost",
"rps", "postgres", "password");
Player player = new Player();
try {
Connection connection = dcm.getConnection();
PlayerDAO playerDAO = new PlayerDAO(connection);
player = playerDAO.findById(id);
System.out.println(player);
}
catch(SQLException e) {
e.printStackTrace();
}
return player;
}
}
```
- Note we added ```@RestController``` and ```@GetMapping```
- ```GetMapping``` specifies what the API url maps to
## Testing with Postman
- Create a post request with the following url: ```http://localhost:8080/getPlayerById/1```
- Add a Body with the message desired: ```issac```
- Send the request
- You can also run this on chrome
## PostMapping
```
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@PostMapping("/createNewPlayer")
public Player createNewPlayer(@RequestBody String json) {
System.out.println(json);
ObjectMapper objectMapper = new ObjectMapper();
Player inputPlayer;
try {
inputPlayer = objectMapper.readValue(json, Player.class);
}
catch (JsonProcessingException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JSON payload", e);
}
DatabaseConnectionManager dcm = new DatabaseConnectionManager("localhost",
"rps", "postgres", "password");
Player player = new Player();
try {
Connection connection = dcm.getConnection();
PlayerDAO playerDAO = new PlayerDAO(connection);
player.setUserName(inputPlayer.getUserName());
player.setPassword(inputPlayer.getPassword());
player = playerDAO.create(player);
System.out.println(player);
}
catch(SQLException e) {
e.printStackTrace();
}
return player;
}
```
- We use the jackson library to parse the json
## Docker Compose
```
services:
db:
image: postgres
volumes:
- $HOME/srv/postgres:/var/lib/postgresql
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=password
expose:
- "5432"
ports:
- "5432:5432"
restart: always
app:
build: .
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=password
expose:
- "8080"
ports:
- "8080:8080"
depends_on:
- db
```
## Dockerfile for Spring Boot
```
FROM maven:3.9.6-eclipse-temurin-21 AS build
ADD . /project
WORKDIR /project
RUN mvn -e package
FROM eclipse-temurin:latest
COPY --from=build /project/target/rps-0.0.1-SNAPSHOT.jar /app/rps.jar
ENTRYPOINT java -jar /app/rps.jar
```
## Common Traps
- Make sure you don't have another spring boot application running on the same port
- Rebuild your docker compose if you edited docker-compose.yaml or any Dockerfiles
- Make sure you use your docker compose services name for your database
## Java Persistence API (JPA)
- Standard Java EE (Jakarta EE) specification for ORM (Object–Relational Mapping)
- Allows you to map between objects and database tables
- Streamlines persistence to standard format
- Reduces JDBC code
- Focus on OOP
## Spring Initializr
- start.spring.io
- Project: Maven
- Language: Java
- Spring Boot: 4.0.3
- Group: com.ece366
- Artifact: rps
- Java 25
- Add spring web & spring data jpa dependencies
- Download the zip file and put it in your workspace
## resources/application.properties
```
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:db}:5432/${POSTGRES_DB:rps}
spring.datasource.username=${POSTGRES_USER:postgres}
spring.datasource.password=${POSTGRES_PASSWORD:password}
```
## Hard coded
```
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/rps
spring.datasource.username=postgres
spring.datasource.password=password
```
## pom.xml
Note that jpa is in the pom file and add postgres dependency
```
org.springframework.boot
spring-boot-starter-data-jpa
org.postgresql
postgresql
runtime
```
## Important Annotations
- ```@RestController``` - enables API endpoints
- ```@Entity``` - a row from the database
- ```@Table``` - the table name
- ```@Id``` - the ID of a table row
- ```@Column``` - a column of the table
- ```@GeneratedValue``` - strategies for primary key
## Code Organization for JPA
## Player Class
data/Player.java
```
package com.ece366.rpsjpa.data;
import jakarta.persistence.*;
@Entity
@Table(name="PLAYER")
public class Player {
@Id
@Column(name="USER_NAME")
private String userName;
@Column(name="PASSWORD")
private String password;
@Column(name="TOTAL_WINS")
private int totalWins;
@Column(name="TOTAL_LOSSES")
private int totalLosses;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getTotalWins() {
return totalWins;
}
public void setTotalWins(int totalWins) {
this.totalWins = totalWins;
}
public int getTotalLosses() {
return totalLosses;
}
public void setTotalLosses(int totalLosses) {
this.totalLosses = totalLosses;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
", totalWin=" + totalWins +
", totalLoss=" + totalLosses +
'}';
}
}
```
## Dockerfile
```
FROM maven:3-eclipse-temurin-25 AS build
ADD . /project
WORKDIR /project
RUN mvn -e -Dmaven.test.skip package
FROM eclipse-temurin:25-jre
COPY --from=build /project/target/rpsjpa-0.0.1-SNAPSHOT.jar /app/rps.jar
ENTRYPOINT java -jar /app/rps.jar
```
- Note, the ```-Dmaven.test.skip``` skips tests during the build
## Docker Compose
```
services:
db:
image: postgres
volumes:
- $HOME/srv/postgres:/var/lib/postgresql
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=password
expose:
- "5432"
ports:
- "5432:5432"
restart: always
app:
build: .
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=rps
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
expose:
- "8080"
ports:
- "8080:8080"
depends_on:
- db
```
- Don't forget to update the applications.properties file!