Skip to content

Commit

Permalink
Statement batching (#45)
Browse files Browse the repository at this point in the history
* Rename executeStatement methods to executeUpdate

* Add batch() method to DbOperations

* Use executeUpdate() instead of execute()

* Use simple statement for queries without parameter

---------

Co-authored-by: kaklakariada <[email protected]>
Co-authored-by: kaklakariada <[email protected]>
  • Loading branch information
3 people authored Jan 6, 2025
1 parent f00bce7 commit 3518777
Show file tree
Hide file tree
Showing 24 changed files with 833 additions and 155 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [PR #42](https://github.com/itsallcode/simple-jdbc/pull/42): Allow direct access to `Connection`
- [PR #43](https://github.com/itsallcode/simple-jdbc/pull/43): Allow direct access to `Connection` from `DbOperations`
- [PR #44](https://github.com/itsallcode/simple-jdbc/pull/44): Add `GenericDialect` for unsupported databases
- [PR #45](https://github.com/itsallcode/simple-jdbc/pull/45): Rename `executeStatement()` to `executeUpdate()` and return row count (**Breaking change**)

## [0.9.0] - 2024-12-23

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

import org.itsallcode.jdbc.batch.*;
import org.itsallcode.jdbc.dialect.GenericDialect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -35,15 +36,16 @@ void rowStatementSetter() {
}

private RowBatchInsertBuilder<NameRow> rowTestee() {
final PreparedStatement stmt = createNoopPreparedStatement();
return new RowBatchInsertBuilder<NameRow>(sql -> new SimplePreparedStatement(null, null, stmt, "sql"))
.maxBatchSize(MAX_BATCH_SIZE);
return new RowBatchInsertBuilder<NameRow>(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE);
}

private BatchInsertBuilder testee() {
return new BatchInsertBuilder(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE);
}

private SimplePreparedStatement prepareStatement(final String sql) {
final PreparedStatement stmt = createNoopPreparedStatement();
return new BatchInsertBuilder(sql -> new SimplePreparedStatement(null, null, stmt, "sql"))
.maxBatchSize(MAX_BATCH_SIZE);
return new SimplePreparedStatement(Context.builder().build(), GenericDialect.INSTANCE, stmt, sql);
}

private PreparedStatement createNoopPreparedStatement() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ record TypeTest(String value, String type, Object expectedValue, JDBCType expect
void batchInsert() {
final LocalDate date = LocalDate.parse("2024-10-20");
try (final SimpleConnection connection = connect()) {
connection.executeStatement("create schema test");
connection.executeStatement("create table tab(col date)");
connection.executeUpdate("create schema test");
connection.executeUpdate("create table tab(col date)");
connection.batchInsert(LocalDate.class).into("TAB", List.of("COL"))
.mapping((row, stmt) -> stmt.setObject(1, row)).rows(Stream.of(date)).start();
try (SimpleResultSet<LocalDate> resultSet = connection.query("select * from tab",
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
* <li>Execute statements
* <ul>
* <li>... single statement:
* {@link org.itsallcode.jdbc.DbOperations#executeStatement(String)}</li>
* {@link org.itsallcode.jdbc.DbOperations#executeUpdate(String)}</li>
* <li>... with a prepared statement and generic parameters:
* {@link org.itsallcode.jdbc.DbOperations#executeStatement(String, List)}</li>
* {@link org.itsallcode.jdbc.DbOperations#executeUpdate(String, List)}</li>
* <li>... with a prepared statement and custom parameter setter:
* {@link org.itsallcode.jdbc.DbOperations#executeStatement(String, org.itsallcode.jdbc.PreparedStatementSetter)}</li>
* {@link org.itsallcode.jdbc.DbOperations#executeUpdate(String, org.itsallcode.jdbc.PreparedStatementSetter)}</li>
* <li>... multiple statements in a batch:
* {@link org.itsallcode.jdbc.DbOperations#batch()}</li>
*
* <li>... semicolon separated SQL script:
* {@link org.itsallcode.jdbc.DbOperations#executeScript(String)}</li>
Expand Down
55 changes: 38 additions & 17 deletions src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import static java.util.function.Predicate.not;

import java.sql.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.*;
import java.util.logging.Logger;

import org.itsallcode.jdbc.batch.BatchInsertBuilder;
import org.itsallcode.jdbc.batch.RowBatchInsertBuilder;
import org.itsallcode.jdbc.batch.*;
import org.itsallcode.jdbc.dialect.DbDialect;
import org.itsallcode.jdbc.resultset.*;
import org.itsallcode.jdbc.resultset.generic.Row;
Expand All @@ -34,44 +32,59 @@ class ConnectionWrapper implements AutoCloseable {
this.paramSetterProvider = new ParamSetterProvider(dialect);
}

void executeStatement(final String sql) {
this.executeStatement(sql, ps -> {
});
int executeUpdate(final String sql) {
try (SimpleStatement statement = createSimpleStatement()) {
return statement.executeUpdate(sql);
}
}

void executeStatement(final String sql, final PreparedStatementSetter preparedStatementSetter) {
int executeUpdate(final String sql, final PreparedStatementSetter preparedStatementSetter) {
try (SimplePreparedStatement statement = prepareStatement(sql)) {
statement.setValues(preparedStatementSetter);
statement.execute();
return statement.executeUpdate();
}
}

void executeScript(final String sqlScript) {
Arrays.stream(sqlScript.split(";"))
final List<String> statements = Arrays.stream(sqlScript.split(";"))
.map(String::trim)
.filter(not(String::isEmpty))
.forEach(this::executeStatement);
.toList();
if (statements.isEmpty()) {
return;
}
try (StatementBatch batch = this.batch().build()) {
statements.forEach(batch::addBatch);
}
}

SimpleResultSet<Row> query(final String sql) {
return this.query(sql, ps -> {
}, ContextRowMapper.generic(dialect));
LOG.finest(() -> "Executing query '" + sql + "'...");
final SimpleStatement statement = createSimpleStatement();
// TODO: close statement when resultset is closed
return statement.executeQuery(sql, ContextRowMapper.create(ContextRowMapper.generic(dialect)));
}

<T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter,
final RowMapper<T> rowMapper) {
LOG.finest(() -> "Executing query '" + sql + "'...");
final SimplePreparedStatement statement = prepareStatement(sql);
statement.setValues(preparedStatementSetter);
// TODO: close statement when resultset is closed
return statement.executeQuery(ContextRowMapper.create(rowMapper));
}

SimplePreparedStatement prepareStatement(final String sql) {
return new SimplePreparedStatement(context, dialect, wrap(prepare(sql)), sql);
private SimplePreparedStatement prepareStatement(final String sql) {
return new SimplePreparedStatement(context, dialect,
new ConvertingPreparedStatement(prepare(sql), paramSetterProvider), sql);
}

StatementBatchBuilder batch() {
return new StatementBatchBuilder(this::createSimpleStatement);
}

private PreparedStatement wrap(final PreparedStatement preparedStatement) {
return new ConvertingPreparedStatement(preparedStatement, paramSetterProvider);
private SimpleStatement createSimpleStatement() {
return new SimpleStatement(context, dialect, createStatement());
}

BatchInsertBuilder batchInsert() {
Expand All @@ -90,6 +103,14 @@ private PreparedStatement prepare(final String sql) {
}
}

private Statement createStatement() {
try {
return connection.createStatement();
} catch (final SQLException e) {
throw new UncheckedSQLException("Error creating statement", e);
}
}

void setAutoCommit(final boolean autoCommit) {
try {
connection.setAutoCommit(autoCommit);
Expand Down
32 changes: 21 additions & 11 deletions src/main/java/org/itsallcode/jdbc/DbOperations.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import java.util.List;
import java.util.stream.Stream;

import org.itsallcode.jdbc.batch.BatchInsertBuilder;
import org.itsallcode.jdbc.batch.RowBatchInsertBuilder;
import org.itsallcode.jdbc.batch.*;
import org.itsallcode.jdbc.resultset.RowMapper;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;
Expand All @@ -29,33 +28,36 @@ public interface DbOperations extends AutoCloseable {
* Execute a single SQL statement.
*
* @param sql SQL statement
* @return either the row count for SQL Data Manipulation Language (DML)
* statements or 0 for SQL statements that return nothing
*/
default void executeStatement(final String sql) {
this.executeStatement(sql, stmt -> {
});
}
int executeUpdate(final String sql);

/**
* Execute a single SQL statement as a prepared statement with placeholders.
* <p>
* This will use {@link PreparedStatement#setObject(int, Object)} for setting
* parameters. If you need more control, use
* {@link #executeStatement(String, PreparedStatementSetter)}.
* {@link #executeUpdate(String, PreparedStatementSetter)}.
*
* @param sql SQL statement
* @param parameters parameters to set in the prepared statement
* @return either the row count for SQL Data Manipulation Language (DML)
* statements or 0 for SQL statements that return nothing
*/
default void executeStatement(final String sql, final List<Object> parameters) {
this.executeStatement(sql, new GenericParameterSetter(parameters));
default int executeUpdate(final String sql, final List<Object> parameters) {
return this.executeUpdate(sql, new GenericParameterSetter(parameters));
}

/**
* Execute a single SQL statement as a prepared statement with placeholders.
*
* @param sql SQL statement
* @param preparedStatementSetter prepared statement setter
* @return either the row count for SQL Data Manipulation Language (DML)
* statements or 0 for SQL statements that return nothing
*/
void executeStatement(final String sql, PreparedStatementSetter preparedStatementSetter);
int executeUpdate(final String sql, PreparedStatementSetter preparedStatementSetter);

/**
* Execute a SQL query and return a {@link SimpleResultSet result set} with
Expand Down Expand Up @@ -86,7 +88,7 @@ default <T> SimpleResultSet<T> query(final String sql, final RowMapper<T> rowMap
* <p>
* This will use {@link PreparedStatement#setObject(int, Object)} for setting
* parameters. If you need more control, use
* {@link #executeStatement(String, PreparedStatementSetter)}.
* {@link #executeUpdate(String, PreparedStatementSetter)}.
*
* @param <T> generic row type
* @param sql SQL query
Expand All @@ -112,6 +114,14 @@ default <T> SimpleResultSet<T> query(final String sql, final List<Object> parame
<T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter,
final RowMapper<T> rowMapper);

/**
* Create a batch statement builder for executing multiple statements in a
* batch.
*
* @return batch statement builder
*/
StatementBatchBuilder batch();

/**
* Create a batch insert builder for inserting rows by directly setting values
* of a {@link PreparedStatement}.
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/org/itsallcode/jdbc/SimpleConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

import java.sql.Connection;

import org.itsallcode.jdbc.batch.BatchInsertBuilder;
import org.itsallcode.jdbc.batch.RowBatchInsertBuilder;
import org.itsallcode.jdbc.batch.*;
import org.itsallcode.jdbc.dialect.DbDialect;
import org.itsallcode.jdbc.resultset.RowMapper;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
Expand Down Expand Up @@ -69,16 +68,22 @@ private void checkOperationAllowed() {
}
}

@Override
public int executeUpdate(final String sql) {
checkOperationAllowed();
return connection.executeUpdate(sql);
}

@Override
public void executeScript(final String sqlScript) {
checkOperationAllowed();
connection.executeScript(sqlScript);
}

@Override
public void executeStatement(final String sql, final PreparedStatementSetter preparedStatementSetter) {
public int executeUpdate(final String sql, final PreparedStatementSetter preparedStatementSetter) {
checkOperationAllowed();
connection.executeStatement(sql, preparedStatementSetter);
return connection.executeUpdate(sql, preparedStatementSetter);
}

@Override
Expand All @@ -94,6 +99,12 @@ public <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSet
return connection.query(sql, preparedStatementSetter, rowMapper);
}

@Override
public StatementBatchBuilder batch() {
checkOperationAllowed();
return connection.batch();
}

@Override
public BatchInsertBuilder batchInsert() {
checkOperationAllowed();
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.itsallcode.jdbc;

import java.sql.*;
import java.util.Objects;

import org.itsallcode.jdbc.dialect.DbDialect;
import org.itsallcode.jdbc.resultset.*;
Expand All @@ -16,10 +17,10 @@ public class SimplePreparedStatement implements AutoCloseable {

SimplePreparedStatement(final Context context, final DbDialect dialect, final PreparedStatement statement,
final String sql) {
this.context = context;
this.dialect = dialect;
this.statement = statement;
this.sql = sql;
this.context = Objects.requireNonNull(context, "context");
this.dialect = Objects.requireNonNull(dialect, "dialect");
this.statement = Objects.requireNonNull(statement, "statement");
this.sql = Objects.requireNonNull(sql, "sql");
}

<T> SimpleResultSet<T> executeQuery(final ContextRowMapper<T> rowMapper) {
Expand All @@ -36,9 +37,12 @@ private ResultSet doExecuteQuery() {
}
}

boolean execute() {
/**
* @see PreparedStatement#executeUpdate()
*/
int executeUpdate() {
try {
return statement.execute();
return statement.executeUpdate();
} catch (final SQLException e) {
throw new UncheckedSQLException("Error executing statement '" + sql + "'", e);
}
Expand Down
Loading

0 comments on commit 3518777

Please sign in to comment.