From 6be194f680450cc1f7bf960f916a5ff9906c3e05 Mon Sep 17 00:00:00 2001
From: Boris Kolpackov The final bit of code in our example is the Let's now compile (Section 2.3, "Compiling and
@@ -1712,7 +1713,7 @@ class person
can make it private. It is also possible to have an object type
without the default constructor. However, in this case, the database
operations can only load the persistent state into an existing instance
- (Section 3.6, "Loading Persistent Objects",
+ (Section 3.7, "Loading Persistent Objects",
Section 4.4, "Query Result"). The object id type
should be default-constructible.3.2 Object Pointers 3.3 Database
- 3.4 Transactions
- 3.5 Making Objects Persistent
- 3.6 Loading Persistent Objects
- 3.7 Updating Persistent Objects
- 3.8 Deleting Persistent Objects
- 3.9 Executing Native SQL Statements
+ 3.10 ODB Exceptions
+ 3.5 Error Handling and Recovery
+ 3.6 Making Objects Persistent
+ 3.7 Loading Persistent Objects
+ 3.8 Updating Persistent Objects
+ 3.9 Deleting Persistent Objects
+ 3.10 Executing Native SQL Statements
@@ -1203,7 +1204,7 @@ main (int argc, char* argv[])
3.11 ODB Exceptions catch
block that handles the database exceptions. We do this by catching
- the base ODB exception (Section 3.10, "ODB
+ the base ODB exception (Section 3.11, "ODB
Exceptions") and printing the diagnostics.
has_current()
static function.
- If two or more transactions access or modify more than one object
- and are executed concurrently by different applications or by
- different threads within the same application, then it is possible
- that these transactions will try to access objects in an incompatible
- order and deadlock. The canonical example of a deadlock are
- two transactions in which the first has modified object1
- and is waiting for the second transaction to commit its changes to
- object2
so that it can also update object2
.
- At the same time the second transaction has modified object2
- and is waiting for the first transaction to commit its changes to
- object1
because it also needs to modify object1
.
- As a result, none of the two transactions can be completed.
The database system detects such situations and automatically
- aborts the waiting operation in one of the deadlocked transactions.
- In ODB this translates to the odb::deadlock
exception
- being thrown from one of the database functions. You would normally
- handle a deadlock by restarting the transaction, for example:
-for (;;) -{ - try - { - transaction t (db.begin ()); - - ... - - t.commit (); - break; - } - catch (const odb::deadlock&) - { - continue; - } -} --
Note that in the above discussion of atomicity, consistency, isolation, and durability, all of those guarantees only apply to the object's state in the database as opposed to the object's @@ -2122,7 +2085,85 @@ update_age (database& db, person& p) -
ODB uses C++ exceptions to report database operation errors. Most
+ ODB exceptions signify hard errors or errors that cannot
+ be corrected without some intervention from the application.
+ For example, if we try to load an object with an unknown object
+ id, the odb::object_not_persistent
exception is
+ thrown. Our application may be able to correct this error, for
+ instance, by obtaining a valid object id and trying again.
+ The hard errors and corresponding ODB exceptions that can be
+ thrown by each database function are described in the remainder
+ of this chapter with Section 3.11, "ODB Exceptions"
+ providing a quick reference for all the ODB exceptions.
The second group of ODB exceptions signify soft or
+ recoverable errors. Such errors are temporary
+ failures which normally can be corrected by simply re-executing
+ the transaction. ODB defines three such exceptions:
+ odb::connection_lost
, odb::timeout
,
+ and odb::deadlock
. All recoverable ODB exceptions
+ are derived from the common odb::recoverable
base
+ exception which can be used to handle all the recoverable
+ conditions with a single catch
block.
The odb::connection_lost
exception is thrown if
+ a connection to the database is lost in the middle of
+ a transaction. In this situation the transaction is aborted but
+ it can be re-tried without any changes. Similarly, the
+ odb::timeout
exception is thrown if one of the
+ database operations or the whole transaction has timed out.
+ Again, in this case the transaction is aborted but can be
+ re-tried as is.
If two or more transactions access or modify more than one object
+ and are executed concurrently by different applications or by
+ different threads within the same application, then it is possible
+ that these transactions will try to access objects in an incompatible
+ order and deadlock. The canonical example of a deadlock are
+ two transactions in which the first has modified object1
+ and is waiting for the second transaction to commit its changes to
+ object2
so that it can also update object2
.
+ At the same time the second transaction has modified object2
+ and is waiting for the first transaction to commit its changes to
+ object1
because it also needs to modify object1
.
+ As a result, none of the two transactions can be completed.
The database system detects such situations and automatically
+ aborts the waiting operation in one of the deadlocked transactions.
+ In ODB this translates to the odb::deadlock
+ recoverable exception being thrown from one of the database functions.
The following code fragment shows how to handle the recoverable + exceptions by restarting the affected transaction:
+ ++const unsigned short max_retries = 5; + +for (unsigned short retry_count (0); ; retry_count++) +{ + try + { + transaction t (db.begin ()); + + ... + + t.commit (); + break; + } + catch (const odb::recoverable& e) + { + if (retry_count > max_retries) + throw retry_limit_exceeded (e.what ()); + else + continue; + } +} ++ +
A newly created instance of a persistent class is transient.
We use the database::persist()
function template
@@ -2208,7 +2249,7 @@ cerr << "Jane's id: " << jane_id << endl;
threads in your application and to other applications as soon as
possible.
Once an object is made persistent, and you know its object id, it
can be loaded by the application using the database::load()
@@ -2273,7 +2314,7 @@ t.commit ();
identifier can be significantly faster than executing a query.
If a persistent object has been modified, we can store the updated
state in the database using the database::update()
@@ -2355,7 +2396,7 @@ transfer (database& db,
t.commit ();
-
To delete a persistent object's state from the database we use the
database::erase()
function template. If the application
@@ -2410,7 +2451,7 @@ db.erase<person> (joe_id);
t.commit ();
-
In some situations we may need to execute native SQL statements instead of using the object-oriented database API described above. @@ -2448,7 +2489,7 @@ db.execute ("CREATE TABLE test (n INT PRIMARY KEY)"); t.commit (); -
In the previous sections we have already mentioned some of the exceptions that can be thrown by the database functions. In this @@ -2483,7 +2524,7 @@ namespace odb
namespace odb { - struct null_pointer: odb::exception + struct null_pointer: exception { virtual const char* what () const throw (); @@ -2491,19 +2532,19 @@ namespace odb // Transaction exceptions. // - struct already_in_transaction: odb::exception + struct already_in_transaction: exception { virtual const char* what () const throw (); }; - struct not_in_transaction: odb::exception + struct not_in_transaction: exception { virtual const char* what () const throw (); }; - struct transaction_already_finalized: odb::exception + struct transaction_already_finalized: exception { virtual const char* what () const throw (); @@ -2511,19 +2552,19 @@ namespace odb // Session exceptions. // - struct already_in_session: odb::exception + struct already_in_session: exception { virtual const char* what () const throw (); }; - struct not_in_session: odb::exception + struct not_in_session: exception { virtual const char* what () const throw (); }; - struct const_object: odb::exception + struct const_object: exception { virtual const char* what () const throw (); @@ -2531,31 +2572,47 @@ namespace odb // Database operations exceptions. // - struct deadlock: odb::exception + struct recoverable: exception + { + }; + + struct connection_lost: recoverable + { + virtual const char* + what () const throw (); + }; + + struct timeout: recoverable + { + virtual const char* + what () const throw (); + }; + + struct deadlock: recoverable { virtual const char* what () const throw (); }; - struct object_not_persistent: odb::exception + struct object_not_persistent: exception { virtual const char* what () const throw (); }; - struct object_already_persistent: odb::exception + struct object_already_persistent: exception { virtual const char* what () const throw (); }; - struct result_not_cached: odb::exception + struct result_not_cached: exception { virtual const char* what () const throw (); }; - struct database_exception: odb::exception + struct database_exception: exception { }; } @@ -2578,21 +2635,28 @@ namespace odbodb::session
class and are discussed in Chapter 8, "Session". -The
deadlock
exception is thrown when a transaction - deadlock is detected by the database system. It can be thrown by any - database function. See Section 3.4, "Transactions" +The
recoverable
exception serves as a common base + for all the recoverable exceptions, which are:connection_lost
, +timeout
, anddeadlock
. The +connection_lost
exception is thrown when a connection + to the database is lost. Similarly, thetimeout
exception + is thrown if one of the database operations or the whole transaction + has timed out. Thedeadlock
exception is thrown when a + transaction deadlock is detected by the database system. These + exceptions can be thrown by any database function. See + Section 3.5, "Error Handling and Recovery" for details.The
object_already_persistent
exception is thrown by thepersist()
database function. See - Section 3.5, "Making Objects Persistent" + Section 3.6, "Making Objects Persistent" for details.The
object_not_persistent
exception is thrown by theload()
andupdate()
database functions. Refer to - Section 3.6, "Loading Persistent Objects" and - Section 3.7, "Updating Persistent Objects" for + Section 3.7, "Loading Persistent Objects" and + Section 3.8, "Updating Persistent Objects" for more information.The
result_not_cached
exception is thrown by @@ -4942,9 +5006,9 @@ namespace odbA session is an object cache. Every time an object is made persistent by calling the
database::persist()
function - (Section 3.5, "Making Objects Persistent"), loaded + (Section 3.6, "Making Objects Persistent"), loaded by calling thedatabase::load()
ordatabase::find()
- function (Section 3.6, "Loading Persistent Objects"), + function (Section 3.7, "Loading Persistent Objects"), or loaded by iterating over a query result (Section 4.4, "Query Result"), the pointer to the persistent object, in the form of the canonical object pointer (Section 3.2, "Object @@ -4952,7 +5016,7 @@ namespace odb session is in effect, any subsequent calls to load the same object will return the cached instance. When an object's state is deleted from the database with thedatabase::erase()
function - (Section 3.8, "Deleting Persistent Objects"), the + (Section 3.9, "Deleting Persistent Objects"), the cached object pointer is removed from the session. For example:@@ -5721,7 +5785,7 @@ private: choice.For additional information on the automatic identifier assignment, - refer to Section 3.5, "Making Objects Persistent".
+ refer to Section 3.6, "Making Objects Persistent".9.3.3
-- cgit v1.1type