Spring Boot 3.0
Spring boot 3 Features :
- Spring Boot 3.0 will require Java 17
- 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 18
- 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, Docker MariaDB 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.0.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>17</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>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.0</version>
</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.1.5.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:mariadb://localhost:3306/test1
username: root
password: mypass
driver-class-name: org.mariadb.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL5Dialect
show-sql: true
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());
}
}
Downloading and installing MariaDB
docker run --name mariadbtest -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mypass -d mariadb/server:10.3
sudo docker exec -it mariadbtest /bin/bash
mysql -u root -p
create database test1;
Script
DROP TABLE IF EXISTS users;
CREATE TABLE users (id bigint primary key, first_name varchar(128), last_name varchar(128));
DROP FUNCTION IF EXISTS get_sum;
DELIMITER //
CREATE function get_sum (a INT,
b INT)
returns INT DETERMINISTIC
begin
DECLARE total_value INT;
SET total_value = a + b;
RETURN total_value;
end; //
DELIMITER ;
Run & Test
Run Spring Boot application with command: mvn test -Dtest=UserControllerTests. by console, IntelliJ etc.
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.798 s - in com.henry.UserControllerTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.670 s
[INFO] Finished at: 2022-11-20T12:34:18-06:00
[INFO] ------------------------------------------------------------------------
Process finished with exit code 0
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/
https://stackoverflow.com/questions/14621495/what-is-the-difference-between-an-spring-entity-manager-and-spring-data-reposito
https://www.javaguides.net/2022/03/spring-boot-unit-testing-crud-rest-api-with-junit-and-mockito.html
No comments:
Post a Comment