Transaktionen

Sollen mehrere Abfragen gebündelt an den Datenbankserver übertragen werden und sollen bei einem Fehler die Anweisugen nicht ausgeführt werden, so kommen Transaktionen zum Einsatz. In dem folgenden Beispiel wird die Verwendung von Transaktionen exemplarisch dargestellt.

Verschachtelte Transaktionen

Wird in einer Funktion eine Transaktion verwendet, so könnte man diese Funktion nicht in andere Transaktionen einbinden. Die Funktion doSomethingInTransaction() startet eine Transaktion und beendet diese auch wieder. Mit dem Commit wird aber auch die Transaktion von doOtherThingsInTransaction beendet.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

function doSomethingInTransaction(\Alvine\Persistence\Relation\DataObject $dataObject) {

    $dataObject->beginTransaction();

    // Einige SQL-Statements ...

    $dataObject->commitTransaction();
}

function doOtherThingsInTransaction(\Alvine\Persistence\Relation\DataObject $dataObject) {

    $dataObject->beginTransaction();
    doSomethingInTransaction($dataObject);

    // Die Transaktion des DataObjekt ist aber bereits beendet, 
    // da doSomethingInTransaction ein Commit ausführt.
    $dataObject->commitTransaction();
}

Dieses Verhalten ist aber nicht gewünscht.

Durch den Aufruf von DataObject::enableTransactionStackCounter() wird die Transaktion nun nicht mehr in der Funktion doSomethingInTransaction() beendet, sondern erst in doOtherThingsInTransaction.

Beispiel

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());


/*
 * Gebündelte Statements
 */
$transaction=new \Alvine\Persistence\Relation\Transaction(true);

/** Erstes Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'10',
    'title'=>'My Title 10', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'20',
    'title'=>'My Title 20', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));

$insertStatement=new \Alvine\Persistence\Relation\SQL\Insert\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'ISBN'],
        ['example-1', 'title'],
        ['example-1', 'record', '\Alvine\Persistence\Relation\Field\Date'],
        ['example-1', 'book-count', '\Alvine\Persistence\Relation\Field\Integer'],
    ]));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($insertStatement, $records));

/** Zweites Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'11']));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'22']));

$updateStatement=new \Alvine\Persistence\Relation\SQL\Update\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'book-count', '\Alvine\Persistence\Relation\Field\Integer']
    ]));
$updateStatement->where(new \Alvine\Persistence\Relation\SQL\Where
    (new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN')));
$transaction->append(new \Alvine\Persistence\Relation\Query(
    $updateStatement, $records));

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($transaction);
    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * START TRANSACTION
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *      VALUES ('111-1-86681-001-6','My Title 10','2017-12-31',10)
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *      VALUES ('222-2-86681-001-6','My Title 20','2017-12-31',20)
     * UPDATE `example-1` SET `book-count`=11 
     *  WHERE `example-1`.`ISBN`='111-1-86681-001-6'
     * UPDATE `example-1` SET `book-count`=22 
     *  WHERE `example-1`.`ISBN`='222-2-86681-001-6'
     * COMMIT
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    print_r($result);
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    //print_r($resultSet);
    /** Gibt die Anzahl der betroffenen Datensätze zurückx */
    print_r($resultSet);
}

Tritt ein Fehler auf oder ist ein Statement inkorrekt, so erfolgt wie im folgenden Beispiel ein Rollback.

<!--- example:persistence/relationaldatabase-example-transaction-rollback.php -->
```php
/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());


/*
 * Gebündelte Statements
 */
$transaction=new \Alvine\Persistence\Relation\Transaction(true);

/** Erstes Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'10',
    'title'=>'My Title 10', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'20',
    'title'=>'My Title 20', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));

$insertStatement=new \Alvine\Persistence\Relation\SQL\Insert\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'ISBN'],
        ['example-1', 'title'],
        ['example-1', 'record', 
            '\Alvine\Persistence\Relation\Field\Date'],
        ['example-1', 'book-count', 
            '\Alvine\Persistence\Relation\Field\Integer'],
    ]));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($insertStatement, $records));

/** Zweites Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'11']));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'22']));

$updateStatement=new \Alvine\Persistence\Relation\SQL\Update\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'book-count', 
            '\Alvine\Persistence\Relation\Field\Integer']
    ]));
$updateStatement->where(new \Alvine\Persistence\Relation\SQL\Where
    (new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN')));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($updateStatement, $records));

$statement=new \Alvine\Persistence\Relation\GenericStatement('DO FEHLER');
$transaction->append(new \Alvine\Persistence\Relation\Query($statement));

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($transaction);
    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * START TRANSACTION
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *        VALUES ('111-1-86681-001-6','My Title 10','2017-12-31',10)
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *        VALUES ('222-2-86681-001-6','My Title 20','2017-12-31',20)
     * UPDATE `example-1` SET `book-count`=11 
     *  WHERE `example-1`.`ISBN`='111-1-86681-001-6'
     * UPDATE `example-1` SET `book-count`=22 
     *  WHERE `example-1`.`ISBN`='222-2-86681-001-6'
     * DO FEHLER
     * ROLLBACK
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    print_r($result);
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    //print_r($resultSet);
    /** Gibt die Anzahl der betroffenen Datensätze zurückx */
    print_r($resultSet);
}

Rollback

Verlorene" Auto-Inkrement-Werte und Sequenzlücken

Wenn in allen Sperrmodi (0, 1 und 2) eine Transaktion, die Auto-Inkrement-Werte erzeugt hat, zurückgesetzt wird, sind diese Auto-Inkrement-Werte "verloren". Sobald ein Wert für eine Spalte mit automatischer Erhöhung erzeugt wurde, kann er nicht mehr zurückgesetzt werden, unabhängig davon, ob die Anweisung "INSERT-like" abgeschlossen ist oder nicht und ob die enthaltene Transaktion zurückgesetzt wird oder nicht. Solche verlorenen Werte werden nicht wiederverwendet. Daher kann es zu Lücken in den Werten kommen, die in einer AUTO_INCREMENT-Spalte einer Tabelle gespeichert sind.

Quelle dev.mysql.com