Skip to main content

Spring Boot

The foundations-jdbc-spring module integrates foundations-jdbc with Spring Boot. It provides auto-configuration that creates a Transactor bean backed by your Spring-managed DataSource, and automatically participates in @Transactional contexts.

Dependencies

Add the Spring integration module alongside spring-boot-starter-jdbc:

Gradle:

implementation("dev.typr:foundations-jdbc-spring:1.0.0-RC1")
implementation("org.springframework.boot:spring-boot-starter-jdbc")

Maven:

<dependency>
<groupId>dev.typr</groupId>
<artifactId>foundations-jdbc-spring</artifactId>
<version>1.0.0-RC1</version>
</dependency>
No HikariCP module needed

You do not need foundations-jdbc-hikari. Spring Boot's spring-boot-starter-jdbc already configures a HikariCP connection pool. The Spring transactor obtains connections through Spring's DataSource, which is already pooled.

Auto-Configuration

TransactorAutoConfiguration creates a Transactor bean automatically when:

  • A DataSource bean is present (@ConditionalOnBean(DataSource.class))
  • No existing Transactor bean is defined (@ConditionalOnMissingBean(Transactor.class))

Just configure your datasource in application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=mypassword

Then inject the Transactor directly into your services:

@Service
class OrderService(private val tx: Transactor) {

@Transactional
fun getGreeting(): String =
sql { "SELECT 'Hello from Oracle' FROM dual" }
.query(RowCodec.of(OracleTypes.varchar2).exactlyOne())
.transact(tx)
}

Transaction Behavior

The SpringTransactor adapts its behavior based on the current Spring transaction context:

ContextBehavior
Inside @TransactionalJoins the existing Spring-managed transaction. Does not change auto-commit, commit, or rollback — Spring controls the transaction lifecycle.
Outside @TransactionalManages its own transaction: sets auto-commit to false, commits on success, rolls back on error, and releases the connection.

This means @Transactional works exactly as you'd expect. Multiple transact() calls inside a @Transactional method share the same connection and transaction:

@Service
public class OrderService {
private final Transactor tx;

public OrderService(Transactor tx) {
this.tx = tx;
}

@Transactional
public void placeOrder(Order order) {
insertOrder.on(order).transact(tx);
updateInventory.on(order.itemId()).transact(tx);
// both share the same transaction — committed together by Spring
}

public List<Order> listOrders() {
// no @Transactional — the transactor manages its own transaction
return selectOrders.transact(tx);
}
}

Database errors throw DatabaseException, which is unchecked — so @Transactional rolls back automatically without any extra configuration.

Manual Configuration

If you need to customize the transactor (e.g., different datasource, adding a query listener), disable auto-configuration by defining your own Transactor bean:

@Configuration
public class AppConfig {
@Bean
public Transactor transactor(DataSource dataSource) {
return SpringTransactor.create(dataSource);
}
}

Since TransactorAutoConfiguration uses @ConditionalOnMissingBean, your custom bean takes precedence.

Example Project

See the example-spring-boot directory for a working Spring Boot application using DuckDB with foundations-jdbc.