From 9c0629b8ac14ee85d36f0ce13ee747ada6c61652 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:52:36 +0000 Subject: [PATCH] Upgrade to Spring Boot 3.5.6 and migrate to Jakarta EE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated Spring Boot from 2.7.14/2.7.15 to 3.5.6 across all 7 services - Updated Spring Cloud from 2021.0.8 to 2025.0.0 - Updated Keycloak from 21.0.1 to 22.0.5 in User-Service - Migrated javax.persistence.* to jakarta.persistence.* in all entity files - Migrated javax.transaction.Transactional to jakarta.transaction.Transactional - Updated GlobalExceptionHandler method signatures for Spring Framework 6.x (HttpStatus → HttpStatusCode) - Replaced Apache Commons IOUtils with standard Java BufferedReader (no longer a transitive dependency) - Updated SecurityConfig for Spring Security 6.x compatibility (lambda-based configuration) - Removed deprecated @EnableEurekaClient annotation from API-Gateway, User-Service, Transaction-Service Services upgraded: - API-Gateway - Account-Service - Fund-Transfer - Sequence-Generator - Service-Registry - Transaction-Service - User-Service All services compile successfully and tests pass. Co-Authored-By: Sam Fertig --- API-Gateway/pom.xml | 4 ++-- .../api/gateway/ApiGatewayApplication.java | 2 -- .../api/gateway/config/SecurityConfig.java | 22 +++++++++---------- Account-Service/pom.xml | 4 ++-- .../FeignClientErrorDecoder.java | 12 +++++++--- .../exception/GlobalExceptionHandler.java | 7 +++--- .../account/service/model/entity/Account.java | 2 +- Fund-Transfer/pom.xml | 4 ++-- .../FeignClientErrorDecoder.java | 10 +++++++-- .../exception/GlobalExceptionHandler.java | 5 +++-- .../model/entity/FundTransfer.java | 4 ++-- Sequence-Generator/pom.xml | 4 ++-- .../generator/model/entity/Sequence.java | 8 +++---- Service-Registry/pom.xml | 4 ++-- Transaction-Service/pom.xml | 4 ++-- .../TransactionServiceApplication.java | 2 -- .../FeignClientErrorDecoder.java | 12 +++++++--- .../model/entity/Transaction.java | 2 +- User-Service/pom.xml | 6 ++--- .../user/service/UserServiceApplication.java | 2 -- .../exception/GlobalExceptionHandler.java | 5 +++-- .../user/service/model/entity/User.java | 4 ++-- .../service/model/entity/UserProfile.java | 8 +++---- .../implementation/UserServiceImpl.java | 2 +- 24 files changed, 76 insertions(+), 63 deletions(-) diff --git a/API-Gateway/pom.xml b/API-Gateway/pom.xml index 6623607..f692c0b 100644 --- a/API-Gateway/pom.xml +++ b/API-Gateway/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.14 + 3.5.6 org.training @@ -15,7 +15,7 @@ API Gateway 17 - 2021.0.8 + 2025.0.0 diff --git a/API-Gateway/src/main/java/org/training/api/gateway/ApiGatewayApplication.java b/API-Gateway/src/main/java/org/training/api/gateway/ApiGatewayApplication.java index 5d16c71..5c7a3ef 100644 --- a/API-Gateway/src/main/java/org/training/api/gateway/ApiGatewayApplication.java +++ b/API-Gateway/src/main/java/org/training/api/gateway/ApiGatewayApplication.java @@ -2,10 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication -@EnableEurekaClient public class ApiGatewayApplication { public static void main(String[] args) { diff --git a/API-Gateway/src/main/java/org/training/api/gateway/config/SecurityConfig.java b/API-Gateway/src/main/java/org/training/api/gateway/config/SecurityConfig.java index a028a68..1b9bc3e 100644 --- a/API-Gateway/src/main/java/org/training/api/gateway/config/SecurityConfig.java +++ b/API-Gateway/src/main/java/org/training/api/gateway/config/SecurityConfig.java @@ -6,6 +6,8 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; +import static org.springframework.security.config.Customizer.withDefaults; + @Configuration @EnableWebFluxSecurity public class SecurityConfig { @@ -13,17 +15,13 @@ public class SecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http - .authorizeExchange() - //ALLOWING REGISTER API FOR DIRECT ACCESS - .pathMatchers("/api/users/register").permitAll() - //ALL OTHER APIS ARE AUTHENTICATED - .anyExchange().authenticated() - .and() - .csrf().disable() - .oauth2Login() - .and() - .oauth2ResourceServer() - .jwt(); + .authorizeExchange(exchanges -> exchanges + .pathMatchers("/api/users/register").permitAll() + .anyExchange().authenticated() + ) + .csrf(csrf -> csrf.disable()) + .oauth2Login(withDefaults()) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults())); return http.build(); } -} \ No newline at end of file +} diff --git a/Account-Service/pom.xml b/Account-Service/pom.xml index 3d4401c..d81ff0c 100644 --- a/Account-Service/pom.xml +++ b/Account-Service/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.15 + 3.5.6 org.training @@ -15,7 +15,7 @@ Account Service 17 - 2021.0.8 + 2025.0.0 diff --git a/Account-Service/src/main/java/org/training/account/service/configuration/FeignClientErrorDecoder.java b/Account-Service/src/main/java/org/training/account/service/configuration/FeignClientErrorDecoder.java index 6563d92..1730a3e 100644 --- a/Account-Service/src/main/java/org/training/account/service/configuration/FeignClientErrorDecoder.java +++ b/Account-Service/src/main/java/org/training/account/service/configuration/FeignClientErrorDecoder.java @@ -5,9 +5,9 @@ import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; import org.training.account.service.exception.GlobalException; +import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; @@ -50,7 +50,13 @@ private GlobalException extractGlobalException(Response response) { try { reader = response.body().asReader(StandardCharsets.UTF_8); - String result = IOUtils.toString(reader); + BufferedReader bufferedReader = new BufferedReader(reader); + StringBuilder resultBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + resultBuilder.append(line); + } + String result = resultBuilder.toString(); log.error(result); ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); @@ -69,4 +75,4 @@ private GlobalException extractGlobalException(Response response) { } return globalException; } -} \ No newline at end of file +} diff --git a/Account-Service/src/main/java/org/training/account/service/exception/GlobalExceptionHandler.java b/Account-Service/src/main/java/org/training/account/service/exception/GlobalExceptionHandler.java index ac70343..9e53140 100644 --- a/Account-Service/src/main/java/org/training/account/service/exception/GlobalExceptionHandler.java +++ b/Account-Service/src/main/java/org/training/account/service/exception/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -27,13 +28,13 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { * * @param ex The MethodArgumentNotValidException that occurred. * @param headers The HttpHeaders of the response. - * @param status The HttpStatus of the response. + * @param status The HttpStatusCode of the response. * @param request The WebRequest of the response. * @return A ResponseEntity with an ErrorResponse and HttpStatus.BAD_REQUEST. */ @Override public ResponseEntity handleMethodArgumentNotValid( - MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { return new ResponseEntity<>(new ErrorResponse(badRequest, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST); } @@ -53,4 +54,4 @@ public ResponseEntity handleGlobalException(GlobalException globalExcept .message(globalException.getErrorMessage()) .build()); } -} \ No newline at end of file +} diff --git a/Account-Service/src/main/java/org/training/account/service/model/entity/Account.java b/Account-Service/src/main/java/org/training/account/service/model/entity/Account.java index 19f9532..dd311bd 100644 --- a/Account-Service/src/main/java/org/training/account/service/model/entity/Account.java +++ b/Account-Service/src/main/java/org/training/account/service/model/entity/Account.java @@ -8,7 +8,7 @@ import org.training.account.service.model.AccountStatus; import org.training.account.service.model.AccountType; -import javax.persistence.*; +import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDate; diff --git a/Fund-Transfer/pom.xml b/Fund-Transfer/pom.xml index e5edb09..01773b9 100644 --- a/Fund-Transfer/pom.xml +++ b/Fund-Transfer/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.14 + 3.5.6 org.training @@ -15,7 +15,7 @@ Fund Transfer 17 - 2021.0.8 + 2025.0.0 diff --git a/Fund-Transfer/src/main/java/org/training/fundtransfer/configuration/FeignClientErrorDecoder.java b/Fund-Transfer/src/main/java/org/training/fundtransfer/configuration/FeignClientErrorDecoder.java index 93c34fe..eb4399b 100644 --- a/Fund-Transfer/src/main/java/org/training/fundtransfer/configuration/FeignClientErrorDecoder.java +++ b/Fund-Transfer/src/main/java/org/training/fundtransfer/configuration/FeignClientErrorDecoder.java @@ -5,9 +5,9 @@ import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; import org.training.fundtransfer.exception.GlobalException; +import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; @@ -53,7 +53,13 @@ private GlobalException extractGlobalException(Response response) { try { reader = response.body().asReader(StandardCharsets.UTF_8); - String result = IOUtils.toString(reader); + BufferedReader bufferedReader = new BufferedReader(reader); + StringBuilder resultBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + resultBuilder.append(line); + } + String result = resultBuilder.toString(); log.error(result); ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); diff --git a/Fund-Transfer/src/main/java/org/training/fundtransfer/exception/GlobalExceptionHandler.java b/Fund-Transfer/src/main/java/org/training/fundtransfer/exception/GlobalExceptionHandler.java index eb5b9f8..c15fb14 100644 --- a/Fund-Transfer/src/main/java/org/training/fundtransfer/exception/GlobalExceptionHandler.java +++ b/Fund-Transfer/src/main/java/org/training/fundtransfer/exception/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -21,12 +22,12 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { * * @param ex The MethodArgumentNotValidException to handle. * @param headers The HttpHeaders to include in the response. - * @param status The HttpStatus to set in the response. + * @param status The HttpStatusCode to set in the response. * @param request The WebRequest associated with the request. * @return A ResponseEntity containing an ErrorResponse and HttpStatus.BAD_REQUEST. */ protected ResponseEntity handleMethodArgumentNotValid( - MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { return new ResponseEntity<>(new ErrorResponse(badRequest, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST); } diff --git a/Fund-Transfer/src/main/java/org/training/fundtransfer/model/entity/FundTransfer.java b/Fund-Transfer/src/main/java/org/training/fundtransfer/model/entity/FundTransfer.java index eff0c47..ee84d35 100644 --- a/Fund-Transfer/src/main/java/org/training/fundtransfer/model/entity/FundTransfer.java +++ b/Fund-Transfer/src/main/java/org/training/fundtransfer/model/entity/FundTransfer.java @@ -8,7 +8,7 @@ import org.training.fundtransfer.model.TransactionStatus; import org.training.fundtransfer.model.TransferType; -import javax.persistence.*; +import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -39,4 +39,4 @@ public class FundTransfer { @CreationTimestamp private LocalDateTime transferredOn; -} \ No newline at end of file +} diff --git a/Sequence-Generator/pom.xml b/Sequence-Generator/pom.xml index 8fa1910..60c3023 100644 --- a/Sequence-Generator/pom.xml +++ b/Sequence-Generator/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.15 + 3.5.6 org.training @@ -15,7 +15,7 @@ Sequence Generator 17 - 2021.0.8 + 2025.0.0 diff --git a/Sequence-Generator/src/main/java/org/training/sequence/generator/model/entity/Sequence.java b/Sequence-Generator/src/main/java/org/training/sequence/generator/model/entity/Sequence.java index 94e8170..3cf700f 100644 --- a/Sequence-Generator/src/main/java/org/training/sequence/generator/model/entity/Sequence.java +++ b/Sequence-Generator/src/main/java/org/training/sequence/generator/model/entity/Sequence.java @@ -5,10 +5,10 @@ import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; @Entity @Data diff --git a/Service-Registry/pom.xml b/Service-Registry/pom.xml index f236a23..05f5091 100644 --- a/Service-Registry/pom.xml +++ b/Service-Registry/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.14 + 3.5.6 org.training @@ -15,7 +15,7 @@ Service Registry 17 - 2021.0.8 + 2025.0.0 diff --git a/Transaction-Service/pom.xml b/Transaction-Service/pom.xml index 6f70a80..af527ae 100644 --- a/Transaction-Service/pom.xml +++ b/Transaction-Service/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.15 + 3.5.6 org.training @@ -15,7 +15,7 @@ Transaction Service 17 - 2021.0.8 + 2025.0.0 diff --git a/Transaction-Service/src/main/java/org/training/transactions/TransactionServiceApplication.java b/Transaction-Service/src/main/java/org/training/transactions/TransactionServiceApplication.java index a729304..d2a4512 100644 --- a/Transaction-Service/src/main/java/org/training/transactions/TransactionServiceApplication.java +++ b/Transaction-Service/src/main/java/org/training/transactions/TransactionServiceApplication.java @@ -2,12 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients -@EnableEurekaClient public class TransactionServiceApplication { public static void main(String[] args) { diff --git a/Transaction-Service/src/main/java/org/training/transactions/configuration/FeignClientErrorDecoder.java b/Transaction-Service/src/main/java/org/training/transactions/configuration/FeignClientErrorDecoder.java index 809aea1..3e60d47 100644 --- a/Transaction-Service/src/main/java/org/training/transactions/configuration/FeignClientErrorDecoder.java +++ b/Transaction-Service/src/main/java/org/training/transactions/configuration/FeignClientErrorDecoder.java @@ -5,9 +5,9 @@ import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; import org.training.transactions.exception.GlobalException; +import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; @@ -50,7 +50,13 @@ private GlobalException extractGlobalException(Response response) { try { reader = response.body().asReader(StandardCharsets.UTF_8); - String result = IOUtils.toString(reader); + BufferedReader bufferedReader = new BufferedReader(reader); + StringBuilder resultBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + resultBuilder.append(line); + } + String result = resultBuilder.toString(); ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); globalException = mapper.readValue(result, GlobalException.class); @@ -67,4 +73,4 @@ private GlobalException extractGlobalException(Response response) { } return globalException; } -} \ No newline at end of file +} diff --git a/Transaction-Service/src/main/java/org/training/transactions/model/entity/Transaction.java b/Transaction-Service/src/main/java/org/training/transactions/model/entity/Transaction.java index fe0f837..2e0f640 100644 --- a/Transaction-Service/src/main/java/org/training/transactions/model/entity/Transaction.java +++ b/Transaction-Service/src/main/java/org/training/transactions/model/entity/Transaction.java @@ -8,7 +8,7 @@ import org.training.transactions.model.TransactionStatus; import org.training.transactions.model.TransactionType; -import javax.persistence.*; +import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; diff --git a/User-Service/pom.xml b/User-Service/pom.xml index 75e9b29..9722953 100644 --- a/User-Service/pom.xml +++ b/User-Service/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.14 + 3.5.6 org.training @@ -15,7 +15,7 @@ User Service 17 - 2021.0.8 + 2025.0.0 @@ -69,7 +69,7 @@ org.keycloak keycloak-admin-client - 21.0.1 + 22.0.5 org.springframework.cloud diff --git a/User-Service/src/main/java/org/training/user/service/UserServiceApplication.java b/User-Service/src/main/java/org/training/user/service/UserServiceApplication.java index 70d2cbf..adf4771 100644 --- a/User-Service/src/main/java/org/training/user/service/UserServiceApplication.java +++ b/User-Service/src/main/java/org/training/user/service/UserServiceApplication.java @@ -2,12 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients -@EnableEurekaClient public class UserServiceApplication { public static void main(String[] args) { diff --git a/User-Service/src/main/java/org/training/user/service/exception/GlobalExceptionHandler.java b/User-Service/src/main/java/org/training/user/service/exception/GlobalExceptionHandler.java index 6b555af..357ce5b 100644 --- a/User-Service/src/main/java/org/training/user/service/exception/GlobalExceptionHandler.java +++ b/User-Service/src/main/java/org/training/user/service/exception/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -27,12 +28,12 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { * * @param ex The MethodArgumentNotValidException that was thrown. * @param headers The HttpHeaders object for the response. - * @param status The HttpStatus for the response. + * @param status The HttpStatusCode for the response. * @param request The WebRequest object for the response. * @return A ResponseEntity containing an ErrorResponse object with the error code and localized message, and the HTTP status code. */ @Override - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { return new ResponseEntity<>(new ErrorResponse(errorCodeBadRequest, ex.getLocalizedMessage()), HttpStatus.BAD_REQUEST); } diff --git a/User-Service/src/main/java/org/training/user/service/model/entity/User.java b/User-Service/src/main/java/org/training/user/service/model/entity/User.java index cf0de9c..d10f342 100644 --- a/User-Service/src/main/java/org/training/user/service/model/entity/User.java +++ b/User-Service/src/main/java/org/training/user/service/model/entity/User.java @@ -7,7 +7,7 @@ import org.hibernate.annotations.CreationTimestamp; import org.training.user.service.model.Status; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDate; @Entity @@ -38,4 +38,4 @@ public class User { @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "user_profile_id", referencedColumnName = "userProfileId") private UserProfile userProfile; -} \ No newline at end of file +} diff --git a/User-Service/src/main/java/org/training/user/service/model/entity/UserProfile.java b/User-Service/src/main/java/org/training/user/service/model/entity/UserProfile.java index e0a983f..f317bf3 100644 --- a/User-Service/src/main/java/org/training/user/service/model/entity/UserProfile.java +++ b/User-Service/src/main/java/org/training/user/service/model/entity/UserProfile.java @@ -5,10 +5,10 @@ import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; @Entity @Data diff --git a/User-Service/src/main/java/org/training/user/service/service/implementation/UserServiceImpl.java b/User-Service/src/main/java/org/training/user/service/service/implementation/UserServiceImpl.java index 7e416ba..8cbfcf4 100644 --- a/User-Service/src/main/java/org/training/user/service/service/implementation/UserServiceImpl.java +++ b/User-Service/src/main/java/org/training/user/service/service/implementation/UserServiceImpl.java @@ -27,7 +27,7 @@ import org.training.user.service.service.UserService; import org.training.user.service.utils.FieldChecker; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors;