Skip to content

Commit 7efa609

Browse files
Upgrade Spring to 3.4.2 and JDK21 compatible
1 parent 61a0e5c commit 7efa609

File tree

7 files changed

+119
-32
lines changed

7 files changed

+119
-32
lines changed

.cursorrules

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
## Instruction to developer: save this file as .cursorrules and place it on the root project directory
2+
3+
AI Persona:
4+
5+
You are an experienced Senior Java Developer, You always adhere to SOLID principles, DRY principles, KISS principles and YAGNI principles. You always follow OWASP best practices. You always break task down to smallest units and approach to solve any task in step by step manner.
6+
7+
Technology stack:
8+
9+
Framework: Java Spring Boot 3 Maven with Java 21 Dependencies: Spring WebFlux, Spring Data JPA, Lombok, MySQL driver, Spring Data R2DBC
10+
11+
Application Logic Design:
12+
13+
1. All request and response handling must be done only in RestController.
14+
2. All database operation logic must be done in ServiceImpl classes, which must use methods provided by Repositories.
15+
3. RestControllers cannot autowire Repositories directly unless absolutely beneficial to do so.
16+
4. ServiceImpl classes cannot query the database directly and must use Repositories methods, unless absolutely necessary.
17+
5. Data carrying between RestControllers and serviceImpl classes, and vice versa, must be done only using DTOs.
18+
6. Entity classes must be used only to carry data out of database query executions.
19+
20+
Entities
21+
22+
1. Must annotate entity classes with @Entity.
23+
2. Must annotate entity classes with @Data (from Lombok), unless specified in a prompt otherwise.
24+
3. Must annotate entity ID with @Id and @GeneratedValue(strategy=GenerationType.IDENTITY).
25+
4. Must use FetchType.LAZY for relationships, unless specified in a prompt otherwise.
26+
5. Annotate entity properties properly according to best practices, e.g., @Size, @NotEmpty, @Email, etc.
27+
28+
Repository (DAO):
29+
30+
1. Must annotate repository classes with @Repository.
31+
2. Repository classes must be of type interface.
32+
3. Must extend JpaRepository with the entity and entity ID as parameters, unless specified in a prompt otherwise.
33+
4. Must use JPQL for all @Query type methods, unless specified in a prompt otherwise.
34+
5. Must use @EntityGraph(attributePaths={"relatedEntity"}) in relationship queries to avoid the N+1 problem.
35+
6. Must use a DTO as The data container for multi-join queries with @Query.
36+
7. Must support reactive database operations using Spring Data R2DBC for non-blocking data access.
37+
38+
Service:
39+
40+
1. Service classes must be of type interface.
41+
2. All service class method implementations must be in ServiceImpl classes that implement the service class,
42+
3. All ServiceImpl classes must be annotated with @Service.
43+
4. All dependencies in ServiceImpl classes must be @Autowired without a constructor, unless specified otherwise.
44+
5. Return objects of ServiceImpl methods should be DTOs, not entity classes, unless absolutely necessary.
45+
6. For any logic requiring checking the existence of a record, use the corresponding repository method with an appropriate .orElseThrow lambda method.
46+
7. For any multiple sequential database executions, must use @Transactional or transactionTemplate, whichever is appropriate.
47+
8. ServiceImpl classes should handle reactive types (e.g., Mono, Flux) when dealing with R2DBC repositories.
48+
49+
Data Transfer object (DTo):
50+
51+
1. Must be of type record, unless specified in a prompt otherwise.
52+
2. Must specify a compact canonical constructor to validate input parameter data (not null, blank, etc., as appropriate).
53+
54+
RestController:
55+
56+
1. Must annotate controller classes with @RestController.
57+
2. Must specify class-level API routes with @RequestMapping, e.g. ("/api/user").
58+
3. Class methods must use best practice HTTP method annotations, e.g, create = @postMapping("/create"), etc.
59+
4. All dependencies in class methods must be @Autowired without a constructor, unless specified otherwise.
60+
5. Methods return objects must be of type Response Entity of type ApiResponse.
61+
6. All class method logic must be implemented in a try..catch block(s).
62+
7. Caught errors in catch blocks must be handled by the Custom GlobalExceptionHandler class.
63+
8. RestController methods should handle reactive types (e.g., Mono, Flux) for endpoints interacting with R2DBC services.
64+
65+
ApiResponse Class (/ApiResponse.java):
66+
67+
@Data
68+
@NoArgsConstructor
69+
@AllArgsConstructor
70+
public class ApiResponse<T> {
71+
private String result; // SUCCESS or ERROR
72+
private String message; // success or error message
73+
private T data; // return object from service class, if successful
74+
}
75+
76+
GlobalExceptionHandler Class (/GlobalExceptionHandler.java)
77+
78+
@RestControllerAdvice
79+
public class GlobalExceptionHandler {
80+
81+
public static ResponseEntity<ApiResponse<?>> errorResponseEntity(String message, HttpStatus status) {
82+
ApiResponse<?> response = new ApiResponse<>("error", message, null)
83+
return new ResponseEntity<>(response, status);
84+
}
85+
86+
@ExceptionHandler(IllegalArgumentException.class)
87+
public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException ex) {
88+
return new ResponseEntity<>(ApiResponse.error(400, ex.getMessage()), HttpStatus.BAD_REQUEST);
89+
}
90+
}

.github/workflows/spring-boot-webflux-kotlin-coroutine.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
uses: actions/setup-java@v3
2323
with:
2424
distribution: 'temurin' # See 'Supported distributions' for available options
25-
java-version: '17'
25+
java-version: '21'
2626

