Skip to content

Commit

Permalink
Add direct batch insert (#36)
Browse files Browse the repository at this point in the history
* Move batch to separate package

* Add direct batch insert

* Publish test & coverage reports

* Improve documentation

* Prepare release

---------

Co-authored-by: kaklakariada <[email protected]>
  • Loading branch information
kaklakariada and kaklakariada authored Dec 23, 2024
1 parent c0000c4 commit 855d793
Show file tree
Hide file tree
Showing 22 changed files with 598 additions and 158 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
uses: actions/configure-pages@v5
- name: Build Javadoc
run: ./gradlew javadoc --info
- name: Build Reports
run: ./gradlew check jacocoTestReport --exclude-task integrationTest --info
- name: Collect artifacts
run: cp -r build/reports/ build/docs/
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.0] - unreleased
## [0.10.0] - unreleased

## [0.9.0] - 2024-12-23

- [PR #33](https://github.com/itsallcode/simple-jdbc/pull/33): Update dependencies
- [#32](https://github.com/itsallcode/simple-jdbc/issues/32): Add support for data sources
- [#31](https://github.com/itsallcode/simple-jdbc/issues/31): Avoid unnecessary `setAutoCommit()` calls in `Transaction`
- [PR #36](https://github.com/itsallcode/simple-jdbc/pull/36): Add direct batch insert

## [0.8.0] - 2024-11-03

Expand Down
119 changes: 100 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,64 @@ Wrapper to simplify working with JDBC.
[![Maven Central](https://img.shields.io/maven-central/v/org.itsallcode/simple-jdbc)](https://search.maven.org/artifact/org.itsallcode/simple-jdbc)

* [Changelog](CHANGELOG.md)
* [API JavaDoc](https://blog.itsallcode.org/simple-jdbc/javadoc/org.itsallcode.jdbc/module-summary.html)
* [Test report](https://blog.itsallcode.org/simple-jdbc/reports/tests/test/index.html)
* [Coverage report](https://blog.itsallcode.org/simple-jdbc/reports/jacoco/test/html/index.html)

## Usage

This project requires Java 17 or later.

Add dependency to your gradle project:
### Add Dependency

Add dependency to your Gradle project:

```groovy
dependencies {
implementation 'org.itsallcode:simple-jdbc:0.8.0'
implementation 'org.itsallcode:simple-jdbc:0.9.0'
}
```

Add dependency to your Maven project:

```xml
<dependency>
<groupId>org.itsallcode</groupId>
<artifactId>simple-jdbc</artifactId>
<version>0.9.0</version>
</dependency>
```

### Features

See features and API documentation in the [API documentation](https://blog.itsallcode.org/simple-jdbc/javadoc/org.itsallcode.jdbc/module-summary.html).

### Examples

See complete example code in [ExampleTest](src/test/java/org/itsallcode/jdbc/example/ExampleTest.java).

#### Imports

```java
import org.itsallcode.jdbc.ConnectionFactory;
import org.itsallcode.jdbc.SimpleConnection;
import org.itsallcode.jdbc.Transaction;
import org.itsallcode.jdbc.resultset.batch.BatchInsert;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;
```

#### Create `SimpleConnection`

```java
final ConnectionFactory connectionFactory = ConnectionFactory.create(Context.builder().build());
try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) {
// Use connection...
}
```

#### Batch Insert Using Rows

```java
// Define a model record or class
record Name(int id, String name) {
Expand All @@ -36,28 +81,64 @@ record Name(int id, String name) {
}
}

import org.itsallcode.jdbc.ConnectionFactory;
import org.itsallcode.jdbc.SimpleConnection;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;
connection.batchInsert(Name.class)
.into("NAMES", List.of("ID", "NAME"))
.rows(Stream.of(new Name(1, "a"), new Name(2, "b"), new Name(3, "c")))
.mapping(Name::setPreparedStatement)
.start();
```

// Execute query and fetch result
final ConnectionFactory connectionFactory = ConnectionFactory.create(Context.builder().build());
try (SimpleConnection connection = connectionFactory.create(H2TestFixture.H2_MEM_JDBC_URL, "user", "password")) {
connection.executeScript(readResource("/schema.sql"));
connection.batchInsert(Name.class)
.into("NAMES", List.of("ID", "NAME"))
.rows(Stream.of(new Name(1, "a"), new Name(2, "b"), new Name(3, "c")))
.mapping(Name::setPreparedStatement)
.start();
try (SimpleResultSet<Row> rs = connection.query("select * from names order by id")) {
final List<Row> result = rs.stream().toList();
assertEquals(3, result.size());
assertEquals(1, result.get(0).get(0).value());
#### Direct Batch Insert

This allows using batch inserts without creating objects for each row to avoid memory allocations.

```java
try (BatchInsert batch = transaction.batchInsert().into("NAMES", List.of("ID", "NAME")).build()) {
for (int i = 0; i < 5; i++) {
final int id = i + 1;
batch.add(ps -> {
ps.setInt(1, id);
ps.setString(2, "name" + id);
});
}
}
```

#### Transactions

`Transaction` implements `DbOperations` which provides most of the methods as `SimpleConnection`. Transactions will be automatically rolled back during close unless you commit before.

```java
try (Transaction transaction = connection.startTransaction()) {
// Use transaction
// ...
// Commit successful transaction
transaction.commit();
}
```

#### Query Using Generic Row Type

```java
try (SimpleResultSet<Row> rs = connection.query("select * from names order by id")) {
final List<Row> result = rs.stream().toList();
assertEquals(3, result.size());
assertEquals(1, result.get(0).get(0).value());
}
```

#### Query Using Row Mapper and Prepared Statement

```java
try (SimpleResultSet<Name> result = connection.query("select id, name from names where id = ?",
ps -> ps.setInt(1, 2),
(rs, idx) -> new Name(rs.getInt("id"), rs.getString("name")))) {
final List<Name> names = result.stream().toList();
assertEquals(1, names.size());
assertEquals(new Name(2, "b"), names.get(0));
}
```

## Development

### Check if dependencies are up-to-date
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
}

group = 'org.itsallcode'
version = '0.8.0'
version = '0.9.0'

java {
toolchain {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.itsallcode.jdbc.batch.RowBatchInsertBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -34,11 +35,11 @@ void performanceTestRowStmtSetter() {
}).rows(generateStream(rowCount)).start();
}

private BatchInsertBuilder<NameRow> testee() {
private RowBatchInsertBuilder<NameRow> testee() {
final PreparedStatement stmt = createNoopPreparedStatement();
when(connectionMock.prepareStatement(anyString()))
.thenReturn(new SimplePreparedStatement(null, null, stmt, "sql"));
return new BatchInsertBuilder<NameRow>(connectionMock::prepareStatement);
return new RowBatchInsertBuilder<NameRow>(connectionMock::prepareStatement);
}

private PreparedStatement createNoopPreparedStatement() {
Expand Down
61 changes: 59 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,66 @@
/**
* Module containing a simple wrapper for the JDBC API.
* <a href="https://github.com/itsallcode/simple-jdbc">Simple-JDBC</a> is a
* wrapper for the JDBC API that simplifies the use of JDBC.
* <ul>
* <li>Create a {@link org.itsallcode.jdbc.SimpleConnection}
* <ul>
* <li>with a connection factory using {@link java.sql.DriverManager}, see
* {@link org.itsallcode.jdbc.DataSourceConnectionFactory}</li>
* <li>with a {@link javax.sql.DataSource}, see
* {@link org.itsallcode.jdbc.DataSourceConnectionFactory}</li>
* </ul>
* </li>
* <li>Execute statements
* <ul>
* <li>... single statement:
* {@link org.itsallcode.jdbc.DbOperations#executeStatement(String)}</li>
* <li>... with a prepared statement:
* {@link org.itsallcode.jdbc.DbOperations#executeStatement(String, org.itsallcode.jdbc.PreparedStatementSetter)}</li>
* <li>... semicolon separated SQL script:
* {@link org.itsallcode.jdbc.DbOperations#executeScript(String)}</li>
* </ul>
* <li>Execute queries
* <ul>
* <li>...with generic result types:
* {@link org.itsallcode.jdbc.DbOperations#query(String)}</li>
* <li>...with a {@link org.itsallcode.jdbc.resultset.RowMapper}, returning
* custom result types:
* {@link org.itsallcode.jdbc.DbOperations#query(String, org.itsallcode.jdbc.resultset.RowMapper)}</li>
* <li>...with a prepared statement:
* {@link org.itsallcode.jdbc.DbOperations#query(String, org.itsallcode.jdbc.PreparedStatementSetter, org.itsallcode.jdbc.resultset.RowMapper)}</li>
* </ul>
* </li>
* <li>Use transactions:
* <ul>
* <li>Start a new transaction:
* {@link org.itsallcode.jdbc.SimpleConnection#startTransaction()}</li>
* <li>Use all operations from {@link org.itsallcode.jdbc.DbOperations} in a
* transaction</li>
* <li>Rollback a transaction:
* {@link org.itsallcode.jdbc.Transaction#rollback()}</li>
* <li>Commit a transaction:
* {@link org.itsallcode.jdbc.Transaction#commit()}</li>
* <li>Automatic rollback using try-with-resources if not committed:
* {@link org.itsallcode.jdbc.Transaction#close()}</li>
* </ul>
* </li>
* <li>Batch inserts
* <ul>
* <li>... using a {@link java.util.stream.Stream} or {@link java.util.Iterator}
* of row objects:
* {@link org.itsallcode.jdbc.SimpleConnection#batchInsert(java.lang.Class)}</li>
* <li>... directly setting values of a {@link java.sql.PreparedStatement}:
* {@link org.itsallcode.jdbc.SimpleConnection#batchInsert()}</li>
* </ul>
* </li>
* <li>Simplified Exception Handling: converts checked exception
* {@link java.sql.SQLException} to runtime exception
* {@link org.itsallcode.jdbc.UncheckedSQLException}</li>
* </ul>
*/

module org.itsallcode.jdbc {
exports org.itsallcode.jdbc;
exports org.itsallcode.jdbc.batch;
exports org.itsallcode.jdbc.identifier;
exports org.itsallcode.jdbc.resultset;
exports org.itsallcode.jdbc.resultset.generic;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/itsallcode/jdbc/ConnectionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
/**
* This class connects to a database using {@link DriverManager} and returns new
* {@link SimpleConnection}s.
* <p>
* Create a new instance using {@link #create()}.
*/
public final class ConnectionFactory {
private final Context context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
/**
* This class connects to a database using a {@link DataSource} and returns new
* {@link SimpleConnection}s.
* <p>
* Create a new instance using {@link #create(String, DataSource)} or
* {@link #create(DbDialect, DataSource)}.
*/
public final class DataSourceConnectionFactory {
private final Context context;
Expand Down
44 changes: 33 additions & 11 deletions src/main/java/org/itsallcode/jdbc/DbOperations.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@

import static java.util.function.Predicate.not;

import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Stream;

import org.itsallcode.jdbc.batch.BatchInsertBuilder;
import org.itsallcode.jdbc.batch.RowBatchInsertBuilder;
import org.itsallcode.jdbc.resultset.RowMapper;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;

/**
* Interface for various DB operations.
* Interface containing various DB operations. Use one of the implementations
* {@link SimpleConnection} or {@link Transaction}.
*/
public interface DbOperations extends AutoCloseable {

/**
* Execute all commands in a SQL script, separated with {@code ;}.
*
* @param sqlScript the script to execute.
* @param sqlScript script to execute.
*/
default void executeScript(final String sqlScript) {
Arrays.stream(sqlScript.split(";"))
Expand All @@ -25,32 +32,32 @@ default void executeScript(final String sqlScript) {
}

/**
* Execute a single SQL statement.
* Execute a single SQL statement as a prepared statement with placeholders.
*
* @param sql the statement
* @param sql SQL statement
* @param preparedStatementSetter prepared statement setter
*/
void executeStatement(final String sql, PreparedStatementSetter preparedStatementSetter);

/**
* Execute a single SQL statement.
*
* @param sql the statement
* @param sql SQL statement
*/
void executeStatement(final String sql);

/**
* Execute a SQL query and return a {@link SimpleResultSet result set} with
* generic {@link Row}s.
*
* @param sql the query
* @return the result set
* @param sql SQL query
* @return result set
*/
SimpleResultSet<Row> query(final String sql);

/**
* Execute a SQL query and return a {@link SimpleResultSet result set} with rows
* converted to a custom type {@link T}.
* converted to a custom type {@link T} using the given {@link RowMapper}.
*
* @param <T> generic row type
* @param sql SQL query
Expand All @@ -73,13 +80,28 @@ <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter pre
final RowMapper<T> rowMapper);

/**
* Create a batch insert builder
* Create a batch insert builder for inserting rows by directly setting values
* of a {@link PreparedStatement}.
* <p>
* If you want to insert rows from an {@link Iterator} or a {@link Stream}, use
* {@link #batchInsert(Class)}.
*
* @return batch insert builder
*/
BatchInsertBuilder batchInsert();

/**
* Create a row-based batch insert builder for inserting rows from an
* {@link Iterator} or a {@link Stream}.
* <p>
* If you want to insert rows by directly setting values of a
* {@link PreparedStatement}, use {@link #batchInsert()}.
*
* @param rowType row type
* @param <T> row type
* @return batch insert builder
* @return row-based batch insert builder
*/
<T> BatchInsertBuilder<T> batchInsert(final Class<T> rowType);
<T> RowBatchInsertBuilder<T> batchInsert(final Class<T> rowType);

@Override
void close();
Expand Down
Loading

0 comments on commit 855d793

Please sign in to comment.