mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
683 lines
25 KiB
HTML
683 lines
25 KiB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||
<title>Program Listing</title>
|
||
<link rel="stylesheet" href="gettingStarted.css" type="text/css" />
|
||
<meta name="generator" content="DocBook XSL Stylesheets V1.73.2" />
|
||
<link rel="start" href="index.html" title="Getting Started with Replicated Berkeley DB Applications" />
|
||
<link rel="up" href="txnapp.html" title="Chapter 2. Transactional Application" />
|
||
<link rel="prev" href="txnapp.html" title="Chapter 2. Transactional Application" />
|
||
<link rel="next" href="repapp.html" title="Chapter 3. The DB Replication Manager" />
|
||
</head>
|
||
<body>
|
||
<div xmlns="" class="navheader">
|
||
<div class="libver">
|
||
<p>Library Version 11.2.5.3</p>
|
||
</div>
|
||
<table width="100%" summary="Navigation header">
|
||
<tr>
|
||
<th colspan="3" align="center">Program Listing</th>
|
||
</tr>
|
||
<tr>
|
||
<td width="20%" align="left"><a accesskey="p" href="txnapp.html">Prev</a> </td>
|
||
<th width="60%" align="center">Chapter 2. Transactional Application</th>
|
||
<td width="20%" align="right"> <a accesskey="n" href="repapp.html">Next</a></td>
|
||
</tr>
|
||
</table>
|
||
<hr />
|
||
</div>
|
||
<div class="sect1" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h2 class="title" style="clear: both"><a id="simpleprogramlisting"></a>Program Listing</h2>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="toc">
|
||
<dl>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#repconfiginfo_cxx">
|
||
<span>Class: RepConfigInfo</span>
|
||
|
||
</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#repmgr_cxx">Class: excxx_repquote_gsg_simple</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#usage_cxx">Function: usage()</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#main_cxx">Function: main()</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#repmgr_init_cxx">Method: SimpleTxn::init()</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#doloop_cxx">Method: SimpleTxn::doloop()</a>
|
||
</span>
|
||
</dt>
|
||
<dt>
|
||
<span class="sect2">
|
||
<a href="simpleprogramlisting.html#printstocks_c">
|
||
|
||
<span>Method: SimpleTxn::print_stocks()</span>
|
||
|
||
</a>
|
||
</span>
|
||
</dt>
|
||
</dl>
|
||
</div>
|
||
<p>
|
||
Our example program is a fairly simple transactional
|
||
application. At this early stage of its development, the
|
||
application contains no hint that it must be network-aware
|
||
so the only command line argument that it takes is one that
|
||
allows us to specify the environment home directory.
|
||
(Eventually, we will specify things like host names and
|
||
ports from the command line).
|
||
</p>
|
||
<p>
|
||
Note that the application performs all writes under the
|
||
protection of a transaction; however, multiple database
|
||
operations are not performed per transaction. Consequently,
|
||
we simplify things a bit by using autocommit for our
|
||
database writes.
|
||
</p>
|
||
<p>
|
||
Also, this application is single-threaded. It is possible
|
||
to write a multi-threaded or multi-process application that
|
||
performs replication. That said, the concepts described in
|
||
this book are applicable to both single threaded and
|
||
multi-threaded applications so nothing
|
||
is gained by multi-threading this application other than
|
||
distracting complexity. This manual
|
||
does, however, identify where care must be taken when
|
||
performing replication with a non-single threaded
|
||
application.
|
||
</p>
|
||
<p>
|
||
Finally, remember that transaction processing is not described in
|
||
this manual. Rather, see the
|
||
<em class="citetitle">Berkeley DB Getting Started with Transaction Processing</em> guide for details on
|
||
that topic.
|
||
</p>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="repconfiginfo_cxx"></a>
|
||
<span>Class: RepConfigInfo</span>
|
||
|
||
</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
Before we begin, we present a
|
||
class that we will use to maintain useful
|
||
information for us. Under normal circumstances,
|
||
this class would not be necessary for a simple
|
||
transactional example such as this. However, this code will
|
||
grow into a replicated example that needs to
|
||
track a lot more information for the
|
||
application, and so we lay the groundwork for
|
||
it here.
|
||
</p>
|
||
<p>
|
||
The class that we create is called
|
||
<code class="classname">RepConfigInfo</code>
|
||
|
||
and its only purpose at this time is to track
|
||
the location of our environment home directory.
|
||
</p>
|
||
<pre class="programlisting">#include <db_cxx.h>
|
||
|
||
class RepConfigInfo {
|
||
public:
|
||
RepConfigInfo();
|
||
virtual ~RepConfigInfo();
|
||
|
||
public:
|
||
char* home;
|
||
};
|
||
|
||
|
||
RepConfigInfo::RepConfigInfo()
|
||
{
|
||
home = "TESTDIR";
|
||
}
|
||
|
||
RepConfigInfo::~RepConfigInfo()
|
||
{
|
||
} </pre>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="repmgr_cxx"></a>Class: excxx_repquote_gsg_simple</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
Our transactional example will
|
||
instantiate a class,
|
||
<code class="classname">SimpleTxn</code>, that performs
|
||
all our work for us. Before we implement our
|
||
<code class="function">main()</code> function, we show
|
||
the <code class="classname">SimpleTxn</code> class
|
||
declaration.
|
||
</p>
|
||
<p>
|
||
First, we provide some declarations and
|
||
definitions that are needed later in
|
||
our example:
|
||
</p>
|
||
<pre class="programlisting">#include <iostream>
|
||
#include <db_cxx.h>
|
||
#include "RepConfig.h"
|
||
|
||
|
||
using std::cout;
|
||
using std::cin;
|
||
using std::cerr;
|
||
using std::endl;
|
||
using std::flush;
|
||
|
||
#define CACHESIZE (10 * 1024 * 1024)
|
||
#define DATABASE "quote.db"
|
||
|
||
const char *progname = "excxx_reqquote_gsg_simple";
|
||
|
||
#ifdef _WIN32
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <windows.h>
|
||
#include <direct.h>
|
||
|
||
extern "C" {
|
||
extern int getopt(int, char * const *, const char *);
|
||
extern char *optarg;
|
||
}
|
||
#else
|
||
#include <errno.h>
|
||
#endif </pre>
|
||
<p>
|
||
And then we define our <code class="classname">SimpleTxn</code> class:
|
||
</p>
|
||
<pre class="programlisting">class SimpleTxn
|
||
{
|
||
public:
|
||
// Constructor.
|
||
SimpleTxn();
|
||
// Initialization method. Creates and opens our environment handle.
|
||
int init(RepConfigInfo* config);
|
||
// The doloop is where all the work is performed.
|
||
int doloop();
|
||
// terminate() provides our shutdown code.
|
||
int terminate();
|
||
|
||
private:
|
||
// disable copy constructor.
|
||
SimpleTxn(const SimpleTxn &);
|
||
void operator = (const SimpleTxn &);
|
||
|
||
// internal data members.
|
||
RepConfigInfo *app_config;
|
||
DbEnv dbenv;
|
||
|
||
// private methods.
|
||
// print_stocks() is used to display the contents of our database.
|
||
static int print_stocks(Db *dbp);
|
||
}; </pre>
|
||
<p>
|
||
Note that we show the implementation of the various
|
||
<code class="classname">SimpleTxn</code> methods later in this section.
|
||
</p>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="usage_cxx"></a>Function: usage()</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
Our <code class="function">usage()</code> is at this
|
||
stage of development trivial because we only
|
||
have one command line argument to manage.
|
||
Still, we show it here for the sake of
|
||
completeness.
|
||
</p>
|
||
<pre class="programlisting">static void usage()
|
||
{
|
||
cerr << "usage: " << progname << endl
|
||
<< "-h home" << endl;
|
||
|
||
exit(EXIT_FAILURE);
|
||
} </pre>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="main_cxx"></a>Function: main()</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
Now we provide our <code class="function">main()</code>
|
||
function. This is a trivial function whose only
|
||
job is to collect command line information,
|
||
then instantiate a <code class="classname">SimpleTxn</code>
|
||
object, run it, then terminate it.
|
||
</p>
|
||
<p>
|
||
We begin by declaring some useful variables. Of
|
||
these, note that we instantiate our
|
||
<code class="classname">RepConfigInfo</code>
|
||
object here. Recall that this is used to store
|
||
information useful to our code. This class becomes more
|
||
interesting later in this book.
|
||
</p>
|
||
<pre class="programlisting">int main(int argc, char **argv)
|
||
{
|
||
RepConfigInfo config;
|
||
char ch;
|
||
int ret; </pre>
|
||
<p>
|
||
Then we collect our command line information. Again, this is at
|
||
this point fairly trivial:
|
||
</p>
|
||
<pre class="programlisting"> // Extract the command line parameters
|
||
while ((ch = getopt(argc, argv, "h:")) != EOF) {
|
||
switch (ch) {
|
||
case 'h':
|
||
config.home = optarg;
|
||
break;
|
||
case '?':
|
||
default:
|
||
usage();
|
||
}
|
||
}
|
||
|
||
// Error check command line.
|
||
if (config.home == NULL)
|
||
usage(); </pre>
|
||
<p>
|
||
Now we instantiate and initialize our <code class="classname">SimpleTxn</code>
|
||
class, which is what is responsible for doing all our real work.
|
||
The <code class="methodname">SimpleTxn::init()</code> method creates and
|
||
opens our environment handle.
|
||
</p>
|
||
<pre class="programlisting"> SimpleTxn runner;
|
||
try {
|
||
if((ret = runner.init(&config)) != 0)
|
||
goto err; </pre>
|
||
<p>
|
||
Then we call the <code class="methodname">SimpleTxn::doloop()</code>
|
||
method, which is where the actual transactional work is
|
||
performed for this application.
|
||
</p>
|
||
<pre class="programlisting"> if((ret = runner.doloop()) != 0)
|
||
goto err; </pre>
|
||
<p>
|
||
Finally, catch exceptions and terminate the program:
|
||
</p>
|
||
<pre class="programlisting"> } catch (DbException dbe) {
|
||
cerr << "Caught an exception during initialization or"
|
||
<< " processing: " << dbe.what() << endl;
|
||
}
|
||
err:
|
||
runner.terminate();
|
||
return 0;
|
||
} </pre>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="repmgr_init_cxx"></a>Method: SimpleTxn::init()</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
The <code class="methodname">SimpleTxn::init()</code>
|
||
method is used to create and open our environment handle.
|
||
For readers familiar with writing transactional
|
||
DB applications, there should be no surprises
|
||
here. However, we will be adding to this in later
|
||
chapters as we roll replication into this example.
|
||
</p>
|
||
<p>
|
||
First, we show the class constructor
|
||
implementation, which is only used to initialize a
|
||
few variables:
|
||
</p>
|
||
<pre class="programlisting">SimpleTxn::SimpleTxn() : app_config(0), dbenv(0)
|
||
{
|
||
} </pre>
|
||
<p>
|
||
We now provide the <code class="methodname">init()</code> method
|
||
implementation. The only thing of interest here is that we specify
|
||
<code class="literal">DB_TXN_NOSYNC</code> to our environment. This causes
|
||
our transactional commits to become non-durable, which is something
|
||
that we are doing only because of the nature of our example.
|
||
</p>
|
||
<pre class="programlisting">int SimpleTxn::init(RepConfigInfo *config)
|
||
{
|
||
int ret = 0;
|
||
|
||
app_config = config;
|
||
|
||
dbenv.set_errfile(stderr);
|
||
dbenv.set_errpfx(progname);
|
||
|
||
/*
|
||
* We can now open our environment.
|
||
*/
|
||
dbenv.set_cachesize(0, CACHESIZE, 0);
|
||
dbenv.set_flags(DB_TXN_NOSYNC, 1);
|
||
|
||
try {
|
||
dbenv.open(app_config->home,
|
||
DB_CREATE |
|
||
DB_RECOVER |
|
||
DB_INIT_LOCK |
|
||
DB_INIT_LOG |
|
||
DB_INIT_MPOOL |
|
||
DB_INIT_TXN,
|
||
0);
|
||
} catch(DbException dbe) {
|
||
cerr << "Caught an exception during DB environment open." << endl
|
||
<< "Ensure that the home directory is created prior to"
|
||
<< " starting the application." << endl;
|
||
ret = ENOENT;
|
||
goto err;
|
||
}
|
||
|
||
err:
|
||
return ret;
|
||
} </pre>
|
||
<p>
|
||
Finally, we present the <code class="methodname">SimpleTxn::terminate()</code>
|
||
method here. All this does is close the environment handle. Again,
|
||
there should be no surprises here, but we provide the
|
||
implementation for the sake of completeness anyway.
|
||
</p>
|
||
<pre class="programlisting">int SimpleTxn::terminate()
|
||
{
|
||
try {
|
||
dbenv.close(0);
|
||
} catch (DbException dbe) {
|
||
cerr << "error closing environment: " << dbe.what() << endl;
|
||
}
|
||
return 0;
|
||
} </pre>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="doloop_cxx"></a>Method: SimpleTxn::doloop()</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
Having written our <code class="function">main()</code>
|
||
function and support utility methods, we now implement
|
||
our application's
|
||
primary data processing method. This
|
||
method provides a command prompt at which the
|
||
user can enter a stock ticker value and a price for
|
||
that value. This information is then entered to the
|
||
database.
|
||
</p>
|
||
<p>
|
||
To display the database, simply enter
|
||
<code class="literal">return</code> at the prompt.
|
||
</p>
|
||
<p>
|
||
To begin, we declare a database pointer,
|
||
several <code class="classname">Dbt</code> variables, and
|
||
the usual assortment of variables used for buffers
|
||
and return codes. We also initialize all of this.
|
||
</p>
|
||
<pre class="programlisting">#define BUFSIZE 1024
|
||
int SimpleTxn::doloop()
|
||
{
|
||
Db *dbp;
|
||
Dbt key, data;
|
||
char buf[BUFSIZE], *rbuf;
|
||
int ret;
|
||
|
||
dbp = NULL;
|
||
memset(&key, 0, sizeof(key));
|
||
memset(&data, 0, sizeof(data));
|
||
ret = 0; </pre>
|
||
<p>
|
||
Next, we begin the loop and we immediately open our
|
||
database if it has not already been opened. Notice that
|
||
we specify autocommit when we open the database. In
|
||
this case, autocommit is important because we will only
|
||
ever write to our database using it. There is no need
|
||
for explicit transaction handles and commit/abort code
|
||
in this application, because we are not combining
|
||
multiple database operations together under a single
|
||
transaction.
|
||
</p>
|
||
<p>
|
||
Autocommit is described in greater detail in the
|
||
<em class="citetitle">Berkeley DB Getting Started with Transaction Processing</em> guide.
|
||
</p>
|
||
<pre class="programlisting"> for (;;) {
|
||
if (dbp == NULL) {
|
||
dbp = new Db(&dbenv, 0);
|
||
|
||
try {
|
||
dbp->open(NULL, DATABASE, NULL, DB_BTREE,
|
||
DB_CREATE | DB_AUTO_COMMIT, 0);
|
||
} catch(DbException dbe) {
|
||
dbenv.err(ret, "DB->open");
|
||
throw dbe;
|
||
}
|
||
} </pre>
|
||
<p>
|
||
Now we implement our command prompt. This is a simple and not
|
||
very robust implementation of a command prompt.
|
||
If the user enters the keywords <code class="literal">exit</code>
|
||
or <code class="literal">quit</code>, the loop is exited and the
|
||
application ends. If the user enters nothing and instead simply
|
||
presses <code class="literal">return</code>, the entire contents of the
|
||
database is displayed. We use our
|
||
<code class="function">print_stocks()</code> method to display the
|
||
database. (That implementation is shown next in this chapter.)
|
||
</p>
|
||
<p>
|
||
Notice that very little error checking is performed on the data
|
||
entered at this prompt. If the user fails to enter at least one
|
||
space in the value string, a simple help message is printed and
|
||
the prompt is returned to the user. That is the only error
|
||
checking performed here. In a real-world application,
|
||
at a minimum the application would probably check to ensure
|
||
that the price was in fact an integer or float value.
|
||
However, in order to keep this example code as simple as
|
||
possible, we refrain from implementing a thorough user interface.
|
||
</p>
|
||
<pre class="programlisting"> cout << "QUOTESERVER" ;
|
||
cout << "> " << flush;
|
||
|
||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||
break;
|
||
if (strtok(&buf[0], " \t\n") == NULL) {
|
||
switch ((ret = print_stocks(dbp))) {
|
||
case 0:
|
||
continue;
|
||
default:
|
||
dbp->err(ret, "Error traversing data");
|
||
goto err;
|
||
}
|
||
}
|
||
rbuf = strtok(NULL, " \t\n");
|
||
if (rbuf == NULL || rbuf[0] == '\0') {
|
||
if (strncmp(buf, "exit", 4) == 0 ||
|
||
strncmp(buf, "quit", 4) == 0)
|
||
break;
|
||
dbenv.errx("Format: TICKER VALUE");
|
||
continue;
|
||
} </pre>
|
||
<p>
|
||
Now we assign data to the <code class="classname">Dbt</code>s that
|
||
we will use to write the new information to the database.
|
||
</p>
|
||
<pre class="programlisting"> key.set_data(buf);
|
||
key.set_size((u_int32_t)strlen(buf));
|
||
|
||
data.set_data(rbuf);
|
||
data.set_size((u_int32_t)strlen(rbuf)); </pre>
|
||
<p>
|
||
Having done that, we can write the new information to the
|
||
database. Remember that this application uses autocommit,
|
||
so no explicit transaction management is required. Also,
|
||
the database is not configured for duplicate records, so
|
||
the data portion of a record is overwritten if the provided
|
||
key already exists in the database. However, in this case
|
||
DB returns <code class="literal">DB_KEYEXIST</code> — which
|
||
we ignore.
|
||
</p>
|
||
<pre class="programlisting"> if ((ret = dbp->put(NULL, &key, &data, 0)) != 0)
|
||
{
|
||
dbp->err(ret, "DB->put");
|
||
if (ret != DB_KEYEXIST)
|
||
goto err;
|
||
}
|
||
} </pre>
|
||
<p>
|
||
Finally, we close our database before returning from the
|
||
method.
|
||
</p>
|
||
<pre class="programlisting">err: if (dbp != NULL) {
|
||
(void)dbp->close(DB_NOSYNC);
|
||
cout << "database closed" << endl;
|
||
}
|
||
|
||
return (ret);
|
||
} </pre>
|
||
</div>
|
||
<div class="sect2" lang="en" xml:lang="en">
|
||
<div class="titlepage">
|
||
<div>
|
||
<div>
|
||
<h3 class="title"><a id="printstocks_c"></a>
|
||
|
||
<span>Method: SimpleTxn::print_stocks()</span>
|
||
|
||
</h3>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>
|
||
The <code class="function">print_stocks()</code>
|
||
|
||
|
||
<span>method</span>
|
||
simply takes a database handle, opens a cursor, and uses
|
||
it to display all the information it finds in a database.
|
||
This is trivial cursor operation that should hold
|
||
no surprises for you. We simply provide it here for
|
||
the sake of completeness.
|
||
</p>
|
||
<p>
|
||
If you are unfamiliar with basic cursor operations,
|
||
please see the <em class="citetitle">Getting Started with Berkeley DB</em>
|
||
guide.
|
||
</p>
|
||
<pre class="programlisting">int SimpleTxn::print_stocks(Db *dbp)
|
||
{
|
||
Dbc *dbc;
|
||
Dbt key, data;
|
||
#define MAXKEYSIZE 10
|
||
#define MAXDATASIZE 20
|
||
char keybuf[MAXKEYSIZE + 1], databuf[MAXDATASIZE + 1];
|
||
int ret, t_ret;
|
||
u_int32_t keysize, datasize;
|
||
|
||
if ((ret = dbp->cursor(NULL, &dbc, 0)) != 0) {
|
||
dbp->err(ret, "can't open cursor");
|
||
return (ret);
|
||
}
|
||
|
||
memset(&key, 0, sizeof(key));
|
||
memset(&data, 0, sizeof(data));
|
||
|
||
cout << "\tSymbol\tPrice" << endl
|
||
<< "\t======\t=====" << endl;
|
||
|
||
for (ret = dbc->get(&key, &data, DB_FIRST);
|
||
ret == 0;
|
||
ret = dbc->get(&key, &data, DB_NEXT)) {
|
||
keysize = key.get_size() > MAXKEYSIZE ? MAXKEYSIZE :
|
||
key.get_size();
|
||
memcpy(keybuf, key.get_data(), keysize);
|
||
keybuf[keysize] = '\0';
|
||
|
||
datasize = data.get_size() >=
|
||
MAXDATASIZE ? MAXDATASIZE : data.get_size();
|
||
memcpy(databuf, data.get_data(), datasize);
|
||
databuf[datasize] = '\0';
|
||
|
||
cout << "\t" << keybuf << "\t" << databuf << endl;
|
||
}
|
||
cout << endl << flush;
|
||
|
||
if ((t_ret = dbc->close()) != 0 && ret == 0) {
|
||
cout << "closed cursor" << endl;
|
||
ret = t_ret;
|
||
}
|
||
|
||
switch (ret) {
|
||
case 0:
|
||
case DB_NOTFOUND:
|
||
return (0);
|
||
default:
|
||
return (ret);
|
||
}
|
||
} </pre>
|
||
</div>
|
||
</div>
|
||
<div class="navfooter">
|
||
<hr />
|
||
<table width="100%" summary="Navigation footer">
|
||
<tr>
|
||
<td width="40%" align="left"><a accesskey="p" href="txnapp.html">Prev</a> </td>
|
||
<td width="20%" align="center">
|
||
<a accesskey="u" href="txnapp.html">Up</a>
|
||
</td>
|
||
<td width="40%" align="right"> <a accesskey="n" href="repapp.html">Next</a></td>
|
||
</tr>
|
||
<tr>
|
||
<td width="40%" align="left" valign="top">Chapter 2. Transactional Application </td>
|
||
<td width="20%" align="center">
|
||
<a accesskey="h" href="index.html">Home</a>
|
||
</td>
|
||
<td width="40%" align="right" valign="top"> Chapter 3. The DB Replication Manager</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</body>
|
||
</html>
|