Skip to content

Commit

Permalink
Implement transactions (#30)
Browse files Browse the repository at this point in the history
* Implement transactions

* Add executeStatement with param setter

---------

Co-authored-by: kaklakariada <[email protected]>
  • Loading branch information
kaklakariada and kaklakariada authored Nov 3, 2024
1 parent d08dca6 commit 30290ee
Show file tree
Hide file tree
Showing 11 changed files with 432 additions and 92 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.9.0] - unreleased

## [0.8.0] - 2024-10-20
## [0.8.0] - 2024-11-03

- [PR #27](https://github.com/itsallcode/simple-jdbc/pull/27): Update dependencies
- [PR #28](https://github.com/itsallcode/simple-jdbc/pull/28): Refactored batch inserts (**Breaking change**)
- [PR #29](https://github.com/itsallcode/simple-jdbc/pull/29): Setting values for a `PreparedStatement` (**Breaking change**)
- [PR #30](https://github.com/itsallcode/simple-jdbc/pull/30): Add transaction support

## [0.7.1] - 2024-09-01

Expand Down
86 changes: 86 additions & 0 deletions src/main/java/org/itsallcode/jdbc/DbOperations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.itsallcode.jdbc;

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

import java.util.Arrays;

import org.itsallcode.jdbc.resultset.RowMapper;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;

/**
* Interface for various DB operations.
*/
public interface DbOperations extends AutoCloseable {
/**
* Execute all commands in a SQL script, separated with {@code ;}.
*
* @param sqlScript the script to execute.
*/
default void executeScript(final String sqlScript) {
Arrays.stream(sqlScript.split(";"))
.map(String::trim)
.filter(not(String::isEmpty))
.forEach(this::executeStatement);
}

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

/**
* Execute a single SQL statement.
*
* @param sql the 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
*/
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}.
*
* @param <T> generic row type
* @param sql SQL query
* @param rowMapper row mapper
* @return the result set
*/
<T> SimpleResultSet<T> query(final String sql, final RowMapper<T> rowMapper);

/**
* Execute a SQL query, set parameters and return a {@link SimpleResultSet
* result set} with rows converted to a custom type {@link T}.
*
* @param <T> generic row type
* @param sql SQL query
* @param preparedStatementSetter the prepared statement setter
* @param rowMapper row mapper
* @return the result set
*/
<T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter,
final RowMapper<T> rowMapper);

/**
* Create a batch insert builder
*
* @param rowType row type
* @param <T> row type
* @return batch insert builder
*/
<T> BatchInsertBuilder<T> batchInsert(final Class<T> rowType);

@Override
void close();
}
101 changes: 41 additions & 60 deletions src/main/java/org/itsallcode/jdbc/SimpleConnection.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.itsallcode.jdbc;

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

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

Expand All @@ -17,7 +14,7 @@
* A simplified version of a JDBC {@link Connection}. Create new connections
* with {@link ConnectionFactory#create(String, String, String)}.
*/
public class SimpleConnection implements AutoCloseable {
public class SimpleConnection implements DbOperations {
private static final Logger LOG = Logger.getLogger(SimpleConnection.class.getName());

private final Connection connection;
Expand All @@ -33,65 +30,40 @@ public class SimpleConnection implements AutoCloseable {
}

/**
* Execute all commands in a SQL script, separated with {@code ;}.
* Start a new transaction by disabling auto commit.
*
* @param sqlScript the script to execute.
* @return a new, running transaction.
*/
public void executeScript(final String sqlScript) {
Arrays.stream(sqlScript.split(";"))
.map(String::trim)
.filter(not(String::isEmpty))
.forEach(this::executeStatement);
public Transaction startTransaction() {
return Transaction.start(this);
}

/**
* Execute a single SQL statement.
*
* @param sql the statement
*/
@Override
public void executeStatement(final String sql) {
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
} catch (final SQLException e) {
throw new UncheckedSQLException("Error executing '" + sql + "'", e);
this.executeStatement(sql, stmt -> {
});
}

@Override
public void executeStatement(final String sql, final PreparedStatementSetter preparedStatementSetter) {
try (SimplePreparedStatement statement = prepareStatement(sql)) {
statement.setValues(preparedStatementSetter);
statement.execute();
}
}

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

/**
* Execute a SQL query and return a {@link SimpleResultSet result set} with rows
* converted to a custom type {@link T}.
*
* @param <T> generic row type
* @param sql SQL query
* @param rowMapper row mapper
* @return the result set
*/
@Override
public <T> SimpleResultSet<T> query(final String sql, final RowMapper<T> rowMapper) {
return query(sql, ps -> {
}, rowMapper);
}

/**
* Execute a SQL query, set parameters and return a {@link SimpleResultSet
* result set} with rows converted to a custom type {@link T}.
*
* @param <T> generic row type
* @param sql SQL query
* @param preparedStatementSetter the prepared statement setter
* @param rowMapper row mapper
* @return the result set
*/
@Override
public <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter,
final RowMapper<T> rowMapper) {
LOG.finest(() -> "Executing query '" + sql + "'...");
Expand All @@ -108,13 +80,7 @@ private PreparedStatement wrap(final PreparedStatement preparedStatement) {
return new ConvertingPreparedStatement(preparedStatement, paramSetterProvider);
}

/**
* Create a batch insert builder
*
* @param rowType row type
* @param <T> row type
* @return batch insert builder
*/
@Override
public <T> BatchInsertBuilder<T> batchInsert(final Class<T> rowType) {
return new BatchInsertBuilder<>(this::prepareStatement);
}
Expand All @@ -127,13 +93,28 @@ private PreparedStatement prepare(final String sql) {
}
}

/**
* Database dialect of this connection.
*
* @return dialect
*/
public DbDialect getDialect() {
return dialect;
void setAutoCommit(final boolean autoCommit) {
try {
this.connection.setAutoCommit(autoCommit);
} catch (final SQLException e) {
throw new UncheckedSQLException("Failed to set autoCommit to " + autoCommit, e);
}
}

void rollback() {
try {
this.connection.rollback();
} catch (final SQLException e) {
throw new UncheckedSQLException("Failed to rollback transaction", e);
}
}

void commit() {
try {
this.connection.commit();
} catch (final SQLException e) {
throw new UncheckedSQLException("Failed to commit transaction", e);
}
}

@Override
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,27 @@ class SimplePreparedStatement implements AutoCloseable {
}

<T> SimpleResultSet<T> executeQuery(final ContextRowMapper<T> rowMapper) {
final ResultSet resultSet = doExecute();
final ResultSet resultSet = doExecuteQuery();
final ResultSet convertingResultSet = ConvertingResultSet.create(dialect, resultSet);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper);
}

private ResultSet doExecute() {
private ResultSet doExecuteQuery() {
try {
return statement.executeQuery();
} catch (final SQLException e) {
throw new UncheckedSQLException("Error executing query '" + sql + "'", e);
}
}

boolean execute() {
try {
return statement.execute();
} catch (final SQLException e) {
throw new UncheckedSQLException("Error executing statement '" + sql + "'", e);
}
}

@Override
public void close() {
try {
Expand Down
84 changes: 84 additions & 0 deletions src/main/java/org/itsallcode/jdbc/Transaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.itsallcode.jdbc;

import org.itsallcode.jdbc.resultset.RowMapper;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;

/**
* A running database transaction. The transaction will be rolled back
* automatically in {@link #close()}.
*/
public final class Transaction implements DbOperations {

private final SimpleConnection connection;

private Transaction(final SimpleConnection connection) {
this.connection = connection;
}

static Transaction start(final SimpleConnection connection) {
connection.setAutoCommit(false);
return new Transaction(connection);
}

/**
* Commit the transaction.
*/
public void commit() {
connection.commit();
}

/**
* Rollback the transaction.
*/
public void rollback() {
connection.rollback();
}

@Override
public void executeStatement(final String sql) {
connection.executeStatement(sql);
}

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

@Override
public SimpleResultSet<Row> query(final String sql) {
return connection.query(sql);
}

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

@Override
public <T> SimpleResultSet<T> query(final String sql, final RowMapper<T> rowMapper) {
return connection.query(sql, rowMapper);
}

@Override
public <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter,
final RowMapper<T> rowMapper) {
return connection.query(sql, preparedStatementSetter, rowMapper);
}

@Override
public <T> BatchInsertBuilder<T> batchInsert(final Class<T> rowType) {
return connection.batchInsert(rowType);
}

/**
* Rollback transaction and enable auto commit.
* <p>
* Explicitly run {@link #commit()} before to commit your transaction.
*/
@Override
public void close() {
this.rollback();
connection.setAutoCommit(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public Iterator<T> iterator() {
* @return a list with all rows.
*/
public List<T> toList() {
return stream().toList();
try (Stream<T> stream = stream()) {
return stream.toList();
}
}

/**
Expand Down Expand Up @@ -106,7 +108,7 @@ private ResultSetIterator(final Context context, final SimpleResultSet<T> simple
this.hasNext = hasNext;
}

public static <T> Iterator<T> create(final Context context, final SimpleResultSet<T> simpleResultSet,
private static <T> Iterator<T> create(final Context context, final SimpleResultSet<T> simpleResultSet,
final ContextRowMapper<T> rowMapper) {
final boolean firstRowExists = simpleResultSet.next();
return new ResultSetIterator<>(context, simpleResultSet, rowMapper, firstRowExists);
Expand Down
Loading

0 comments on commit 30290ee

Please sign in to comment.