Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 83 additions & 122 deletions poc-rest-api/spring-boot-rest-webmvc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.11</version>
<version>4.0.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -18,14 +18,13 @@
<properties>
<java.version>21</java.version>
<org.mapstruct.version>1.6.3</org.mapstruct.version>
<springdoc-open-ui.version>2.8.15</springdoc-open-ui.version>
<springdoc-open-ui.version>3.0.1</springdoc-open-ui.version>

<testcontainers-jooq-codegen-maven-plugin.version>1.0.0</testcontainers-jooq-codegen-maven-plugin.version>

<maven-pmd-plugin.version>3.13.0</maven-pmd-plugin.version>
<dependency-check-maven.version>12.2.0</dependency-check-maven.version>
<spotless.version>3.2.1</spotless.version>

<db.username>local</db.username>
<db.password>local</db.password>
</properties>

<dependencies>
Expand All @@ -43,7 +42,7 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -66,8 +65,8 @@
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-liquibase</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
Expand Down Expand Up @@ -102,22 +101,37 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-resttestclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down Expand Up @@ -215,121 +229,89 @@
<failOnError>false</failOnError>
</configuration>
</plugin>
<!-- Much better if there was a testcontainers lifecycle management plugin!
Upvote here if you like the idea: https://github.com/testcontainers/testcontainers-java/issues/4397 -->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
db = new org.testcontainers.containers.PostgreSQLContainer("postgres:latest")
.withUsername("${db.username}")
.withDatabaseName("rest")
.withPassword("${db.password}");

db.start();
project.properties.setProperty('db.url', db.getJdbcUrl());
</source>
</configuration>
</execution>
</executions>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>

<plugin>
<groupId>dev.sivalabs</groupId>
<artifactId>testcontainers-jooq-codegen-maven-plugin</artifactId>
<version>${testcontainers-jooq-codegen-maven-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<artifactId>testcontainers-postgresql</artifactId>
<version>${testcontainers.version}</version>
</dependency>
</dependencies>
</plugin>

<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>migrate</goal>
</goals>
<configuration>
<url>${db.url}</url>
<user>${db.username}</user>
<password>${db.password}</password>
<locations>
<location>filesystem:src/main/resources/db/migration</location>
</locations>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<version>${flyway.version}</version>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen</artifactId>
<version>${jooq.version}</version>
</dependency>
</dependencies>
</plugin>

<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>

<executions>
<execution>
<id>java-generator</id>
<phase>generate-sources</phase>
<id>generate-jooq-sources</id>
<goals>
<goal>generate</goal>
</goals>

<phase>generate-sources</phase>
<configuration>
<jdbc>
<url>${db.url}</url>
<user>${db.username}</user>
<password>${db.password}</password>
</jdbc>
<generator>
<database>
<inputSchema>public</inputSchema>
</database>
<target>
<packageName>com.example.poc.webmvc.testcontainersflyway.db</packageName>
<directory>target/generated-sources/jooq</directory>
</target>
</generator>
<database>
<!--
"type" can be: POSTGRES, MYSQL, MARIADB
-->
<type>POSTGRES</type>
<!--
"containerImage" is optional.
The defaults are
POSTGRES: postgres:15.2-alpine
MYSQL: mysql:8.0.33
MARIADB: mariadb:10.11
-->
<containerImage>postgres:18.2-alpine</containerImage>
</database>
<flyway>
<locations>
filesystem:src/main/resources/db/migration
</locations>
</flyway>
<!--
You can configure any supporting jooq config here.
see https://www.jooq.org/doc/latest/manual/code-generation/codegen-configuration/
-->
<jooq>
<generator>
<database>
<includes>.*</includes>
<excludes>DATABASECHANGELOG.*</excludes>
<inputSchema>public</inputSchema>
</database>
<target>
<packageName>com.example.poc.webmvc.db</packageName>
<directory>target/generated-sources/jooq</directory>
</target>
</generator>
</jooq>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<db.url>${db.url}</db.url>
<db.username>${db.username}</db.username>
<db.password>${db.password}</db.password>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless.version}</version>
<configuration>
<java>
<googleJavaFormat>
<version>1.25.2</version>
<version>1.28.0</version>
<style>AOSP</style>
</googleJavaFormat>
<licenseHeader>
Expand All @@ -349,25 +331,4 @@
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import com.example.poc.webmvc.config.ApplicationProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.persistence.autoconfigure.EntityScan;

