Permalink
Browse files

Clean up transaction handling.

  • Loading branch information...
taylorotwell committed Jan 3, 2017
1 parent 82a2b17 commit b2a415971061e9d722e40aefceefa8b834fcb632
Showing with 106 additions and 36 deletions.
  1. +106 −36 src/Illuminate/Database/Concerns/ManagesTransactions.php
@@ -19,43 +19,64 @@
*/
public function transaction(Closure $callback, $attempts = 1)
{
for ($a = 1; $a <= $attempts; $a++) {
for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block
// and if we catch any exception we can rollback the transaction
// so that none of the changes are persisted to the database.
try {
$result = $callback($this);
$this->commit();
return tap($callback($this), function ($result) {
$this->commit();
});
}
// If we catch an exception, we will roll back so nothing gets messed
// up in the database. Then we'll re-throw the exception so it can
// be handled how the developer sees fit for their applications.
catch (Exception $e) {
if ($this->causedByDeadlock($e) && $this->transactions > 1) {
--$this->transactions;
throw $e;
}
$this->rollBack();
if ($this->causedByDeadlock($e) && $a < $attempts) {
continue;
}
throw $e;
$this->handleTransactionException(
$e, $currentAttempt, $attempts
);
} catch (Throwable $e) {
$this->rollBack();
throw $e;
}
}
}
/**
* Handle an exception encountered when running a transacted statement.
*
* @param \Exception $e
* @param int $currentAttempt
* @param int $maxAttempts
* @return void
*/
protected function handleTransactionException($e, $currentAttempt, $maxAttempts)
{
// On a deadlock, MySQL rolls back the entire transaction so we can't just
// retry the query. We have to throw this exception all the way out and
// let the developer handle it in another way. We will decrement too.
if ($this->causedByDeadlock($e) &&
$this->transactions > 1) {
--$this->transactions;
throw $e;
}
return $result;
// If there was an exception we will rollback this transaction and then we
// can check if we have exceeded the maximum attempt count for this and
// if we haven't we will return and try this query again in our loop.
$this->rollBack();
if ($this->causedByDeadlock($e) &&
$currentAttempt < $maxAttempts) {
return;
}
throw $e;
}
/**
@@ -65,27 +86,59 @@ public function transaction(Closure $callback, $attempts = 1)
* @throws Exception
*/
public function beginTransaction()
{
$this->createTransaction();
++$this->transactions;
$this->fireConnectionEvent('beganTransaction');
}
/**
* Create a transaction within the database.
*
* @return void
*/
protected function createTransaction()
{
if ($this->transactions == 0) {
try {
$this->getPdo()->beginTransaction();
} catch (Exception $e) {
if ($this->causedByLostConnection($e)) {
$this->reconnect();
$this->pdo->beginTransaction();
} else {
throw $e;
}
$this->handleBeginTransactionException($e);
}
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
$this->getPdo()->exec(
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
);
$this->createSavepoint();
}
}
++$this->transactions;
/**
* Create a save point within the database.
*
* @return void
*/
protected function createSavepoint()
{
$this->getPdo()->exec(
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
);
}
$this->fireConnectionEvent('beganTransaction');
/**
* Handle an exception from a transaction beginning.
*
* @param \Exception $e
* @return void
*/
protected function handleBeginTransactionException($e)
{
if ($this->causedByLostConnection($e)) {
$this->reconnect();
$this->pdo->beginTransaction();
} else {
throw $e;
}
}
/**
@@ -112,25 +165,42 @@ public function commit()
*/
public function rollBack($toLevel = null)
{
if (is_null($toLevel)) {
$toLevel = $this->transactions - 1;
}
// We allow developers to rollback to a certain transaction level. We will verify
// that this given transaction level is valid before attempting to rollback to
// that level. If it's not we will just return out and not attempt anything.
$toLevel = is_null($toLevel)
? $this->transactions - 1
: $toLevel;
if ($toLevel < 0 || $toLevel >= $this->transactions) {
return;
}
// Next, we will actually perform this rollback within this database and fire the
// rollback event. We will also set the current transaction level to the given
// level that was passed into this method so it will be right from here out.
$this->performRollBack($toLevel);
$this->transactions = $toLevel;
$this->fireConnectionEvent('rollingBack');
}
/**
* Perform a rollback within the database.
*
* @param int $toLevel
* @return void
*/
protected function performRollBack($toLevel)
{
if ($toLevel == 0) {
$this->getPdo()->rollBack();
} elseif ($this->queryGrammar->supportsSavepoints()) {
$this->getPdo()->exec(
$this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
);
}
$this->transactions = $toLevel;
$this->fireConnectionEvent('rollingBack');
}
/**

0 comments on commit b2a4159

Please sign in to comment.