Oracle® TimesTen In-Memory Database TTClasses Guide Release 11.2.1 Part Number E13074-02 |
|
|
View PDF |
This chapter contains brief descriptions of the recommended way to use TTClasses. It includes the following topics:
While TTClasses can be used in a number of ways, the following general approach has been successfully and can easily be adapted to a variety of applications.
To achieve optimal performance, real-time applications should use prepared SQL statements. Ideally, all SQL statements that will be used by an application are prepared when the application begins, using separate TTCmd objects for each statement. In ODBC (and thus in the C++ classes), statements are bound to a particular connection, so a full set of all statements used by the application will often be associated with every connection to the TimesTen database.
An easy way to accomplish this is to develop an application-specific class that is derived from TTConnection. For an application called XYZ
, you can create a class called XYZConnection
, derived from TTConnection. The XYZConnection
class contains private TTCmd members representing the prepared SQL statements that can be used in the application. In addition, the XYZConnection
class provides new public methods to implement the application-specific database functionality, which can be implemented using the private TTCmd members.
Example 3-1 Definition of a Connection Class
This is an example of a class that inherits its functionality from TTConnection.
class XYZConnection : public TTConnection { private: TTCmd updateData; TTCmd insertData; TTCmd queryData; public: XYZConnection(); ~XYZConnection(); virtual void Connect (const char* connStr,TTStatus&); void updateUser (TTStatus&); void addUser (char* nameP, TTStatus&); void queryUser (const char* nameP, int* valueP, TTStatus&); };
In the preceding example, an XYZConnection
object is a connection to TimesTen that can be used to perform three application-specific operations: addUser()
, updateUser()
, and queryUser()
. These operations are specific to the application (storing account balances, for example). The implementation of these three methods can use updateData()
, insertData()
, and queryData()
provided in TTCmd to implement the specific functionality of the application.
To cause the SQL statements used by the application to be prepared, the XYZConnection
class overloads the Connect()
method provided by the TTConnection base class. The XYZConnection::Connect()
method will call the Connect()
method of the base class to establish the database connection, and also calls the Prepare()
method for each TTCmd object to cause the SQL statements to be prepared for later use.
Example 3-2 Definition of a Connect() method
This example shows the XYZConnection::Connect()
method.
void XYZConnection::Connect(const char* connStr, TTStatus& stat) { TTStatus stat2; try { TTConnection::Connect(connStr, stat); updateData.Prepare(this, "update mydata v "set foo = ? where bar = ?", stat); insertData.Prepare(this, "insert into mydata " "values(?,0)", stat); queryData.Prepare(this, "select i from mydata where name " " = ?", stat); Commit(stat); } catch (TTStatus st) { cerr << "Error in XYZConnection::Connect: " << st << endl; Rollback(stat2); } return; }
This Connect()
method causes the XYZConnection
to be made fully operational. The application-specific methods are fully functional after Connect()
has been called.
This approach to application design works well with the design of the TTConnectionPool class. The application can create numerous objects of type XYZConnection
and can add them to TTConnectionPool. By calling TTConnectionPool::ConnectAll()
, the application can cause all connections in the pool to be connected to the database, as well as causing all SQL statements to be prepared, in a single line of code.
This approach to application design allows the database components of an application to be separated from the remainder of the application; only the XYZConnection
class contains database-specific code.
An example of this type of design can be found in several of the sample programs that are included with TTClasses. The simplest example is sample.cpp
.
Note that other configurations are possible. Some customers have extended this scheme further, so that SQL statements to be used in an application are listed in a table in the database, rather than being hard-coded in the application itself. This allows changes to database functionality to be implemented by making database changes rather than application changes.
TTClasses has a logging facility that allows applications to capture useful debugging information about running TTClasses programs. TTClasses logging is done on at the process level. You can enable logging for a specific process and produce a single output log stream for the process. TTClasses logging is disabled by default.
TTClasses supports different levels of logging information. See Example 3-4 for more information about what is printed at each log level.
Log level WARN is very useful while developing a TTClasses application and can also be appropriate for production applications because in this log level database query plans are generated.
Note that at the more verbose log levels (INFO and DEBUG), so much log data is generated that application performance is adversely affected. We strongly discourage using these log levels in a production environment.
Although TTClasses logging can print to either stdout
or stderr
, the best approach is to write directly to a TTClasses log file. Example 3-3 demonstrates how to print TTClasses log information at log level WARN into the /tmp/ttclasses.log
output file.
Example 3-3 Printing TTClasses log information
ofstream output; output.open("/tmp/ttclasses.log"); TTGlobal::setLogStream(output); TTGlobal::setLogLevel(TTLog::TTLOG_WARN);
First-time users of TTClasses should spend a little time experimenting with TTClasses logging. You can change the sample.cpp
program to use different log levels so you can see how errors are printed at log level ERROR and how huge amounts of logs are generated at log levels INFO and DEBUG.
See "TTGlobal" for more information about using the TTGlobal
class for logging.
The Transaction Log API (XLA) is a set of functions that enable you to implement applications that:
Monitor TimesTen for changes to specified tables in a local data store
Receive real-time notification of these changes
One of the purposes of XLA is to provide a high-performance, asynchronous alternative to triggers.
For additional information about XLA, see the chapter "XLA and TimesTen Event Management" in the Oracle TimesTen In-Memory Database C Developer's Guide.
TimesTen provides TTClasses XLA demos in the following location:
install_dir
/quickstart/sample_code/ttclasses/xla
Refer to "About the TimesTen TTClasses demos".
The README file in the ttclasses
directory contains instructions for building and running the TTClasses XLA demos (as well as other TTClasses demos).
XLA returns notification of changes to specific tables in the database, as well as information about the transaction boundaries for those database changes. This section shows how to acknowledge updates only at transaction boundaries (a common requirement for XLA applications), using one example that does not use and one example that does use transaction boundaries.
Example 3-4 TTClasses XLA program
This example shows a typical main loop of a TTClasses XLA program.
TTXlaPersistConnection conn; // XLA connection TTXlaTableList list(&conn); // tables being monitored ttXlaUpdateDesc_t ** arry; // ptr to returned XLA recs TTStatus stat; int records_fetched; // ... loop { // fetch the updates conn.fetchUpdatesWait(&arry, MAX_RECS_TO_FETCH, &records_fetched, ...); // Interpret the updates for(j=0;j < records_fetched;j++){ ttXlaUpdateDesc_t *p; p = arry[j]; list.HandleChange(p, NULL); } // end for each record fetched // periodically call ackUpdates() if (some condition is reached) { conn.ackUpdates(stat) ; } } // loop
Inside the HandleChange()
method, depending on whether the record is an insert, update, or delete, the appropriate method of the following is called: HandleInsert()
, HandleUpdate()
, or HandleDelete()
.
It is inside HandleChange()
that you can access the flag that indicates whether the XLA record is the last record in a particular transaction.
Thus there is no way in the example loop for the HandleChange()
method to pass the information about the transaction boundary to the loop, so that this information can influence when to call conn.ackUpdates()
.
Under typical circumstances of only a few records per transaction, this is not an issue. When you ask XLA to return at most 1000 records with the fetchUpdatesWait()
method, usually only a few records are returned. XLA returns records as quickly as it can, and even if huge numbers of transactions are occurring in the database, you usually can pull the XLA records out quickly, a few at a time. When you pull the XLA records out a few at a time, XLA usually makes sure that the last record returned is on a transaction boundary.
In summary: if you ask for 1000 records from XLA, and XLA returns only 15, it is highly probable that the 15th record is at the end of a transaction.
XLA guarantees one of the following:
Either a batch of records will end with a completed transaction (perhaps multiple transactions in a single batch of XLA records)
Or a batch of records will contain a partial transaction, with no completed transactions in the same batch, and that subsequent batches of XLA records will be returned for that single transaction until its transaction boundary has been reached.
Careful XLA applications need to verify whether the last record in a batch of XLA records has a transaction boundary and call ackUpdates()
only on this transaction boundary. This is especially important when operations involve a large number of rows. If a bulk insert/update/delete operation has been performed on the database and the XLA application asks for 1000 records, it might receive all 1000 records (or fewer than 1000). The last record returned through XLA will probably not have the end-of-transaction flag. In fact, if the transaction has made changes to 10,000 records, then clearly a minimum of 10 blocks of 1000 XLA records must be fetched before reaching the transaction boundary.
Calling ackUpdates()
for every transaction boundary is not recommended, because ackUpdates()
is a relatively expensive operation. Careful XLA applications should make sure to call this method only on a transaction boundary, so that when the application or system or database fails, the XLA bookmark is at the start of a transaction after the system recovers.
The HandleChange()
method has a second parameter to allow passing information between HandleChange()
and the main XLA loop. Compare Example 3-4 with Example 3-5 (the do_acknowledge
setting, and the &do_acknowledge
parameter of the HandleChange()
call).
Example 3-5 TTClasses XLA program using transaction boundaries
In this example, ackUpdates()
is called only when the do_acknowledge
flag indicates that this batch of XLA records is at a transaction boundary.
TTXlaPersistConnection conn; // XLA connection TTXlaTableList list(&conn); // tables being monitored ttXlaUpdateDesc_t ** arry; // ptr to returned XLA recs TTStatus stat; int records_fetched; int do_acknowledge; // ... loop { // fetch the updates conn.fetchUpdatesWait(&arry, MAX_RECS_TO_FETCH, &records_fetched, ...); do_acknowledge = FALSE; // Interpret the updates for(j=0;j < records_fetched;j++){ ttXlaUpdateDesc_t *p; p = arry[j]; list.HandleChange(p, &do_acknowledge); } // end for each record fetched // periodically call ackUpdates() if (do_acknowledge == TRUE /* and some other conditions ... */ ) { conn.ackUpdates(stat) ; } } // loop
In addition to this change to the XLA main loop, the HandleChange()
method needs to be overwritten to use ttXlaUpdateDesc_t
.