Sunday, November 20, 2022

Spring boot 3 with Hibernate 6

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 :

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.


Technology

  • Spring Boot 3.0.0
  • Hibernate 6
  • Java 17
  • Docker
  • Maven 
  • IntelliJ IDEA

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.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




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/
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

Spring boot 3 with Hibernate 6

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’...