1. 数据库记录
DBT对象提供一个void *数据类型的成员函数指向您的数据,并使用另一成员函数标识数据的长度。因此,它们可以被用来存储任何东西,从简单的原始数据,复杂的类对象,只要你想要的信息存储驻留在一个单一的连续的内存块。
本章介绍DBT使用。它还引入了从数据库中存储和检索的键/值对。
1.1. 使用数据库记录
#include <db_cxx.h>
#include <string.h>
...
float money = 122.45;
char *description = "Grocery bill.";
Dbt key(&money, sizeof(float));
Dbt data(description, strlen(description)+1);
请注意,在下面的例子中我们不允许DB为money的值分配空间。因为,有些系统可能要求浮点值,有一个特定的对齐,而DB的内存可能无法正确对齐(可能存在同样的问题在一些系统上的结构)。我们通过指定DB_DBT_USERMEM标志,告诉DB使用我们的现有的内存,而不是自己分配内存。要知道,当我们这样做时,我们还必须确定多少用户内存,可通过使用ulen字段设置。
#include <db_cxx.h>
#include <string.h>
...
Dbt key, data;
float money;
char *description;
key.set_data(&money);
key.set_ulen(sizeof(float));
key.set_flags(DB_DBT_USERMEM);
// Database retrieval code goes here
// Money is set into the memory that we supplied.
description = (char *)data.get_data();
1.2. 读、写作数据库记录
当读、写数据库中的记录时,有是一些轻微的差异,由你的数据库是否支持重复的记录而定。数据库中两个或更多的记录项的键相同,则被认为彼此重复。重复记录的可通过duplicates set方法统计,在DB中,在单一的的重复的集,一个给定的的键被只存储一次。
通过默认情况下,DB数据库的不支持重复的记录。支持重复记录时,通常使用游标(见下文)以访问所有的记录中重复的集合。
DB提供了两种基本机制存储和检索的数据库key/data数据对:
•Db::put() 和 Db::get()方法提供最简单的访问数据库中的所有非重复记录。这些方法都本节中描述。
•游标提供多种方式读、写数据库中记录。游标以及游标的数据访问方法查看“游标使用”章节。
1.2.1. 写数据库
记录存储,通过使用您所选择的访问方法存储数据库中。在某些记录存储的排序顺序,你可以定义,如(BTree)(更多信息,请参阅设置比较函数)。
在任何情况下,一旦你已经配置您的排序方式(如果有的话),并打开您的数据库,选择访问方法,则读、写数据库记录机制不可改变。从代码的角度来看,一个简单的数据库读写操作,无论你使用的是什么样的访问方法,很大程度上是相同的。
您可以使用Db::put()放入,或写一个数据库记录。此方法求要你提供一对DBT类型的KEY和VALUE。您也可以提供一个或多个标志,控制DB数据库的读写行为。
所有的标志中,DB_NOOVERWRITE可能会引起您的兴趣。此标志不允许覆盖(替换)现有数据库中的记录。如果所提供的键值已经存在于数据库中,则此方法返回DB_KEYEXIST,即使数据库支持重复。
例如:
#include <db_cxx.h>
#include <string.h>
...
char *description = "Grocery bill.";
float money = 122.45;
Db my_database(NULL, 0);
// Database open omitted for clarity
Dbt key(&money, sizeof(float));
Dbt data(description, strlen(description) + 1);
int ret = my_database.put(NULL, &key, &data,DB_NOOVERWRITE);
if (ret == DB_KEYEXIST) {
my_database.err(ret, "Put failed because key %f alreadyexists", money);
}
1.2.2. 读数据库
您可以使用DB:: get()方法来检索数据库中的记录。请注意,如果你的数据库支持重复记录,则默认情况下,这个方法只会返回第一条记录的复制。出于这个原因,如果你的数据库支持重复的,常见的解决方案是使用游标检索记录。游标使用详见“使用游标”。
(你也可以使用get检索一组重复的记录,在调用Db::get()方法时设置DB_MULTIPLE标志。有关详细信息,请参阅DB程序员参考手册(DB Programmer's Reference Guide))。
默认情况下,Db::get()方法返回与指定的KEY相吻合的第一条记录。如果你的数据库支持重复的记录,你可以通过设置DB_GET_BOTH标志改变这种行为。此标志将导致DB::get()返回KEY和VALUE都匹配的第一条记录。
如果指定的KEY或KEY和VALUE在数据库中不存在,这个方法返回DB_NOTFOUND。
#include <db_cxx.h>
#include <string.h>
...
float money;
char *description[DESCRIPTION_SIZE + 1];
Db my_database(NULL, 0);
// Database open omitted for clarity
money = 122.45;
Dbt key, data;
key.set_data(&money);
key.set_ulen(sizeof(float));
data.set_data(&description);
data.set_ulen(DESCRIPTION_SIZE + 1);
data.set_flags(DB_DBT_USERMEM);
my_database.get(NULL, &key, &data, 0);
// Description is set into the memory that we supplied.
1.2.3. 删除记录
您可以使用Db::del()方法从数据库中删除一条记录。如果你的数据库支持重复的记录,则删除所有相同键值的记录。从重复列表中删除一条记录,则使用游标。游标描述查看“使游标用”章节。
您也可以通过调用Db::truncate()删除数据库中的每一条记录。
#include <db_cxx.h>
...
Db my_database(NULL, 0);
// Database open omitted for clarity
float money = 122.45;
Dbt key(&money, sizeof(float));
my_database.del(NULL, &key, 0);
1.2.4. 数据持久化
当您执行修改数据库时,您的修改存放在内存缓存中。这意味着,数据修改不一定刷新到磁盘,使您的数据在应用程序后重新启动后,可能不会出现在数据库中的。
请注意,把高速缓存数据写入到磁盘,是关闭数据库的正常方式。然而,在应用程序或系统故障的情况下,不能保证数据库正常关闭。在这种情况下,它有可能为你丢失数据。在极为罕见的情况下,它也可以让你数据库损坏。
因此,如果你关心您的数据是对系统故障是健壮,并且要防范罕见的数据库损坏,你应该使用的事务,以保证数据库的修改。每次提交一个事务,即使应用程序或系统故障,DB都会确保数据不会丢失。事务的用法,详见Berkeley DB的事务处理指南入门(Berkeley DB Getting Started with Transaction Processing guide)。
如果你不想使用事务,那么假定:你数据数据在下一次应用程序启动时是不需要存在的。例如,如果你正在使用DB相关的缓存数据只对当前的应用程序运行。
然而,出于某种原因,您不使用事务,你仍然需要保证您的一些的数据库修改是持久的,那么你应该定期调用Db::sync()。同步造成任何内存缓存和操作系统文件中的脏数据写入磁盘(未修改的数据项再次写磁盘)。因此,他们是相当昂贵的,你应该有节制地使用它们。
请记住,默认情况下,任何非事务性数据库正常关闭时,会执行同步同步操作。(可以覆盖此行为通过在调用指定DB_NOSYNC的DB:: close()方法。)这就是说,你可以手动运行同步调用Db::sync()。
注意:如果您的应用程序或系统崩溃,你没有使用事务,那么你应该放弃和重新创建数据库,或验证。您可以验证一个数据库,使用Db::verify()。如果你的数据库验证不干净,您可以使用中的db_dump命令挽救尽可能多的数据。使用-R或-r命令行选项来控制db_dump如何打捞你的数据库。
1.3. 数据库用法示例
在数据库的例子中,我们创建了一个类,为我们打开和关闭数据库。我们现在可以利用这个类的库存数据加载到两个数据库中,我们将使用我们的库存系统。
再次,请记住,你可以找到这些功能的完整实现:DB_INSTALL/examples_cxx/ getting_started,其中DB_INSTALL DB分布的放置位置。
1.3.1. 示例3.1供应商结构
我们想存储有关数据到库存系统。我们有两种类型的信息要管理:库存数据和相关的供应商联系信息。为管理这些信息,我们为每个数据创建一个数据结构。
我们现在定义VENDOR结构。需要注意的是,VENDOR结构采用固定长度的字段。这是没有必要的,实际上如果供应商的在数据库中存储数规模非常大,是一种资源浪费的表现。然而,为了简单起见,我们使用固定长度的字段,无论如何,尤其是考虑到我们的样本数据只包含这么几个供应商记录。
// File: gettingStartedCommon.hpp
#define MAXFIELD 20
typedef struct vendor {
charname[MAXFIELD]; // Vendorname
charstreet[MAXFIELD]; // Streetname and number
charcity[MAXFIELD]; // City
charstate[3]; // Two-digit US state code
charzipcode[6]; // US zipcode
charphone_number[13]; // Vendorphone number
charsales_rep[MAXFIELD]; // Name ofsales representative
charsales_rep_phone[MAXFIELD]; // Salesrep's phone number
} VENDOR;
1.3.2. 示例3.2 InventoryData类
为了管理我们的实际库存数据,我们创建了一个类,它封装了我们要存储的每个库存记录。除了简单的数据封装,这个类也能封装库存数据成一个单一的连续的缓冲区存储到DB数据库中。
我们也为这个类提供了两个构造函数。简单的默认构造函数初始化我们所有的数据成员。还提供了第二个构造函数是能够从一个void *填充我们的数据成员。这第二个构造函数是不是真的需要直到下一章我们将展示如何从数据库中读取数据,我们包括在这里是为了完整性的目的。
把事情简单化了一下,我们这个类(gettingStartedCommon.hpp)包括整个实施,及供应商结构定义。
首先,我们创建public的方法:getter和setter方法。我们还将定义所有的私有成员的初始化。
class InventoryData
{
public:
inline voidsetPrice(double price) {price_ = price;}
inline voidsetQuantity(long quantity) {quantity_ = quantity;}
inline voidsetCategory(std::string &category) {category_ = category;}
inline voidsetName(std::string &name) {name_ = name;}
inline voidsetVendor(std::string &vendor) {vendor_ = vendor;}
inline voidsetSKU(std::string &sku) {sku_ = sku;}
inlinedouble& getPrice() {return(price_);}
inlinelong& getQuantity() {return(quantity_);}
inlinestd::string& getCategory() {return(category_);}
inlinestd::string& getName() {return(name_);}
inlinestd::string& getVendor() {return(vendor_);}
inlinestd::string& getSKU() {return(sku_);}
// Initializeour data members
void clear()
{
price_ =0.0;
quantity_ =0;
category_.clear();
name_.clear();
vendor_.clear();
sku_.clear();
}
下一步,我们实现我们的构造函数。默认构造函数简单地调用clear()。第二个构造函数一个void *作为参数,然后用它来初始化数据成员。再次,请注意,本章节我们不会实际使用第二个构造函数,在这里只为了显示是完整性。
// Default constructor
InventoryData(){ clear(); }
// Constructorfrom a void *
// For use withthe data returned from a bdb get
InventoryData(void *buffer)
{
char *buf =(char *)buffer;
price_ =*((double *)buf);
bufLen_ =sizeof(double);
quantity_ =*((long *)(buf + bufLen_));
bufLen_ +=sizeof(long);
name_ = buf+ bufLen_;
bufLen_ +=name_.size() + 1;
sku_ = buf+ bufLen_;
bufLen_ +=sku_.size() + 1;
category_ =buf + bufLen_;
bufLen_ +=category_.size() + 1;
vendor_ =buf + bufLen_;
bufLen_ +=vendor_.size() + 1;
}
接下来,我们提供了几个方法返回的类的缓冲区和缓冲区大小。这些都是用于存储到DB数据库的数据。
// Marshalls this classes data members into a single
// contiguous memory location for the purpose of storing
// the data in a database.
char *
getBuffer()
{
// Zero out the buffer
memset(databuf_, 0, 500);
// Now pack the data into a single contiguous memory location for
// storage.
bufLen_ = 0;
int dataLen = 0;
dataLen = sizeof(double);
memcpy(databuf_, &price_, dataLen);
bufLen_ += dataLen;
dataLen = sizeof(long);
memcpy(databuf_ + bufLen_, &quantity_, dataLen);
bufLen_ += dataLen;
packString(databuf_, name_);
packString(databuf_, sku_);
packString(databuf_, category_);
packString(databuf_, vendor_);
return (databuf_);
}
// Returns the size of the buffer. Used for storing
// the buffer in a database.
inline int getBufferSize() { return (bufLen_); }
我们最后一个public方法是一个实用的方法,用于展示数据。
// Utility function used to show the contents of this class
void
show() {
std::cout << "\nName: " << name_ << std::endl;
std::cout << " SKU: " << sku_ << std::endl;
std::cout << " Price: " << price_ << std::endl;
std::cout << " Quantity: " << quantity_ << std::endl;
std::cout << " Category: " << category_ << std::endl;
std::cout << " Vendor: " << vendor_ << std::endl;
}
最后,我们提供了一个私有方法,是用来把我们的数据打包到我们的缓冲区,我们定义私有数据成员:
private:
// Utilityfunction that appends a char * to the end of
// the buffer.
void
packString(char*buffer, std::string &theString)
{
intstring_size = theString.size() + 1;
memcpy(buffer+bufLen_, theString.c_str(),string_size);
bufLen_ +=string_size;
}
// Data members
std::stringcategory_, name_, vendor_, sku_;
double price_;
long quantity_;
int bufLen_;
chardatabuf_[500];
};
1.3.3. 示例3.3 example_database_load
我们最初的样本应用程序从多个文件加载数据库信息。为了节省空间,我们将不显示这个例子程序的所有细节。然而,你可以找到这个程序的完整实现:DB_INSTALL/ examples_cxx/ getting_started,其中DB_INSTALL DB分布的放置位置。
我们开始与正常include指令和前瞻性声明:
// File: example_database_load.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "MyDb.hpp"
#include "gettingStartedCommon.hpp"
// Forward declarations
void loadVendorDB(MyDb&, std::string&);
void loadInventoryDB(MyDb&, std::string&);
接下来,我们开始我们的main()函数中的变量声明:
// Loads the contents of vendors.txt and inventory.txtinto
// Berkeley DB databases.
int
main(int argc, char *argv[])
{
// Initializethe path to the database files
std::stringbasename("./");
std::stringdatabaseHome("./");
// Databasenames
std::stringvDbName("vendordb.db");
std::stringiDbName("inventorydb.db");
// Parse thecommand line arguments here and determine
// the locationof the flat text files containing the
// inventorydata here. This step is omitted for clarity.
// Identify the full name for our input files,which should
// also include some path information.
std::stringinventoryFile = basename + "inventory.txt";
std::stringvendorFile = basename + "vendors.txt";
try
{
// Open alldatabases.
MyDbinventoryDB(databaseHome, iDbName);
MyDbvendorDB(databaseHome, vDbName);
// Load thevendor database
loadVendorDB(vendorDB, vendorFile);
// Load theinventory database
loadInventoryDB(inventoryDB, inventoryFile);
}catch(DbException &e) {
std::cerr<< "Error loading databases. " << std::endl;
std::cerr<< e.what() << std::endl;
return(e.get_errno());
}catch(std::exception &e) {
std::cerr<< "Error loading databases. " << std::endl;
std::cerr<< e.what() << std::endl;
return(-1);
}
return(0);
} // End main
请注意,我们不明确地关闭我们的数据库。这是因为数据库被封装在的MYDB类对象中,这些对象是在堆栈中分配。当释放堆栈信息时,会自动调用其析构函数,关闭数据库。
请注意,有此功能不是很多,因为我们已经推掉所有的数据库活动到其他地方。
接下来,实施loadVendorDB()。我们通过扫描vendors.txt文件的内容(一行一行)加载当前数据成VENDOR结构的。一旦我们扫描有数据,我们可以存储到供应商数据库结构。
注意,我们使用这里的关键供应商的名称。在这样做时,我们假设我们的数据库中,供应商的名称是唯一的。如果不是这样,我们要么选择一个不同的密钥,或创建一个支持多相同供应商的记录的数据库。
// Loads the contents of the vendors.txt file into adatabase
void
loadVendorDB(MyDb &vendorDB, std::string&vendorFile)
{
std::ifstreaminFile(vendorFile.c_str(), std::ios::in);
if ( !inFile )
{
std::cerr<< "Could not open file '" << vendorFile
<< "'. Giving up."<< std::endl;
throwstd::exception();
}
VENDORmy_vendor;
while (!inFile.eof())
{
std::stringstringBuf;
std::getline(inFile, stringBuf);
memset(&my_vendor, 0, sizeof(VENDOR));
// Scan theline into the structure.
//Convenient, but not particularly safe.
// In areal program, there would be a lot more
//defensive code here.
sscanf(stringBuf.c_str(),
"%20[^#]#%20[^#]#%20[^#]#%3[^#]#%6[^#]#%13[^#]#%20[^#]#%20[^\n]",
my_vendor.name, my_vendor.street,
my_vendor.city, my_vendor.state,
my_vendor.zipcode, my_vendor.phone_number,
my_vendor.sales_rep, my_vendor.sales_rep_phone);
Dbtkey(my_vendor.name, strlen(my_vendor.name) + 1);
Dbtdata(&my_vendor, sizeof(VENDOR));
vendorDB.getDb().put(NULL, &key, &data, 0);
}
inFile.close();
}
最后,我们需要写的loadInventoryDB()函数,加载库存信息。读取inventory.txt文件的每一行,取得每个字段,然后我们将这些数据转换成一个InventoryData实例。
为了帮助我们从输入和每一行获取了各个字段,我们还创建了一个简单的辅助功能,输入行每个字段间分隔符为:#。
需要注意的是,我们可以通过类似我们使用上面的VENDOR(字段取固定长度),简单的类型存储库存数据。然而,通过InventoryData存储这些数据,自己识别数据的大小,我们可有使用最小的空间存储数据,其结果是我们缓存可以更小,消耗的磁盘空间比使用固定长度的字段结构占更小。
一个简单的数据集,如我们所使用的这些例子,节约这些资源是可以忽略不计。但是,如果我们几千万条记录存储,节约成本可能会变得显着。
// Used to locate the first pound sign (a fielddelimiter)
// in the input string.
int
getNextPound(std::string &theString, std::string&substring)
{
int pos =theString.find("#");
substring.assign(theString, 0, pos);
theString.assign(theString, pos + 1,theString.size());
return (pos);
}
// Loads the contents of the inventory.txt file into adatabase
void
loadInventoryDB(MyDb &inventoryDB, std::string&inventoryFile)
{
InventoryDatainventoryData;
std::string substring;
int nextPound;
std::ifstreaminFile(inventoryFile.c_str(), std::ios::in);
if (!inFile)
{
std::cerr<< "Could not open file '" << inventoryFile
<< "'. Giving up." << std::endl;
throwstd::exception();
}
while(!inFile.eof())
{
inventoryData.clear();
std::stringstringBuf;
std::getline(inFile, stringBuf);
// Nowparse the line
if(!stringBuf.empty())
{
nextPound = getNextPound(stringBuf, substring);
inventoryData.setName(substring);
nextPound = getNextPound(stringBuf, substring);
inventoryData.setSKU(substring);
nextPound = getNextPound(stringBuf, substring);
inventoryData.setPrice(strtod(substring.c_str(), 0));
nextPound = getNextPound(stringBuf, substring);
inventoryData.setQuantity(strtol(substring.c_str(), 0, 10));
nextPound = getNextPound(stringBuf, substring);
inventoryData.setCategory(substring);
nextPound = getNextPound(stringBuf, substring);
inventoryData.setVendor(substring);
void*buff = (void *)inventoryData.getSKU().c_str();
intsize = inventoryData.getSKU().size()+1;
Dbtkey(buff, size);
buff =inventoryData.getBuffer();
size =inventoryData.getBufferSize();
Dbtdata(buff, size);
inventoryDB.getDb().put(NULL, &key, &data, 0);
}
}
inFile.close();
}
在下一章中,我们提供了一个例子,说明如何阅读的库存和供应商数据库。