@SpringBootApplication
@EntityScan(basePackages = {"com.example.poc.webmvc.entities"})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import java.util.stream.Collector;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,25 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.item.database.AbstractPagingItemReader;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.infrastructure.item.database.AbstractPagingItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

@RequiredArgsConstructor
@Component
@StepScope
public class CustomItemReader<T> extends AbstractPagingItemReader<List<Long>> {

private final PostRepository postRepository;

private List<List<Long>> ids = new ArrayList<>();

@Value("#{jobParameters['key']}")
private String titleValue;

@BeforeStep
public void beforeStep(final StepExecution stepExecution) {
JobParameters parameters = stepExecution.getJobExecution().getJobParameters();
// use your parameters
this.titleValue = parameters.getString("key");
public CustomItemReader(PostRepository postRepository) {
this.postRepository = postRepository;
}
Comment on lines +27 to 29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Constructor must call setPageSize(1) and should override setName() with the concrete class name.

Critical — missing setPageSize(1):

doReadPage() adds exactly one element to results per invocation. AbstractPagingItemReader.doRead() only calls doReadPage() again when current >= pageSize. With the default pageSize = 10 and results.size() == 1 after the first doReadPage() call: after yielding index 0, current becomes 1, which is less than pageSize (10), so no new page is loaded. The next doRead() call computes next = 1, 1 < results.size() (1) is false, and returns null — the end-of-data signal. The reader will process only the first page and terminate.

Moderate — setName() uses the abstract class name:

AbstractPagingItemReader's default constructor calls setName(ClassUtils.getShortName(AbstractPagingItemReader.class)), which means all subclasses that don't override this will share the key prefix "AbstractPagingItemReader" in ExecutionContext. JpaPagingItemReader (and similarly JdbcPagingItemReader) each override this in their constructors with setName(ClassUtils.getShortName(<ConcreteClass>.class))CustomItemReader should follow the same pattern.

🐛 Proposed fix
 public CustomItemReader(PostRepository postRepository) {
+    setPageSize(1);
+    setName(ClassUtils.getShortName(CustomItemReader.class));
     this.postRepository = postRepository;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public CustomItemReader(PostRepository postRepository) {
this.postRepository = postRepository;
}
public CustomItemReader(PostRepository postRepository) {
setPageSize(1);
setName(ClassUtils.getShortName(CustomItemReader.class));
this.postRepository = postRepository;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@poc-rest-api/spring-boot-rest-webmvc/src/main/java/com/example/poc/webmvc/batch/CustomItemReader.java`
around lines 27 - 29, The CustomItemReader constructor must set the reader page
size to 1 and set a concrete reader name: in the CustomItemReader(PostRepository
postRepository) constructor call setPageSize(1) so doReadPage() loads pages
correctly (one item per page), and call
setName(ClassUtils.getShortName(CustomItemReader.class)) so the ExecutionContext
key is unique to this class (ensure ClassUtils is available/imported). Make
these calls inside the constructor after assigning postRepository.


@Override
Expand All @@ -52,6 +49,10 @@ protected void doReadPage() {
/ getPageSize()))
.values());
}
results.add(this.ids.get(getPage() * getPageSize()));
if (!CollectionUtils.isEmpty(ids)) {
if (getPage() < this.ids.size()) {
results.add(this.ids.get(getPage()));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import com.example.poc.webmvc.dto.PostDTO;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.infrastructure.item.Chunk;
import org.springframework.batch.infrastructure.item.ItemWriter;
import org.springframework.stereotype.Component;

@Component
Expand Down
Loading