001: /*
002: * Created on Feb 22, 2004
003: */
004: package net.sourceforge.orbroker;
005:
006: import java.sql.Connection;
007: import java.sql.SQLException;
008: import java.util.ArrayList;
009: import java.util.logging.Level;
010:
011: /**
012: * Any database modifications (INSERT, UPDATE, DELETE) must be done through
013: * the Transaction object. It wraps a {@link java.sql.Connection Connection}
014: * object, and like a {@link Query} instance, {@link #close()} must be called after
015: * use (in a <code>finally</code> block), to avoid connection pool leaks.
016: * A transaction using an isolation level of
017: * {@link java.sql.Connection#TRANSACTION_NONE}
018: * is considered non-transactional and will have auto-commit enabled. This makes
019: * it illegal to call either {@link #commit()} or {@link #rollback()}. All other isolation
020: * levels require a call to {@link #commit()} method for the transaction to commit.
021: *
022: * @author Nils Kilden-Pedersen
023: * @see net.sourceforge.orbroker.Broker#startTransaction()
024: * @see net.sourceforge.orbroker.Broker#startTransaction(int)
025: */
026: public final class Transaction extends ExecutableConnection {
027:
028: private static void log(String message) {
029: Broker.log(Level.INFO, message);
030: }
031:
032: private ArrayList executables;
033: private boolean transactionStarted = false;
034:
035: Transaction(Broker broker, Integer isolationLevel)
036: throws BrokerException {
037: super (broker, isolationLevel);
038: }
039:
040: /**
041: *
042: */
043: private void releaseExecutables() {
044: if (this .executables != null) {
045: int size = this .executables.size();
046: for (int i = 0; i < size; i++) {
047: Executable exe = (Executable) this .executables.get(i);
048: exe.release();
049: }
050: }
051: }
052:
053: /**
054: * @see net.sourceforge.orbroker.QueryableConnection#getClosedConnectionMessage()
055: */
056: protected String getClosedConnectionMessage() {
057: return "Transaction has been closed.";
058: }
059:
060: /**
061: * @see net.sourceforge.orbroker.QueryableConnection#getFinalizeWarning()
062: */
063: protected String getFinalizeWarning() {
064: return "A Transaction was not closed. Temporary connection pool leak now closed.";
065: }
066:
067: /**
068: * @see net.sourceforge.orbroker.QueryableConnection#setConnectionProperties()
069: */
070: protected void setConnectionProperties() throws SQLException {
071: Connection con = getActiveConnection();
072: if (con.isReadOnly()) {
073: con.setReadOnly(false);
074: }
075: }
076:
077: /**
078: * @see net.sourceforge.orbroker.BrokerConnection#shouldConnectionClose()
079: */
080: protected boolean shouldConnectionClose() {
081: return true;
082: }
083:
084: /**
085: * Close transaction. If Transaction has not been explicity committed
086: * or rolled back, it will automatically be rolled back on close. This can
087: * simplify code by automatically rolling back on close:
088: * <pre>Transaction txn = broker.startTransaction();
089: * try {
090: * txn.setParameter("account", account);
091: * txn.execute("updateAccount");
092: * txn.commit();
093: * ] finally {
094: * txn.close();
095: * }</pre>
096: * There is no need to issue a rollback. If commit() is not called due to an exception,
097: * rollback is done automatically.
098: * This is assuming that the transaction is in isolation level other than
099: * {@link Connection#TRANSACTION_NONE}.
100: */
101: public void close() {
102: if (this .transactionStarted) {
103: rollback();
104: }
105: releaseExecutables();
106: dropConnection();
107: }
108:
109: /**
110: * Commit the transaction.
111: * <p>This method cannot be called if isolation level
112: * is {@link Connection#TRANSACTION_NONE}.</p>
113: * @throws BrokerException
114: * @throws ConstraintException
115: * @throws DeadlockException
116: * @see #rollback()
117: */
118: public void commit() throws BrokerException, ConstraintException,
119: DeadlockException {
120: if (isAutoCommit()) {
121: throw new BrokerException(
122: "Commit not supported with transaction isolation level of "
123: + getTransactionIsolationText());
124: }
125:
126: /* Since transaction is automatically rolled back
127: * on a failed commit, it is safe to set
128: * this.transactionStarted to false before trying.
129: */
130: this .transactionStarted = false;
131: try {
132: getActiveConnection().commit();
133: } catch (SQLException e) {
134: throwException(e);
135: }
136: log("Transaction committed.");
137: }
138:
139: /**
140: * Obtain an Executable that uses this Transaction's connection.
141: * This class provides safe semantics for other methods that
142: * need to take part in the transaction, without exposing commit
143: * or rollback capabilities.
144: * The Executable will be automatically released and invalidated when
145: * Transaction is closed.
146: *
147: * @return Executable
148: * @see #close()
149: * @see Broker#obtainExecutable(Connection)
150: */
151: public Executable obtainExecutable() {
152: if (this .executables == null) {
153: this .executables = new ArrayList(5);
154: }
155: Executable executable = newExecutable(this );
156: this .executables.add(executable);
157: return executable;
158: }
159:
160: /**
161: * Roll back the transaction. If rollback() is not called on an
162: * exception, it will be done automatically on {@link #close()}
163: * (provided that {@link #close()} method is called in a
164: * <code>finally</code> block as it always should).
165: * <p>This method cannot be called if isolation level
166: * is {@link Connection#TRANSACTION_NONE}.</p>
167: * @throws BrokerException
168: * @see #commit()
169: */
170: public void rollback() throws BrokerException {
171: if (isAutoCommit()) {
172: throw new BrokerException(
173: "Rollback not supported with transaction isolation level of "
174: + getTransactionIsolationText());
175: }
176:
177: /* If a rollback fails, I'm not really sure what
178: * state the transaction is in. If it's committed then
179: * it is safe to set this.transactionStarted to false,
180: * if not, the intent is to rollback and that cannot
181: * happen, so the transaction must be considered
182: * finished.
183: */
184: this .transactionStarted = false;
185: try {
186: getActiveConnection().rollback();
187: } catch (SQLException e) {
188: throwException(e);
189: }
190: log("Transaction rolled back.");
191: }
192:
193: /**
194: * @inheritDoc
195: * @see net.sourceforge.orbroker.ExecutableConnection#markTransactionStarted()
196: */
197: protected void markTransactionStarted() {
198: if (!this .transactionStarted && !isAutoCommit()) {
199: this .transactionStarted = true;
200: log("Transaction started...");
201: }
202: }
203:
204: }
|