Basic Transaction Example

The following source code is an example of how to perform transaction handling. The code demonstrates the creating a transaction, commiting and rollback. Note that an automatic rollback will occur if a query smart pointer goes out of scope without calling commit.

/*
 * basic_transaction application showing the transaction support usage of the dbConnect API
 * Copyright (C) 2004 Johnathan Ingram, jingram@rogueware.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  US
 *
 * Note: This example requires the TransactionTest table as defined in the SQL for each database type
 *       The example uses two database connections, showing the state of data on each as transactions are performed
 */



#include <iostream>
#include <map>
#include <string>


#include "dbconn/dbconnect.h"

using namespace std;

void
showTableData(
      DbQueryVar &conn1,
      DbConnectionVar &driver)
   throw(
      BaseException)
{
   // Create a second connection
   DbQueryVar conn2 = driver->requestQueryConnection();

   // Execute the same query on both connections
   string sqlQuery =
      "SELECT "
      "  id, description "
      "FROM "
      "  TransactionTest ";


   // Connection 1: List the contents of the table
   conn1->command(sqlQuery);
   conn1->execute();

   cout << "Connection 1: TransactionTest table data: " << endl;
   cout << "----------------------------------------- " << endl;
   cout << "id\tdescription" << endl;
   cout << "--\t-----------" << endl;
   if (conn1->eof())
      cout << "No rows in result set for this query" << endl;
   else
   {
      while (!conn1->eof())
      {
         conn1->fetchNext();
         cout << conn1->getFieldByName("id")->asString() << "\t" << conn1->getFieldByName("description")->asString() << endl;
      }
   }
   cout << endl;

   // Connection 2: List the contents of the table
   conn2->command(sqlQuery);
   conn2->execute();

   cout << "Connection 2: TransactionTest table data: " << endl;
   cout << "----------------------------------------- " << endl;
   cout << "id\tdescription" << endl;
   cout << "--\t-----------" << endl;
   if (conn2->eof())
      cout << "No rows in result set for this query" << endl;
   else
   {
      while (!conn2->eof())
      {
         conn2->fetchNext();
         cout << conn2->getFieldByName("id")->asString() << "\t" << conn2->getFieldByName("description")->asString() << endl;
      }
   }
   cout << endl;
}



int
main(
      int argc, char** argv)
{
   map<string, DbConnection::Driver> drivers;

   drivers["MYSQL"] =       DbConnection::MYSQL;
   drivers["MSQL"] =        DbConnection::MSQL;
   drivers["POSTGRESQL"] =  DbConnection::POSTGRESQL;
   drivers["DB2"] =         DbConnection::DB2;

   if (argc == 2)
   {
      // Use smart pointers. Must be declared outside of the try
      //   as if an exception is caught it will loose scope and free.
      DbConnectionVar driver;
      DbQueryVar      conn1;

      string sqlQuery =
         "INSERT INTO TransactionTest "
         "  (id, description) "
         "VALUES "
         "  (:id, :description) ";

      try
      {
         driver = new DbConnection(drivers[argv[1]]);

         // Connect to the database. (DB2 takes different conn string)
         if (drivers[argv[1]] == DbConnection::DB2)
         {
            cout << "WARNING: DB2's locking will cause this program to hang" << endl;
            driver->connect("dbconn""letmein""dbConnDB"""52);
         }
         else
         {
            driver->connect("dbconnect""letmein""dbConnectDB""localhost"52);
         }

         // Get a query connection object
         conn1 = driver->requestQueryConnection();



         // Test 1: Insert data with commit
         cout << "TEST 1: Inserting data and commiting" << endl;
         cout << "------------------------------------" << endl << endl;

         // Note: You dont have to call transBegin as the first query will always
         //       implicitly create a transaction if one does not exist.
         conn1->transBegin();

         cout << "Data on connections before record insert.." << endl;
         showTableData(conn1, driver);
         cout << endl;

         // Insert the data using connection 1
         conn1->command(sqlQuery);
         conn1->bindParam("id")->setLong(1);
         conn1->bindParam("description")->setString("RECORD ONE");
         conn1->execute();

         cout << "Data on connections after record insert.." << endl;
         showTableData(conn1, driver);
         cout << endl;

         // Commit on connection one to make the data final
         conn1->commit();

         cout << "Data on connections after commit.." << endl;
         showTableData(conn1, driver);
         cout << endl;




         // Test 2: Insert data with rollback
         cout << endl << endl;
         cout << "TEST 2: Inserting data and rolling back" << endl;
         cout << "------------------------------------" << endl << endl;


         cout << "Data on connections before record insert.." << endl;
         showTableData(conn1, driver);
         cout << endl;

         // Insert the data using connection 1
         conn1->command(sqlQuery);
         conn1->bindParam("id")->setLong(2);
         conn1->bindParam("description")->setString("RECORD TWO");
         conn1->execute();

         cout << "Data on connections after record insert.." << endl;
         showTableData(conn1, driver);
         cout << endl;

         // Rollback on connection one to get rid of data
         conn1->rollback();

         cout << "Data on connections after rollback.." << endl;
         showTableData(conn1, driver);
         cout << endl;



         // Finnally clean all the data out the table
         cout << "TEST 3: Deleting all data and commiting" << endl;
         cout << "------------------------------------" << endl << endl;
         sqlQuery =
            "DELETE FROM TransactionTest WHERE id IN (1, 2)" ;

         conn1->command(sqlQuery);
         conn1->execute();
         conn1->commit();

         cout << "Data on connections after delete and commit" << endl;
         showTableData(conn1, driver);
         cout << endl;


         // Note: If you do not call commit or rollback on a connection,
         //       the connection will rollback automatically.
         //       This is implemented so if an exception is thrown, a rollback will automatically occur.
      }
      catch(BaseException &ex)
      {
         // Only need to catch a single exception. Can use 'name' etc to determine the actual exception
         cout << "DbConnect Exception: " << ex.name << endl
              << "  " << ex.code << " : " << ex.description << endl;
      }
      catch(...)
      {
         cout << "An Unknown exception has been trapped!\n" << endl;
      }
      cout << endl;

      // The 'conn' and 'driver' objects are smart pointers and will cleanup as soon as they go out of scope
   }
   else
   {
      cout << "Syntax: basic_transaction DRIVER" << endl;
      cout << "Drivers: MYSQL" << endl;
      cout << "       : MSQL" << endl;
      cout << "       : POSTGRESQL" << endl;
      cout << "       : DB2" << endl;
      return 1;
   }

   return 0;
}