spring-r2dbc

<!-- pom.xml --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>r2dbc-postgresql</artifactId> <scope>runtime</scope> </dependency> </dependencies>

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "spring-r2dbc" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-spring-r2dbc

Spring Data R2DBC

Quick Start

<!-- pom.xml --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>r2dbc-postgresql</artifactId> <scope>runtime</scope> </dependency> </dependencies>

application.yml

spring: r2dbc: url: r2dbc:postgresql://localhost:5432/mydb username: ${DB_USERNAME} password: ${DB_PASSWORD} pool: enabled: true initial-size: 5 max-size: 20

Entity Definition

import org.springframework.data.annotation.Id; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Column;

@Table("products") public class Product {

@Id
private Long id;

@Column("product_name")
private String name;

private String description;
private BigDecimal price;

@Column("category_id")
private Long categoryId;

@CreatedDate
private Instant createdAt;

@LastModifiedDate
private Instant updatedAt;

private boolean active;

// Constructors, getters, setters...

}

// For immutable entities with records (Java 17+) @Table("orders") public record Order( @Id Long id, @Column("customer_id") Long customerId, BigDecimal total, OrderStatus status, @CreatedDate Instant createdAt ) { public Order withStatus(OrderStatus newStatus) { return new Order(id, customerId, total, newStatus, createdAt); } }

Repository Interface

import org.springframework.data.r2dbc.repository.R2dbcRepository; import org.springframework.data.r2dbc.repository.Query; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono;

public interface ProductRepository extends R2dbcRepository<Product, Long> {

// Automatic derived queries
Flux&#x3C;Product> findByActiveTrue();
Flux&#x3C;Product> findByNameContainingIgnoreCase(String name);
Flux&#x3C;Product> findByCategoryId(Long categoryId);
Flux&#x3C;Product> findByPriceBetween(BigDecimal min, BigDecimal max);
Mono&#x3C;Product> findByNameIgnoreCase(String name);

// Ordering and limiting
Flux&#x3C;Product> findTop10ByActiveTrueOrderByCreatedAtDesc();

// Count and Exists
Mono&#x3C;Long> countByActiveTrue();
Mono&#x3C;Boolean> existsByName(String name);

// Custom query
@Query("SELECT * FROM products WHERE category_id = :categoryId AND price &#x3C; :maxPrice")
Flux&#x3C;Product> findByCategoryWithMaxPrice(Long categoryId, BigDecimal maxPrice);

@Query("UPDATE products SET active = false WHERE id = :id")
@Modifying
Mono&#x3C;Integer> deactivateProduct(Long id);

// Projection with DTO
@Query("SELECT id, product_name as name, price FROM products WHERE active = true")
Flux&#x3C;ProductSummary> findAllSummaries();

}

public record ProductSummary(Long id, String name, BigDecimal price) {}

Full Reference: See database-client.md for complex queries with DatabaseClient.

Service Layer

@Service @RequiredArgsConstructor @Slf4j public class ProductService {

private final ProductRepository productRepository;

// Create
public Mono&#x3C;Product> createProduct(CreateProductRequest request) {
    Product product = Product.create(request.name(), request.description(), request.price());
    product.setCategoryId(request.categoryId());
    return productRepository.save(product)
        .doOnSuccess(p -> log.info("Created product: {}", p.getId()));
}

// Read
public Mono&#x3C;Product> getProduct(Long id) {
    return productRepository.findById(id)
        .switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
}

public Flux&#x3C;Product> getAllActiveProducts() {
    return productRepository.findByActiveTrue();
}

// Update
public Mono&#x3C;Product> updateProduct(Long id, UpdateProductRequest request) {
    return productRepository.findById(id)
        .switchIfEmpty(Mono.error(new ProductNotFoundException(id)))
        .map(product -> {
            if (request.name() != null) product.setName(request.name());
            if (request.price() != null) product.setPrice(request.price());
            return product;
        })
        .flatMap(productRepository::save);
}

// Delete (soft delete)
public Mono&#x3C;Void> deactivateProduct(Long id) {
    return productRepository.deactivateProduct(id)
        .filter(count -> count > 0)
        .switchIfEmpty(Mono.error(new ProductNotFoundException(id)))
        .then();
}

}

Reactive Transactions

@Service @RequiredArgsConstructor public class OrderService {

private final OrderRepository orderRepository;
private final OrderItemRepository orderItemRepository;

@Transactional
public Mono&#x3C;Order> createOrder(CreateOrderRequest request) {
    return validateProducts(request.items())
        .then(calculateTotal(request.items()))
        .flatMap(total -> {
            Order order = new Order(null, request.customerId(), total, OrderStatus.PENDING, null);
            return orderRepository.save(order);
        })
        .flatMap(order -> saveOrderItems(order.id(), request.items())
            .then(Mono.just(order)));
}

private Mono&#x3C;BigDecimal> calculateTotal(List&#x3C;OrderItemRequest> items) {
    return Flux.fromIterable(items)
        .flatMap(item -> productRepository.findById(item.productId())
            .map(p -> p.getPrice().multiply(BigDecimal.valueOf(item.quantity()))))
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}

}

Full Reference: See transactions.md for TransactionalOperator and relation handling.

Best Practices

Do Don't

Use R2DBC for WebFlux applications Mix JDBC and R2DBC

Configure connection pool Use without pool

Handle relations manually with batch queries Expect JPA-like lazy loading

Use @Transactional or TransactionalOperator

Forget transaction management

Use StepVerifier for testing Use .block() in production

When NOT to Use This Skill

  • Blocking applications - Use spring-data-jdbc or spring-data-jpa

  • Complex ORM features - R2DBC is simple, use JPA for lazy loading

  • Not using WebFlux - R2DBC is for reactive stack

  • MongoDB reactive - Use spring-data-mongodb reactive repositories

Common Pitfalls

Error Cause Solution

NoSuchBeanDefinitionException: ConnectionFactory

Missing R2DBC driver Add r2dbc-postgresql/mysql dependency

Connection timeout

Pool exhausted Increase max-size, check connection leaks

TransactionRequiredException

Missing @Transactional Add annotation or use TransactionalOperator

Entity not mapped Missing annotations Verify @Table, @Id, @Column

N+1 queries Loading relations Use batch queries with IN clause

Anti-Patterns

Anti-Pattern Problem Solution

Using .block() in production Blocks event loop Use reactive operators

N+1 queries for relations Performance issues Use batch queries with IN

Missing connection pool Connection exhaustion Configure r2dbc-pool

Large transactions Connection held too long Keep transactions short

No error handling Silent failures Use onErrorResume, onErrorMap

Quick Troubleshooting

Problem Diagnostic Fix

Connection timeout Check pool settings Increase max-size, check leaks

Entity not mapped Check annotations Add @Table, @Id, @Column

Transaction not working Check @Transactional Use TransactionalOperator if needed

Query returns empty Check column names Verify mapping matches DB

Pool exhausted Monitor active connections Increase pool, fix leaks

Reference Files

File Content

database-client.md DatabaseClient, Dynamic Queries, Aggregations

transactions.md Transactions, Relations, Pagination

advanced.md Auditing, Converters, Pool Config, Testing

External Documentation

  • Spring Data R2DBC

  • R2DBC Specification

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review