Sunday, November 20, 2022

Spring boot 3 with Hibernate 6 and Java 21

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 :

There are several layers of working with persistent data in Java/Spring:

Demo

Spring boot 3 with hibernate 6, MySQL and YAML file configuration.


Technology

  • Spring Boot 3.2.0
  • Hibernate 6
  • Java 21
  • Docker Compose
  • Maven 
  • IntelliJ IDEA  2023.3.1 Community 

Project Structure


























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.














Source Code


Here on GitHub.




References.

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/


























Virtual Threads in Java 21: Simplified Concurrency for Modern Applications

  With Java 21, Virtual Threads have redefined how we approach concurrency, offering a lightweight and efficient way to handle parallel and ...