2727
- name: Install dependencies, run tests, and collect coverage
2828
run: ./gradlew clean build

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Container in which to build the application
2-
FROM gradle:8.1.1-jdk11-alpine as builder
2+
FROM gradle:8.12-jdk21-alpine as builder
33

44
# Copy the source code into the builder container
55
WORKDIR /app
@@ -9,7 +9,7 @@ COPY . .
99
RUN gradle assemble
1010

1111
# Container in which to run the application
12-
FROM eclipse-temurin:17.0.10_7-jdk-alpine
12+
FROM eclipse-temurin:21-jdk-ubi9-minimal
1313

1414
# Copy the jar from the builder container into the run container
1515
COPY --from=builder /app/build/libs/spring-boot-webflux-kotlin-coroutine-*.jar spring-boot-webflux-kotlin-coroutine.jar

build.gradle.kts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,27 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
33
plugins {
44
java
55
jacoco
6-
id("org.springframework.boot") version "3.2.2"
7-
id("io.spring.dependency-management") version "1.1.4"
8-
id("org.springdoc.openapi-gradle-plugin") version "1.8.0"
9-
id("org.openapi.generator") version "7.2.0"
6+
id("org.springframework.boot") version "3.4.2"
7+
id("io.spring.dependency-management") version "1.1.7"
8+
id("org.springdoc.openapi-gradle-plugin") version "1.9.0"
9+
id("org.openapi.generator") version "7.10.0"
1010

11-
kotlin("jvm") version "1.9.22"
12-
kotlin("plugin.spring") version "1.9.22"
11+
kotlin("jvm") version "1.9.25"
12+
kotlin("plugin.spring") version "1.9.25"
1313
}
1414

1515
group = "com.sennproject"
1616
version = "1.0.0"
17-
java.sourceCompatibility = JavaVersion.VERSION_17
17+
java.sourceCompatibility = JavaVersion.VERSION_21
1818

1919
repositories {
2020
mavenCentral()
2121
}
2222

23-
extra["kotestVersion"] = "5.8.0"
24-
extra["openAPIVersion"] = "1.7.0"
25-
extra["testcontainersVersion"] = "1.19.4"
26-
extra["coroutinesCoreVersion"] = "1.8.0-RC2"
23+
extra["kotestVersion"] = "5.9.1"
24+
extra["openAPIVersion"] = "1.8.0"
25+
extra["testcontainersVersion"] = "1.20.4"
26+
extra["coroutinesCoreVersion"] = "1.10.1"
2727

2828
dependencies {
2929
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
@@ -48,8 +48,9 @@ dependencies {
4848
implementation("org.springdoc:springdoc-openapi-webflux-ui:${property("openAPIVersion")}")
4949

5050
// Database
51-
implementation("io.asyncer:r2dbc-mysql:1.0.6")
52-
runtimeOnly("com.mysql:mysql-connector-j:8.0.+")
51+
implementation("io.asyncer:r2dbc-mysql:1.3.1")
52+
implementation("io.r2dbc:r2dbc-pool:1.0.2.RELEASE")
53+
runtimeOnly("com.mysql:mysql-connector-j:8.4.+")
5354
implementation("org.springframework.data:spring-data-commons")
5455
implementation("org.springframework.data:spring-data-relational")
5556

@@ -63,13 +64,18 @@ dependencies {
6364
testImplementation("org.springframework.boot:spring-boot-starter-test")
6465
testImplementation("org.springframework.boot:spring-boot-testcontainers")
6566
testImplementation("io.projectreactor:reactor-test")
66-
testImplementation("org.testcontainers:junit-jupiter")
67-
testImplementation("org.testcontainers:mysql")
67+
testImplementation("org.testcontainers:junit-jupiter:1.17.3")
68+
testImplementation("org.testcontainers:mysql:1.17.3")
6869
testImplementation("org.testcontainers:r2dbc")
6970

7071
// Faker
7172
implementation("net.datafaker:datafaker:1.8.1")
7273

74+
// Netty native libraries for MacOS
75+
if (System.getProperty("os.name").lowercase().contains("mac")) {
76+
implementation("io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64")
77+
implementation("io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-x86_64")
78+
}
7379
}
7480

7581
dependencyManagement {
@@ -82,7 +88,7 @@ dependencyManagement {
8288
tasks.withType<KotlinCompile> {
8389
kotlinOptions {
8490
freeCompilerArgs = listOf("-Xjsr305=strict")
85-
jvmTarget = "17"
91+
jvmTarget = "21"
8692
}
8793
}
8894

docker-compose-local.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: '3.9'
2-
31
services:
42
mysql:
53
image: mysql:8.0

docker-compose.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: '3.9'
2-
31
services:
42
mysql:
53
image: mysql:8.0

src/main/resources/logback-spring.xml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,11 @@
2525
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
2626
<level>WARN</level>
2727
</filter>
28-
<rollingPolicy
29-
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
30-
<!-- rollover daily and when the file reaches 10 MegaBytes -->
31-
<fileNamePattern>${LOGS}/archived/spring-boot-logger-%d{yyyy-MM-dd}.%i.log
32-
</fileNamePattern>
33-
<timeBasedFileNamingAndTriggeringPolicy
34-
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
35-
<maxFileSize>10MB</maxFileSize>
36-
</timeBasedFileNamingAndTriggeringPolicy>
28+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
29+
<fileNamePattern>./logs/archived/spring-boot-logger-%d{yyyy-MM-dd}.log</fileNamePattern>
30+
<maxHistory>30</maxHistory>
3731
</rollingPolicy>
32+
3833
</appender>
3934

4035

0 commit comments

Comments
 (0)