001package org.itsallcode.jdbc; 002 003import java.sql.Connection; 004import java.util.function.Consumer; 005 006import org.itsallcode.jdbc.batch.*; 007import org.itsallcode.jdbc.resultset.RowMapper; 008import org.itsallcode.jdbc.resultset.SimpleResultSet; 009import org.itsallcode.jdbc.resultset.generic.Row; 010 011/** 012 * A running database transaction. The transaction will be rolled back 013 * automatically in {@link #close()} if not explicitly committed using 014 * {@link #commit()} or rolled back using {@link #rollback()} before. 015 * <p> 016 * Start a new transaction using {@link SimpleConnection#startTransaction()}. 017 * <p> 018 * Operations are not allowed on a closed, committed or rolled back transaction. 019 * <p> 020 * Closing an already closed transaction is a no-op. 021 */ 022public final class Transaction implements DbOperations { 023 024 private final ConnectionWrapper connection; 025 private final Consumer<Transaction> transactionFinishedCallback; 026 private final boolean restoreAutoCommitRequired; 027 private boolean closed; 028 private boolean committed; 029 private boolean rolledBack; 030 031 private Transaction(final ConnectionWrapper connection, final Consumer<Transaction> transactionFinishedCallback, 032 final boolean restoreAutoCommitRequired) { 033 this.connection = connection; 034 this.transactionFinishedCallback = transactionFinishedCallback; 035 this.restoreAutoCommitRequired = restoreAutoCommitRequired; 036 } 037 038 static Transaction start(final ConnectionWrapper connection, 039 final Consumer<Transaction> transactionFinishedCallback) { 040 boolean restoreAutoCommitRequired = false; 041 if (connection.isAutoCommitEnabled()) { 042 connection.setAutoCommit(false); 043 restoreAutoCommitRequired = true; 044 } 045 return new Transaction(connection, transactionFinishedCallback, restoreAutoCommitRequired); 046 } 047 048 /** 049 * Commit the transaction. 050 * <p> 051 * No further operations are allowed on this transaction afterwards. 052 * 053 * @see Connection#commit() 054 */ 055 public void commit() { 056 checkOperationAllowed(); 057 this.connection.commit(); 058 this.committed = true; 059 this.transactionFinishedCallback.accept(this); 060 } 061 062 /** 063 * Rollback the transaction. 064 * <p> 065 * No further operations are allowed on this transaction afterwards. 066 * 067 * @see Connection#rollback() 068 */ 069 public void rollback() { 070 checkOperationAllowed(); 071 this.connection.rollback(); 072 this.rolledBack = true; 073 this.transactionFinishedCallback.accept(this); 074 } 075 076 @Override 077 public int executeUpdate(final String sql) { 078 checkOperationAllowed(); 079 return connection.executeUpdate(sql); 080 } 081 082 @Override 083 public int executeUpdate(final String sql, final PreparedStatementSetter preparedStatementSetter) { 084 checkOperationAllowed(); 085 return connection.executeUpdate(sql, preparedStatementSetter); 086 } 087 088 @Override 089 public SimpleResultSet<Row> query(final String sql) { 090 checkOperationAllowed(); 091 return connection.query(sql); 092 } 093 094 @Override 095 public void executeScript(final String sqlScript) { 096 checkOperationAllowed(); 097 connection.executeScript(sqlScript); 098 } 099 100 @Override 101 public <T> SimpleResultSet<T> query(final String sql, final PreparedStatementSetter preparedStatementSetter, 102 final RowMapper<T> rowMapper) { 103 checkOperationAllowed(); 104 return connection.query(sql, preparedStatementSetter, rowMapper); 105 } 106 107 @Override 108 public StatementBatchBuilder statementBatch() { 109 checkOperationAllowed(); 110 return connection.statementBatch(); 111 } 112 113 @Override 114 public PreparedStatementBatchBuilder preparedStatementBatch() { 115 checkOperationAllowed(); 116 return connection.preparedStatementBatch(); 117 } 118 119 @Override 120 public <T> RowPreparedStatementBatchBuilder<T> preparedStatementBatch(final Class<T> rowType) { 121 checkOperationAllowed(); 122 return connection.rowPreparedStatementBatch(); 123 } 124 125 public Connection getOriginalConnection() { 126 checkOperationAllowed(); 127 return connection.getOriginalConnection(); 128 } 129 130 private void checkOperationAllowed() { 131 if (this.closed) { 132 throw new IllegalStateException("Operation not allowed on closed transaction"); 133 } 134 if (this.rolledBack) { 135 throw new IllegalStateException("Operation not allowed on rolled back transaction"); 136 } 137 if (this.committed) { 138 throw new IllegalStateException("Operation not allowed on committed transaction"); 139 } 140 } 141 142 /** 143 * Rollback transaction if not already committed or rolled back and restore 144 * original auto commit setting if necessary. 145 * <p> 146 * Explicitly run {@link #commit()} before to commit your transaction. 147 * <p> 148 * No further operations are allowed on this transaction afterwards. 149 * <p> 150 * This <em>does not</em> close the connection, so you can continue using it. 151 */ 152 @Override 153 public void close() { 154 if (closed) { 155 return; 156 } 157 if (!rolledBack && !committed) { 158 this.rollback(); 159 } 160 if (restoreAutoCommitRequired) { 161 connection.setAutoCommit(true); 162 } 163 this.closed = true; 164 } 165}