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:
- Java
- Kotlin
- Scala
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>
Gradle:
implementation("dev.typr:foundations-jdbc-spring:1.0.0-RC1")
implementation("dev.typr:foundations-jdbc-kotlin: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>
<dependency>
<groupId>dev.typr</groupId>
<artifactId>foundations-jdbc-kotlin</artifactId>
<version>1.0.0-RC1</version>
</dependency>
Gradle:
implementation("dev.typr:foundations-jdbc-spring:1.0.0-RC1")
implementation("dev.typr:foundations-jdbc-scala_3: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>
<dependency>
<groupId>dev.typr</groupId>
<artifactId>foundations-jdbc-scala_3</artifactId>
<version>1.0.0-RC1</version>
</dependency>
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
DataSourcebean is present (@ConditionalOnBean(DataSource.class)) - No existing
Transactorbean 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:
- Kotlin
- Java
- Scala
@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)
}
@Service
class OrderService {
private final Transactor tx;
OrderService(Transactor tx) {
this.tx = tx;
}
@Transactional
String getGreeting() {
return Fragment
.of("SELECT 'Hello from Oracle' FROM dual")
.query(RowCodec.of(OracleTypes.varchar2)
.exactlyOne())
.transact(tx);
}
}
@Service
class OrderService(tx: Transactor):
@Transactional
def 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:
| Context | Behavior |
|---|---|
Inside @Transactional | Joins the existing Spring-managed transaction. Does not change auto-commit, commit, or rollback — Spring controls the transaction lifecycle. |
Outside @Transactional | Manages 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.