Spring Boot 3
Spring boot 3 Features :
- Spring Boot 3.0 will require Java 17 or later
- Jakarta EE 9 a new top-level jakarta package, replacing EE 8’s javax top-level package. It will also be the first version of Spring Boot that makes use of Jakarta EE 9 APIs (jakarta.*) instead of EE 8 (javax.*).
- Since Spring Boot 2.4 changed the way that application.properties and application.yaml files were loaded.
Hibernate ORM 6.0
Hibernate Compatibility :
- Java 11, 17 or later
- Jakarta Persistence 3.1 and 3.0
There are several layers of working with persistent data in Java/Spring:
Demo
Spring boot 3 with hibernate 6, MySQL and YAML file configuration.
Configuration Spring Boot project
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.henry</groupId> <artifactId>spring3-hibernate6</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring3-hibernate6</name> <description>Spring boot 3 with hibernate 6 configuration</description> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- POSTGRESQL database driver --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.4.1.Final</version> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml
server:
port: 9000
servlet:
context-path: /
spring:
datasource:
url: jdbc:postgresql:postgre_test
username: postgre_test
password: postgre_test
driver-class-name: org.postgresql.Driver
initialization-mode: always
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
Model
package com.henry.model;
import jakarta.persistence.*;
import lombok.*;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
}
Repository
- UserRepositoryCustom
package com.henry.repository;
public interface UserRepositoryCustom {
public Integer getSum(int a, int b);
}
- UserRepository
package com.henry.repository;
import com.henry.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User,Long>, UserRepositoryCustom {
Optional<User> findByFirstName(String firstName);
}
- UserRepositoryCustomImpl
package com.henry.repository.impl;
import com.henry.repository.UserRepositoryCustom;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.hibernate.Session;
import org.hibernate.jdbc.ReturningWork;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public Integer getSum(int a, int b) {
Session session = entityManager.unwrap(Session.class);
int result = session.doReturningWork(new ReturningWork<Integer>() {
@Override
public Integer execute(Connection connection) throws SQLException {
CallableStatement call = connection.prepareCall("{ ? = call get_sum(?,?) }");
call.registerOutParameter(1, Types.INTEGER); // or whatever it is
call.setInt(2, a);
call.setInt(3, b);
call.execute();
return call.getInt(1); // propagate this back to enclosing class
}
});
return result;
}
}
Service
- UserService
package com.henry.service;
import com.henry.model.User;
import java.util.List;
public interface UserService {
User save(User user);
List<User> findAll();
Integer getSum(int a, int b);
}
- UserServiceImpl
package com.henry.service.impl;
import com.henry.model.User;
import com.henry.repository.UserRepository;
import com.henry.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User save(User user) {
return userRepository.save(user);
}
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public Integer getSum(int a, int b) {
return userRepository.getSum(a,b);
}
}
Controller
- UserController
package com.henry.controller;
import com.henry.model.User;
import com.henry.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/hello")
public String helloWorld() {
return """
Hello World,
multi-line,
text block.
""";
}
@GetMapping
public List<User> findAll() {
return userService.findAll();
}
@GetMapping("/sum/{a}/{b}")
public Integer getSum(@PathVariable int a, @PathVariable int b) {
return userService.getSum(a, b);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User addUser(@RequestBody User user) {
return userService.save(user);
}
}
Test Class
package com.henry;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.henry.model.User;
import com.henry.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest
public class UserControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
public void givenUserObject_whenCreateUser_thenReturnSavedUser() throws Exception{
// given - precondition or setup
User user = User.builder()
.firstName("Henry")
.lastName("Xiloj")
.build();
given(userService.save(any(User.class)))
.willAnswer((invocation)-> invocation.getArgument(0));
// when - action or behaviour that we are going test
ResultActions response = mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)));
// then - verify the result or output using assert statements
response.andDo(print()).
andExpect(status().isCreated())
.andExpect(jsonPath("$.firstName",
is(user.getFirstName())))
.andExpect(jsonPath("$.lastName",
is(user.getLastName())));
}
// JUnit test for Get All users REST API
@Test
public void givenListOfUsers_whenGetAllUsers_thenReturnUsersList() throws Exception{
// given - precondition or setup
List<User> listOfUsers = new ArrayList<>();
listOfUsers.add(User.builder().firstName("User1").lastName("User1").build());
listOfUsers.add(User.builder().firstName("User2").lastName("User2").build());
given(userService.findAll()).willReturn(listOfUsers);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/users"));
// then - verify the output
response.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.size()",
is(listOfUsers.size())));
}
@Test
public void givenEmployeeId_whenGetUsersFunSum_thenReturnUsersObject() throws Exception{
// given - precondition or setup
given(userService.getSum(1,2)).willReturn(3);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/api/users/sum/{a}/{b}", 1,2));
// then - verify the output
ResultActions resultActions = response.andExpect(status().isOk())
.andDo(print());
}
}
Environment Setup with Docker Compose
To simplify the setup of your application's environment, you can use Docker Compose. Below is a docker-compose-postgresql.yml file that defines services PostgreSQL database:
version: '3'
services:
postgres:
image: postgres:14.1
container_name: postgre_test
environment:
POSTGRES_USER: postgre_test
POSTGRES_PASSWORD: postgre_test
POSTGRES_DB: postgre_test
ports:
- "5432:5432"
Run command:
docker-compose -f docker-compose-postgresql.yml up -d
Run & Test
Run Spring Boot application with command: mvn test -Dtest=UserControllerTests. by console, IntelliJ etc.
https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0
https://hibernate.org/orm/releases/6.0/
https://jakarta.ee/specifications/persistence/
https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html
https://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
https://docs.oracle.com/en/java/javase/17/