Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [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**)
- [PR #46](https://github.com/itsallcode/simple-jdbc/pull/46): Close `Statement` / `PreparedStatement` when closing the result set.

## [0.9.0] - 2024-12-23

Expand Down
2 changes: 0 additions & 2 deletions src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ void executeScript(final String sqlScript) {
SimpleResultSet<Row> query(final String sql) {
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)));
}

Expand All @@ -70,7 +69,6 @@ <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter pre
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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class SimplePreparedStatement implements AutoCloseable {
<T> SimpleResultSet<T> executeQuery(final ContextRowMapper<T> rowMapper) {
final ResultSet resultSet = doExecuteQuery();
final ResultSet convertingResultSet = ConvertingResultSet.create(dialect, resultSet);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper, this);
}

private ResultSet doExecuteQuery() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/itsallcode/jdbc/SimpleStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class SimpleStatement implements AutoCloseable {
<T> SimpleResultSet<T> executeQuery(final String sql, final ContextRowMapper<T> rowMapper) {
final ResultSet resultSet = doExecuteQuery(sql);
final ResultSet convertingResultSet = ConvertingResultSet.create(dialect, resultSet);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper, this);
}

private ResultSet doExecuteQuery(final String sql) {
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/org/itsallcode/jdbc/resultset/SimpleResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class SimpleResultSet<T> implements AutoCloseable, Iterable<T> {
private final ResultSet resultSet;
private final ContextRowMapper<T> rowMapper;
private final Context context;
private final AutoCloseable statement;
private Iterator<T> iterator;

/**
Expand All @@ -27,11 +28,15 @@ public class SimpleResultSet<T> implements AutoCloseable, Iterable<T> {
* @param context database context
* @param resultSet the underlying result set
* @param rowMapper a row mapper for converting each row
* @param statement the statement that created the result set. This will be
* closed when the result set is closed.
*/
public SimpleResultSet(final Context context, final ResultSet resultSet, final ContextRowMapper<T> rowMapper) {
public SimpleResultSet(final Context context, final ResultSet resultSet, final ContextRowMapper<T> rowMapper,
final AutoCloseable statement) {
this.context = context;
this.resultSet = resultSet;
this.rowMapper = rowMapper;
this.statement = statement;
}

/**
Expand Down Expand Up @@ -71,7 +76,7 @@ public Stream<T> stream() {
}

/**
* Close the underlying {@link ResultSet}.
* Close the underlying {@link ResultSet} and the statement that created it.
*
* @throws UncheckedSQLException if closing fails.
*/
Expand All @@ -82,6 +87,11 @@ public void close() {
} catch (final SQLException e) {
throw new UncheckedSQLException("Error closing resultset", e);
}
try {
statement.close();
} catch (final Exception e) {
throw new IllegalStateException("Error closing statement: " + e.getMessage(), e);
}
}

private boolean next() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ void executeQuery() throws SQLException {
verify(statementMock).executeQuery();
}

@Test
void closingResultSetClosesStatement() throws SQLException {
when(statementMock.executeQuery()).thenReturn(resultSetMock);
when(resultSetMock.getMetaData()).thenReturn(metaDataMock);
testee().executeQuery(rowMapperMock).close();

verify(statementMock).close();
}

@Test
void executeQueryFails() throws SQLException {
when(statementMock.executeQuery()).thenThrow(new SQLException("expected"));
Expand Down
8 changes: 8 additions & 0 deletions src/test/java/org/itsallcode/jdbc/SimpleStatementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ void executeQuery() throws SQLException {
verify(stmtMock).executeQuery("sql");
}

@Test
void closingResultSetClosesStatement() throws SQLException {
when(stmtMock.executeQuery("sql")).thenReturn(resultSetMock);
when(resultSetMock.getMetaData()).thenReturn(metaDataMock);
testee().executeQuery("sql", rowMapperMock).close();
verify(stmtMock).close();
}

@Test
void executeQueryFails() throws SQLException {
when(stmtMock.executeQuery("sql")).thenThrow(new SQLException("expected"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.*;

import org.itsallcode.jdbc.UncheckedSQLException;
import org.junit.jupiter.api.Test;
Expand All @@ -18,12 +17,14 @@
class SimpleResultSetTest {

@Mock
private ResultSet resultSetMock;
ResultSet resultSetMock;
@Mock
private ContextRowMapper<TestingRowType> rowMapper;
ContextRowMapper<TestingRowType> rowMapper;
@Mock
Statement statementMock;

SimpleResultSet<TestingRowType> testee() {
return new SimpleResultSet<>(null, resultSetMock, rowMapper);
return new SimpleResultSet<>(null, resultSetMock, rowMapper, statementMock);
}

@Test
Expand Down Expand Up @@ -69,18 +70,45 @@ void closeSucceeds() throws SQLException {
verify(resultSetMock).close();
}

@Test
void closeClosesStatement() throws SQLException {
testee().close();
verify(statementMock).close();
}

@Test
void closingStatementFails() throws SQLException {
doThrow(new SQLException("expected")).when(statementMock).close();
final SimpleResultSet<TestingRowType> testee = testee();
assertThatThrownBy(testee::close)
.isInstanceOf(IllegalStateException.class)
.hasMessage("Error closing statement: expected");
}

@Test
void streamClosesResultSet() throws SQLException {
testee().stream().close();
verify(resultSetMock).close();
}

@Test
void streamClosesStatement() throws SQLException {
testee().stream().close();
verify(statementMock).close();
}

@Test
void toListClosesResultSet() throws SQLException {
testee().toList();
verify(resultSetMock).close();
}

@Test
void toListClosesStatement() throws SQLException {
testee().toList();
verify(statementMock).close();
}

private void simulateRowMapper() throws SQLException {
when(rowMapper.mapRow(eq(null), same(resultSetMock), anyInt()))
.thenAnswer(invocation -> new TestingRowType(invocation.getArgument(2, Integer.class)));
Expand Down
Loading