diff --git a/r2dbc/boot-jooq-r2dbc-sample/pom.xml b/r2dbc/boot-jooq-r2dbc-sample/pom.xml
index 79db02b3c..4d3ca30ec 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/pom.xml
+++ b/r2dbc/boot-jooq-r2dbc-sample/pom.xml
@@ -336,6 +336,12 @@
+
+
+ src/main/resources/**/*.sql
+
+
+
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/config/Initializer.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/config/Initializer.java
index eae3c123d..8458a3c41 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/config/Initializer.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/config/Initializer.java
@@ -4,36 +4,37 @@
import static com.example.jooq.r2dbc.testcontainersflyway.db.tables.PostComments.POST_COMMENTS;
import static com.example.jooq.r2dbc.testcontainersflyway.db.tables.Posts.POSTS;
import static com.example.jooq.r2dbc.testcontainersflyway.db.tables.Tags.TAGS;
-import static org.jooq.impl.DSL.multiset;
-import static org.jooq.impl.DSL.select;
import com.example.jooq.r2dbc.config.logging.Loggable;
-import com.example.jooq.r2dbc.model.response.PostCommentResponse;
-import com.example.jooq.r2dbc.model.response.PostResponse;
+import com.example.jooq.r2dbc.repository.PostRepository;
import com.example.jooq.r2dbc.testcontainersflyway.db.tables.records.PostCommentsRecord;
import com.example.jooq.r2dbc.testcontainersflyway.db.tables.records.PostsRecord;
import com.example.jooq.r2dbc.testcontainersflyway.db.tables.records.PostsTagsRecord;
import com.example.jooq.r2dbc.testcontainersflyway.db.tables.records.TagsRecord;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.jooq.DSLContext;
import org.jooq.DeleteUsingStep;
-import org.jooq.Record1;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
-@RequiredArgsConstructor
-@Slf4j
public class Initializer implements CommandLineRunner {
+ private static final Logger log = LoggerFactory.getLogger(Initializer.class);
private final DSLContext dslContext;
+ private final PostRepository postRepository;
+
+ public Initializer(DSLContext dslContext, PostRepository postRepository) {
+ this.dslContext = dslContext;
+ this.postRepository = postRepository;
+ }
@Override
@Loggable
public void run(String... args) {
- log.info("Running Initializer.....");
+ log.info("Running Initializer to use JOOQ only...");
DeleteUsingStep postsTagsRecordDeleteUsingStep =
dslContext.deleteFrom(POSTS_TAGS);
DeleteUsingStep tagsRecordDeleteUsingStep = dslContext.deleteFrom(TAGS);
@@ -89,40 +90,9 @@ public void run(String... args) {
"test comments 2")
.returningResult(POST_COMMENTS.ID))
.collectList())
- .thenMany(
- dslContext
- .select(
- POSTS.ID,
- POSTS.TITLE,
- POSTS.CONTENT,
- multiset(
- select(
- POST_COMMENTS.ID,
- POST_COMMENTS.CONTENT,
- POST_COMMENTS.CREATED_AT)
- .from(POST_COMMENTS)
- .where(
- POST_COMMENTS.POST_ID.eq(
- POSTS.ID)))
- .as("comments")
- .convertFrom(
- record3s ->
- record3s.into(
- PostCommentResponse.class)),
- multiset(
- select(TAGS.NAME)
- .from(TAGS)
- .join(POSTS_TAGS)
- .on(TAGS.ID.eq(POSTS_TAGS.TAG_ID))
- .where(
- POSTS_TAGS.POST_ID.eq(
- POSTS.ID)))
- .as("tags")
- .convertFrom(record -> record.map(Record1::value1)))
- .from(POSTS)
- .orderBy(POSTS.CREATED_AT))
+ .thenMany(postRepository.retrievePostsWithCommentsAndTags(null))
.subscribe(
- data -> log.debug("Retrieved data: {}", data.into(PostResponse.class)),
+ data -> log.debug("Retrieved data: {}", data),
error -> log.debug("error: ", error),
() -> log.debug("done"));
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Comment.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Comment.java
index 6451c695f..689f62349 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Comment.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Comment.java
@@ -2,23 +2,13 @@
import java.time.LocalDateTime;
import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
-@Getter
-@Setter
@ToString
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
@Table(value = "post_comments")
public class Comment {
@@ -35,4 +25,42 @@ public class Comment {
@Column("post_id")
private UUID postId;
+
+ public Comment() {}
+
+ public UUID getId() {
+ return id;
+ }
+
+ public Comment setId(UUID id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public Comment setContent(String content) {
+ this.content = content;
+ return this;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public Comment setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public UUID getPostId() {
+ return postId;
+ }
+
+ public Comment setPostId(UUID postId) {
+ this.postId = postId;
+ return this;
+ }
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Post.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Post.java
index 15377e05d..db9527f5c 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Post.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Post.java
@@ -1,12 +1,8 @@
package com.example.jooq.r2dbc.entities;
+import com.example.jooq.r2dbc.model.Status;
import java.time.LocalDateTime;
import java.util.UUID;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
@@ -16,12 +12,7 @@
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
-@Getter
-@Setter
@ToString
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
@Table(value = "posts")
public class Post {
@@ -36,7 +27,6 @@ public class Post {
private String content;
@Column("status")
- @Builder.Default
private Status status = Status.DRAFT;
@Column("created_at")
@@ -54,4 +44,78 @@ public class Post {
@Column("version")
@Version
private Short version;
+
+ public Post() {}
+
+ public UUID getId() {
+ return id;
+ }
+
+ public Post setId(UUID id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Post setTitle(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public Post setContent(String content) {
+ this.content = content;
+ return this;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public Post setStatus(Status status) {
+ this.status = status;
+ return this;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public Post setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public Post setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ return this;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public Post setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public Short getVersion() {
+ return version;
+ }
+
+ public Post setVersion(Short version) {
+ this.version = version;
+ return this;
+ }
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Status.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/Status.java
similarity index 64%
rename from r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Status.java
rename to r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/Status.java
index 34266adbd..ad19abf3e 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/entities/Status.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/Status.java
@@ -1,4 +1,4 @@
-package com.example.jooq.r2dbc.entities;
+package com.example.jooq.r2dbc.model;
public enum Status {
DRAFT,
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/response/PostResponse.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/response/PostResponse.java
index 970af25fd..e538a7613 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/response/PostResponse.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/model/response/PostResponse.java
@@ -1,5 +1,6 @@
package com.example.jooq.r2dbc.model.response;
+import com.example.jooq.r2dbc.model.Status;
import java.util.List;
import java.util.UUID;
@@ -7,5 +8,7 @@ public record PostResponse(
UUID id,
String title,
String content,
+ String createdBy,
+ Status status,
List comments,
List tags) {}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/PostTagRepository.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/PostTagRepository.java
new file mode 100644
index 000000000..7330704e0
--- /dev/null
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/PostTagRepository.java
@@ -0,0 +1,7 @@
+package com.example.jooq.r2dbc.repository;
+
+import com.example.jooq.r2dbc.entities.PostTagRelation;
+import java.util.UUID;
+import org.springframework.data.r2dbc.repository.R2dbcRepository;
+
+public interface PostTagRepository extends R2dbcRepository {}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/CustomPostRepository.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/CustomPostRepository.java
index c844fc6bc..a112fe964 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/CustomPostRepository.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/CustomPostRepository.java
@@ -1,10 +1,14 @@
package com.example.jooq.r2dbc.repository.custom;
import com.example.jooq.r2dbc.model.response.PostResponse;
+import org.jooq.Condition;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CustomPostRepository {
Mono> findByKeyword(String keyword, Pageable pageable);
+
+ Flux retrievePostsWithCommentsAndTags(Condition condition);
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/impl/CustomPostRepositoryImpl.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/impl/CustomPostRepositoryImpl.java
index 3ead0fdd5..9ec6cfb31 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/impl/CustomPostRepositoryImpl.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/repository/custom/impl/CustomPostRepositoryImpl.java
@@ -4,14 +4,14 @@
import static com.example.jooq.r2dbc.testcontainersflyway.db.Tables.POSTS_TAGS;
import static com.example.jooq.r2dbc.testcontainersflyway.db.Tables.POST_COMMENTS;
import static com.example.jooq.r2dbc.testcontainersflyway.db.Tables.TAGS;
-import static org.jooq.impl.DSL.multiset;
-import static org.jooq.impl.DSL.select;
import com.example.jooq.r2dbc.model.response.PostCommentResponse;
import com.example.jooq.r2dbc.model.response.PostResponse;
import com.example.jooq.r2dbc.repository.custom.CustomPostRepository;
+import java.util.List;
import org.jooq.Condition;
import org.jooq.DSLContext;
+import org.jooq.Field;
import org.jooq.Record1;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
@@ -19,7 +19,6 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
-import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -36,77 +35,82 @@ public CustomPostRepositoryImpl(DSLContext dslContext) {
public Mono> findByKeyword(String keyword, Pageable pageable) {
log.debug("Searching posts with keyword: {}, pageable: {}", keyword, pageable);
// Build the where condition dynamically
- Condition condition = DSL.trueCondition();
- if (StringUtils.hasText(keyword)) {
- condition =
- condition.and(
- DSL.or(
- POSTS.TITLE.likeIgnoreCase(
- DSL.concat(
- DSL.val("%"), DSL.val(keyword), DSL.val("%"))),
- POSTS.CONTENT.likeIgnoreCase(
- DSL.concat(
- DSL.val("%"),
- DSL.val(keyword),
- DSL.val("%")))));
- }
-
- // Construct the main data SQL query
- var dataQuery =
- dslContext
- .selectDistinct(
- POSTS.ID,
- POSTS.TITLE,
- POSTS.CONTENT,
- // Fetch comments as a multiset
- multiset(
- select(
- POST_COMMENTS.ID,
- POST_COMMENTS.CONTENT,
- POST_COMMENTS.CREATED_AT)
- .from(POST_COMMENTS)
- .where(POST_COMMENTS.POST_ID.eq(POSTS.ID)))
- .as("comments")
- .convertFrom(
- records -> records.into(PostCommentResponse.class)),
- // Fetch tags as a multiset
- multiset(
- select(TAGS.NAME)
- .from(TAGS)
- .join(POSTS_TAGS)
- .on(TAGS.ID.eq(POSTS_TAGS.TAG_ID))
- .where(POSTS_TAGS.POST_ID.eq(POSTS.ID)))
- .as("tags")
- .convertFrom(records -> records.map(Record1::value1)))
- .from(POSTS)
- .where(condition)
- .orderBy(getSortFields(pageable.getSort(), POSTS))
- .limit(pageable.getPageSize())
- .offset(pageable.getOffset());
-
+ Field searchValue = DSL.concat(DSL.val("%"), DSL.val(keyword), DSL.val("%"));
+ Condition condition =
+ DSL.or(
+ POSTS.TITLE.likeIgnoreCase(searchValue),
+ POSTS.CONTENT.likeIgnoreCase(searchValue));
// Construct the count query
var countQuery = dslContext.selectCount().from(POSTS).where(condition);
- // Execute queries reactively and build the result page
+ // Execute the data and count queries reactively and build the result page
return Mono.zip(
- Flux.from(dataQuery)
- .map(
- record ->
- new PostResponse(
- record.value1(), // Post ID
- record.value2(), // Post Title
- record.value3(), // Post Content
- record.value4(), // Comments
- record.value5() // Tags
- ))
+ // Fetch data query
+ retrievePostsWithCommentsAndTags(condition)
.doOnError(
e ->
log.error(
- "Error executing data query: {}",
- e.getMessage()))
- .collectList(),
- Mono.from(countQuery).map(Record1::value1))
- .doOnError(e -> log.error("Error executing count query: {}", e.getMessage()))
+ "Error fetching data query: {}",
+ e.getMessage(),
+ e))
+ .collectList(), // Collect the result into a list
+ // Fetch count query
+ Mono.from(countQuery).map(Record1::value1) // Get the count value
+ )
+ .doOnError(e -> log.error("Error executing queries: {}", e.getMessage(), e))
+ // Map into PageImpl
.map(tuple -> new PageImpl<>(tuple.getT1(), pageable, tuple.getT2()));
}
+
+ @Override
+ public Flux retrievePostsWithCommentsAndTags(Condition condition) {
+ // Start with a base condition
+ Condition whereCondition = DSL.trueCondition();
+
+ // Add the provided condition if it is not null
+ if (condition != null) {
+ whereCondition = whereCondition.and(condition);
+ }
+
+ // Construct the query
+ return Flux.from(
+ dslContext
+ .select(
+ POSTS.ID, // Post ID
+ POSTS.TITLE, // Post Title
+ POSTS.CONTENT, // Post Content
+ POSTS.CREATED_BY, // Post Created By
+ POSTS.STATUS, // Post status
+ // Fetch comments as a multiset
+ getCommentsMultiSet(),
+ // Fetch tags as a multiset
+ getTagsMultiSet())
+ .from(POSTS)
+ .where(whereCondition) // Apply the dynamic where condition
+ .orderBy(POSTS.CREATED_AT))
+ .map(record -> record.into(PostResponse.class));
+ }
+
+ private Field> getCommentsMultiSet() {
+ return DSL.multiset(
+ DSL.select(
+ POST_COMMENTS.ID,
+ POST_COMMENTS.CONTENT,
+ POST_COMMENTS.CREATED_AT)
+ .from(POST_COMMENTS)
+ .where(POST_COMMENTS.POST_ID.eq(POSTS.ID)))
+ .as("comments")
+ .convertFrom(record -> record.into(PostCommentResponse.class));
+ }
+
+ private Field> getTagsMultiSet() {
+ return DSL.multiset(
+ DSL.select(TAGS.NAME)
+ .from(TAGS)
+ .join(POSTS_TAGS)
+ .on(TAGS.ID.eq(POSTS_TAGS.TAG_ID))
+ .where(POSTS_TAGS.POST_ID.eq(POSTS.ID)))
+ .as("tags")
+ .convertFrom(record -> record.map(Record1::value1));
+ }
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/service/PostService.java b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/service/PostService.java
index d85c5c854..4e9794522 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/service/PostService.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/java/com/example/jooq/r2dbc/service/PostService.java
@@ -126,13 +126,20 @@ private Mono fetchOrInsertTag(String tagName) {
}
public Mono> findByKeyword(String keyword, Pageable pageable) {
- String sanitizedKeyword =
- StringUtils.hasText(keyword) ? keyword.replaceAll("[\n\r\t]", "_") : "";
+ // Check if the keyword has text
+ if (!StringUtils.hasText(keyword)) {
+ log.debug("findByKeyword called with empty or null keyword");
+ return Mono.empty();
+ }
+ // Sanitize the keyword to avoid injection-like issues
+ String sanitizedKeyword = keyword.replaceAll("[^a-zA-Z0-9\\s-]", "_");
log.debug(
- "findByKeyword with sanitizedKeyword :{} with offset :{} and limit :{}",
+ "findByKeyword [keyword: {}, sanitized: {}, page: {}, size: {}, sort: {}]",
+ keyword,
sanitizedKeyword,
- pageable.getOffset(),
- pageable.getPageSize());
+ pageable.getPageNumber(),
+ pageable.getPageSize(),
+ pageable.getSort());
return this.postRepository.findByKeyword(keyword, pageable).map(PaginatedResult::new);
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/main/resources/db/migration/postgresql/V1__01_init.sql b/r2dbc/boot-jooq-r2dbc-sample/src/main/resources/db/migration/postgresql/V1__01_init.sql
index 514f1b4c5..8ce2f4151 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/main/resources/db/migration/postgresql/V1__01_init.sql
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/main/resources/db/migration/postgresql/V1__01_init.sql
@@ -1,42 +1,49 @@
-CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-
-CREATE TABLE posts
-(
- ID uuid NOT NULL DEFAULT uuid_generate_v4 (),
- TITLE text,
- CONTENT text,
- STATUS varchar(50),
- created_at timestamptz DEFAULT NOW(),
- created_by text,
- updated_at timestamptz,
- version int DEFAULT 0,
- PRIMARY KEY (ID)
-);
-
-create table post_comments
-(
- id uuid not null DEFAULT uuid_generate_v4 (),
- content text,
- created_at timestamptz DEFAULT NOW(),
- POST_ID uuid,
- primary key (id),
- CONSTRAINT FK_POST_COMMENTS FOREIGN KEY (POST_ID) REFERENCES POSTS(ID)
-);
-
-create table tags
-(
- id uuid not null DEFAULT uuid_generate_v4 (),
- name text unique,
- created_at timestamptz DEFAULT NOW(),
- primary key (id)
-);
-
-CREATE TABLE posts_tags
-(
- post_id UUID NOT NULL,
- tag_id UUID NOT NULL,
- CONSTRAINT FK_POST_TAGS_PID FOREIGN KEY (post_id) REFERENCES posts(id),
- CONSTRAINT FK_POST_TAGS_TID FOREIGN KEY (tag_id) REFERENCES tags(id),
- CONSTRAINT UK_POST_TAGS UNIQUE (post_id, tag_id)
-);
-
+CREATE
+ EXTENSION IF NOT EXISTS "uuid-ossp";
+
+CREATE
+ TABLE
+ posts(
+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
+ title text,
+ content text,
+ status text,
+ created_at timestamptz DEFAULT NOW(),
+ created_by text,
+ updated_at timestamptz,
+ version INT DEFAULT 0,
+ PRIMARY KEY(id)
+ );
+
+CREATE
+ TABLE
+ post_comments(
+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
+ content text,
+ created_at timestamptz DEFAULT NOW(),
+ post_id uuid,
+ PRIMARY KEY(id),
+ CONSTRAINT FK_POST_COMMENTS FOREIGN KEY(post_id) REFERENCES POSTS(id)
+ );
+
+CREATE
+ TABLE
+ tags(
+ id uuid NOT NULL DEFAULT uuid_generate_v4(),
+ name text UNIQUE,
+ created_at timestamptz DEFAULT NOW(),
+ PRIMARY KEY(id)
+ );
+
+CREATE
+ TABLE
+ posts_tags(
+ post_id UUID NOT NULL,
+ tag_id UUID NOT NULL,
+ CONSTRAINT FK_POST_TAGS_PID FOREIGN KEY(post_id) REFERENCES posts(id),
+ CONSTRAINT FK_POST_TAGS_TID FOREIGN KEY(tag_id) REFERENCES tags(id),
+ CONSTRAINT UK_POST_TAGS UNIQUE(
+ post_id,
+ tag_id
+ )
+ );
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/common/ContainerConfig.java b/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/common/ContainerConfig.java
index 0b5983928..4ae52be22 100644
--- a/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/common/ContainerConfig.java
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/common/ContainerConfig.java
@@ -5,7 +5,6 @@
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
-import org.testcontainers.utility.MountableFile;
@TestConfiguration(proxyBeanMethods = false)
public class ContainerConfig {
@@ -13,9 +12,6 @@ public class ContainerConfig {
@Bean
@ServiceConnection
PostgreSQLContainer> postgreSQLContainer() {
- return new PostgreSQLContainer<>(DockerImageName.parse("postgres").withTag("17.2-alpine"))
- .withCopyFileToContainer(
- MountableFile.forClasspathResource("init.sql"),
- "/docker-entrypoint-initdb.d/init.sql");
+ return new PostgreSQLContainer<>(DockerImageName.parse("postgres").withTag("17.2-alpine"));
}
}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/respository/PostRepositoryTest.java b/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/respository/PostRepositoryTest.java
new file mode 100644
index 000000000..95a3d737c
--- /dev/null
+++ b/r2dbc/boot-jooq-r2dbc-sample/src/test/java/com/example/jooq/r2dbc/respository/PostRepositoryTest.java
@@ -0,0 +1,174 @@
+package com.example.jooq.r2dbc.repository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
+
+import com.example.jooq.r2dbc.common.ContainerConfig;
+import com.example.jooq.r2dbc.config.JooqConfiguration;
+import com.example.jooq.r2dbc.entities.Comment;
+import com.example.jooq.r2dbc.entities.Post;
+import com.example.jooq.r2dbc.entities.PostTagRelation;
+import com.example.jooq.r2dbc.entities.Tags;
+import com.example.jooq.r2dbc.model.Status;
+import com.example.jooq.r2dbc.model.response.PostCommentResponse;
+import com.example.jooq.r2dbc.model.response.PostResponse;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest;
+import org.springframework.context.annotation.Import;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@DataR2dbcTest
+@Import({ContainerConfig.class, JooqConfiguration.class})
+class PostRepositoryTest {
+
+ @Autowired private PostRepository postRepository;
+
+ @Autowired private TagRepository tagRepository;
+
+ @Autowired private CommentRepository postCommentRepository;
+
+ @Autowired private PostTagRepository postTagRepository;
+
+ @Autowired private DSLContext dslContext;
+
+ @BeforeEach
+ void cleanup() {
+ // Ensure existing data is deleted
+ StepVerifier.create(
+ postTagRepository
+ .deleteAll()
+ .then(tagRepository.deleteAll())
+ .then(postCommentRepository.deleteAll())
+ .then(postRepository.deleteAll()))
+ .expectSubscription()
+ .expectComplete()
+ .verify(Duration.ofSeconds(30));
+ }
+
+ @Test
+ void testInsertPostViaR2dbcAndRetrieveViaDSLContext() {
+
+ Flux postResponseFlux =
+ // Step 1: Insert a new post
+ createPost()
+ .flatMap(
+ post -> {
+ UUID postId = post.getId();
+
+ // Step 2: Insert a new tag
+ return tagRepository
+ .save(new Tags().setName("java"))
+ .flatMap(
+ tag -> {
+ UUID tagId = tag.getId();
+
+ // Step 3: Link post and tag
+ return postTagRepository
+ .save(
+ new PostTagRelation(
+ postId, tagId))
+ .thenReturn(postId);
+ });
+ })
+
+ // Step 4: Insert comments
+ .flatMapMany(
+ postId ->
+ createComments(postId, "test comments", "test comments 2"))
+ .thenMany(
+ // Step 5: Retrieve data using jOOQ
+ Flux.from(postRepository.retrievePostsWithCommentsAndTags(null)));
+
+ StepVerifier.create(postResponseFlux)
+ .expectNextMatches(
+ postResponse -> {
+ // Assertions for post data
+ verifyBasicPostResponse(postResponse);
+ assertThat(postResponse.comments()).isNotEmpty().hasSize(2);
+ assertThat(postResponse.tags()).isNotEmpty().hasSize(1);
+
+ // Assertions for
+ assertPostComments(postResponse);
+
+ // Assertions for tags
+ assertThat(postResponse.tags().getFirst()).isEqualTo("java");
+
+ return true;
+ })
+ .expectComplete()
+ .verify();
+ }
+
+ @Test
+ void testInsertPostOnlyViaR2dbcAndRetrieveViaDSLContext() {
+
+ Flux postResponseFlux =
+ // Step 1: Insert a new post
+ createPost()
+ .thenMany(
+ // Step 2: Retrieve data using jOOQ
+ postRepository.retrievePostsWithCommentsAndTags(null));
+
+ StepVerifier.create(postResponseFlux)
+ .expectNextMatches(
+ postResponse -> {
+ // Assertions for post data
+ verifyBasicPostResponse(postResponse);
+ assertThat(postResponse.comments()).isEmpty();
+ assertThat(postResponse.tags()).isEmpty();
+
+ return true;
+ })
+ .expectComplete()
+ .verify();
+ }
+
+ private Mono createPost() {
+ return postRepository.save(
+ new Post().setTitle("jooq test").setContent("content of Jooq test"));
+ }
+
+ private void verifyBasicPostResponse(PostResponse postResponse) {
+ assertThat(postResponse.id()).isInstanceOf(UUID.class);
+ assertThat(postResponse.title()).isEqualTo("jooq test");
+ assertThat(postResponse.content()).isEqualTo("content of Jooq test");
+ assertThat(postResponse.createdBy()).isEqualTo("appUser");
+ assertThat(postResponse.status()).isEqualTo(Status.DRAFT);
+ }
+
+ private void assertPostComments(PostResponse postResponse) {
+ PostCommentResponse postCommentResponse = postResponse.comments().getFirst();
+ assertThat(postCommentResponse.id()).isInstanceOf(UUID.class);
+ assertThat(postCommentResponse.createdAt())
+ .isNotNull()
+ .isInstanceOf(LocalDateTime.class)
+ .isCloseTo(LocalDateTime.now(), within(1, ChronoUnit.MINUTES));
+ assertThat(postCommentResponse.content()).isEqualTo("test comments");
+
+ PostCommentResponse last = postResponse.comments().getLast();
+ assertThat(last.id()).isInstanceOf(UUID.class);
+ assertThat(last.createdAt())
+ .isNotNull()
+ .isInstanceOf(LocalDateTime.class)
+ .isCloseTo(LocalDateTime.now(), within(1, ChronoUnit.MINUTES));
+ assertThat(last.createdAt()).isNotNull();
+ assertThat(last.content()).isEqualTo("test comments 2");
+ }
+
+ private Flux createComments(UUID postId, String... contents) {
+ return Flux.fromArray(contents)
+ .flatMap(
+ content ->
+ postCommentRepository.save(
+ new Comment().setPostId(postId).setContent(content)));
+ }
+}
diff --git a/r2dbc/boot-jooq-r2dbc-sample/src/test/resources/init.sql b/r2dbc/boot-jooq-r2dbc-sample/src/test/resources/init.sql
deleted file mode 100644
index d159cc56a..000000000
--- a/r2dbc/boot-jooq-r2dbc-sample/src/test/resources/init.sql
+++ /dev/null
@@ -1 +0,0 @@
-CREATE EXTENSION IF NOT EXISTS "uuid-ossp";