All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.dbms.DbTransaction Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


package org.tentackle.dbms;

import org.tentackle.session.PersistenceException;
import org.tentackle.session.SavepointHandle;

import java.sql.Savepoint;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Holder for transaction local data.
 *
 * @author harald
 */
public class DbTransaction {

  /**
   * Maximum recursion depth of tx runnables.
* To detect recursion loops when tx runnables create other runnables. */ public static int maxTxRunnableDepth = 100; // the jvm-wide transaction counter private static final AtomicLong TX_COUNT = new AtomicLong(); private static final String DEFAULT_TXNAME = ""; private final Db db; // the session private final DbBatch batch; // statement batch, null if no batching (default) private final long creationTime; // epochal time in ms when started/created private final long txNumber; // the unique transaction number private final String txName; // optional transaction name private final long txVoucher; // the random voucher for commit or rollback private int handleCount; // the last handle number private int txLevel; // number of nested begin() (only for local Db), Integer.MIN_VALUE if invalid private Deque nestedTxNames; // transaction names for nested transactions private Map visitors; // the visitors private Map commitTxRunnables; // the commit runnables private Map rollbackTxRunnables; // the rollback runnables private Map savepoints; // the savepoints /** * Creates a transaction. * * @param db the session * @param txName the transaction name, null if <unnamed> * @param fromRemote true if initiated from remote client */ public DbTransaction(Db db, String txName, boolean fromRemote) { this.db = db; this.txName = txName == null ? DEFAULT_TXNAME : txName; batch = createBatch(); // null if no batching txNumber = TX_COUNT.incrementAndGet(); txLevel = 1; // nesting level 1 = first begin creationTime = System.currentTimeMillis(); // create new commit/rollback voucher long magic; do { magic = UUID.randomUUID().getLeastSignificantBits(); } while (magic == 0); // remote are negative, local positive txVoucher = (fromRemote && magic > 0 || !fromRemote && magic < 0) ? -magic : magic; } /** * Gets the session. * * @return the session */ public Db getSession() { return db; } /** * Gets the statements batch. * * @return the batch, null if no auto batching */ public DbBatch getBatch() { return batch; } /** * Gets the epochal creation time in ms. * * @return the milliseconds since 1970-01-01 */ public long getCreationTime() { return creationTime; } /** * Gets the transaction name. * * @return the name */ public String getTxName() { return txName; } /** * Gets the transaction number. * * @return the tx number */ public long getTxNumber() { return txNumber; } /** * Gets the nesting level. * * @return the tx level */ public int getTxLevel() { return txLevel; } /** * Marks the txLevel invalid. *

* Will suppress any checks and warnings. */ public void invalidateTxLevel() { txLevel = Integer.MIN_VALUE; nestedTxNames = null; } /** * Returns whether the txLevel is valid. * * @return true if valid */ public boolean isTxLevelValid() { return txLevel != Integer.MIN_VALUE; } /** * Increments the transaction level. * * @param txName the optional transaction name * @return the new tx level */ public int incrementTxLevel(String txName) { if (isTxLevelValid()) { if (txName == null) { txName = DEFAULT_TXNAME; } if (nestedTxNames == null) { nestedTxNames = new ArrayDeque<>(); } nestedTxNames.push(txName); txLevel++; } return txLevel; } /** * Decrements the transaction level. * * @return the new tx level */ public int decrementTxLevel() { if (isTxLevelValid()) { if (txLevel <= 1) { throw new PersistenceException(db, "unbalanced tx level"); } --txLevel; if (nestedTxNames != null) { nestedTxNames.pop(); } } return txLevel; } /** * Gets the transaction voucher. * * @return the voucher */ public long getTxVoucher() { return txVoucher; } /** * Asserts the validity of the given voucher.
* If the voucher does not match, a {@link PersistenceException} is thrown. * * @param txVoucher the voucher to test */ public void assertValidTxVoucher(long txVoucher) { if (this.txVoucher != txVoucher) { throw new PersistenceException(db, "invalid voucher " + txVoucher); } } /** * Adds a savepoint to the transaction. * * @param handle the handle * @param savepoint the savepoint */ public void addSavepoint(SavepointHandle handle, Savepoint savepoint) { if (savepoints == null) { savepoints = new HashMap<>(); } savepoints.put(handle, savepoint); } /** * Gets a savepoint by handle. * * @param handle the handle * @return savepoint the savepoint */ public Savepoint getSavepoint(SavepointHandle handle) { return savepoints == null ? null : savepoints.get(handle); } /** * Removes a savepoint. * * @param handle the handle * @return the removed savepoint, null if no such handle */ public Savepoint removeSavepoint(SavepointHandle handle) { return savepoints == null ? null : savepoints.remove(handle); } /** * Returns whether this transaction has active savepoints. * * @return true if savepoints are active */ public boolean isWithSavepoints() { return savepoints != null && !savepoints.isEmpty(); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("TX").append(txNumber); if (isTxLevelValid()) { buf.append('.').append(txLevel); } buf.append('-').append(txName); if (nestedTxNames != null) { for (Iterator iterator = nestedTxNames.descendingIterator(); iterator.hasNext(); ) { buf.append('>').append(iterator.next()); } } return buf.toString(); } /** * Registers a {@link PersistenceVisitor} to be invoked just before * performing a persistence operation.
* * @param visitor the visitor to register * @return the handle for the visitor */ public DbTransactionHandle registerPersistenceVisitor(PersistenceVisitor visitor) { if (visitors == null) { visitors = new HashMap<>(); } DbTransactionHandle handle = new DbTransactionHandle(txNumber, ++handleCount); visitors.put(handle, visitor); return handle; } /** * Unregisters a {@link PersistenceVisitor}. * * @param handle the visitor's handle to unregister * @return the removed visitor, null if not registered */ public PersistenceVisitor unregisterPersistenceVisitor(DbTransactionHandle handle) { return visitors == null ? null : visitors.remove(handle); } /** * Gets the currently registered persistence visitors. * * @return the visitors, null or empty if none */ public Collection getPersistenceVisitors() { return visitors.values(); } /** * Registers a {@link CommitTxRunnable} to be invoked just before * committing a transaction. * * @param commitRunnable the runnable to register * @return the handle for the runnable */ public DbTransactionHandle registerCommitTxRunnable(CommitTxRunnable commitRunnable) { if (commitTxRunnables == null) { commitTxRunnables = new HashMap<>(); } DbTransactionHandle handle = new DbTransactionHandle(txNumber, ++handleCount); commitTxRunnables.put(handle, commitRunnable); return handle; } /** * Unregisters a {@link CommitTxRunnable}. * * @param handle the runnable's handle to unregister * @return the runnable if removed, null if not registered */ public CommitTxRunnable unregisterCommitTxRunnable(DbTransactionHandle handle) { return commitTxRunnables == null ? null : commitTxRunnables.remove(handle); } /** * Gets the currently registered commit runnables. * * @return the runnables, null or empty if none */ public Collection getCommitTxRunnables() { return commitTxRunnables.values(); } /** * Registers a {@link RollbackTxRunnable} to be invoked just before * rolling back a transaction. * * @param rollbackRunnable the runnable to register * @return the handle for the runnable */ public DbTransactionHandle registerRollbackTxRunnable(RollbackTxRunnable rollbackRunnable) { if (rollbackTxRunnables == null) { rollbackTxRunnables = new HashMap<>(); } DbTransactionHandle handle = new DbTransactionHandle(txNumber, ++handleCount); rollbackTxRunnables.put(handle, rollbackRunnable); return handle; } /** * Unregisters a {@link RollbackTxRunnable}. * * @param handle the runnable's handle to unregister * @return the runnable that was removed, null if not registered */ public RollbackTxRunnable unregisterRollbackTxRunnable(DbTransactionHandle handle) { return rollbackTxRunnables == null ? null : rollbackTxRunnables.remove(handle); } /** * Gets the currently registered rollback runnables. * * @return the runnables, null or empty if none */ public Collection getRollbackTxRunnables() { return rollbackTxRunnables.values(); } /** * Performs the commit.
* Finishes any pending batched statements.
* Executes all commit runnables and removes them.
*/ public void commit() { if (batch != null) { batch.execute(true); } if (commitTxRunnables != null) { /* * execute pending commit runnables. * Notice: the runnables should throw PersistenceException on failure! * Runnables may create other runnables up to maxTxRunnableDepth recursion depth. */ int depth = 0; while (!commitTxRunnables.isEmpty()) { for (Map.Entry entry : Map.copyOf(commitTxRunnables).entrySet()) { entry.getValue().commit(db); commitTxRunnables.remove(entry.getKey()); } if (++depth > maxTxRunnableDepth) { throw new PersistenceException(getSession(), "commit runnables nesting level too deep"); } } commitTxRunnables = null; } } /** * Performs a rollback.
* Executes all rollback runnables and removes them. */ public void rollback() { if (rollbackTxRunnables != null) { /* * execute pending rollback runnables. * Notice: the runnables should throw PersistenceException on failure! * Runnables may create other runnables up to maxTxRunnableDepth recursion depth. */ int depth = 0; while (!rollbackTxRunnables.isEmpty()) { for (Map.Entry entry : Map.copyOf(rollbackTxRunnables).entrySet()) { entry.getValue().rollback(db); rollbackTxRunnables.remove(entry.getKey()); } if (++depth > maxTxRunnableDepth) { throw new PersistenceException(getSession(), "rollback runnables nesting level too deep"); } } rollbackTxRunnables = null; } } /** * Checks whether a persistence operation is allowed.
* This is determined by consulting the {@link PersistenceVisitor}s.
* * @param object the persistence object * @param modType the modification type * @return true if allowed * @see #registerPersistenceVisitor(org.tentackle.dbms.PersistenceVisitor) */ public boolean isPersistenceOperationAllowed(AbstractDbObject object, ModificationType modType) { if (visitors != null) { for (PersistenceVisitor visitor: visitors.values()) { object.acceptPersistenceVisitor(visitor, modType); if (!visitor.isPersistenceOperationAllowed(object, modType)) { return false; } } } return true; } /** * Creates the statement batch for this transaction. * * @return the batch, null if no batching */ protected DbBatch createBatch() { int batchSize = db.getBatchSize(); return batchSize > 1 ? new DbBatch(this, batchSize) : null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy