使用IBPP在C++中操作FireBird/Interbase数据库

FireBird是一种小巧的关系型数据库,它有多种版本,包括服务器版(像MySQL),单机版(像Access)以及嵌入式(像SQLite)。而且不管是服务器版还是嵌入式版它都完整支持视图、触发器、存储过程等SQL高级特性。

问题是它提供的C API “不太友好”,不容易掌握(特别是我),所以我一直只会在C++Builder里编程使用FireBird(因为FireBird源于Borland的InterBase,VCL库自带了InterBase的组件),直到有一天我发现了IBPP…

IBPP是一个FireBird/Interbase数据库API的C++包装,使用起来也比较简单,只要把IBPP源码目录里的core/all_in_one.cpp加入工程,在代码中包含头文件ibpp.h,最后根据操作系统预定义一个IBPP_WINDOWSIBPP_UNIX的宏即可。

另外,当然还要安装FireBird客户端,如果只是学习,我们可以下载嵌入式版本。IBPP首先要找到FireBird的动态库,在Windows下, IBPP在下面的路径中查找:

  1. 在程序目录下查找fbembed.dll
  2. 在程序目录下查找fbclient.dll
  3. 依据DefaultInstance注册表键值查找fbclient.dll
  4. 在系统定义位置查找fbclient.dll
  5. 在系统定义位置查找gds32.dll

FireBird主页:http://www.ibphoenix.com
IBPP主页:http://www.ibpp.org

关于FireBird的中文教程我推荐姚启红编著的《Borland InterBase7.0 应用开发指南》(网上一找一大堆)。

基本应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <ibpp.h>
using namespace std;

//数据库名,根据你的情况更改。
const char* g_szDBName = "d:\\test.fdb";
//服务器名,针对服务器版本。对于嵌入式,它应该是""
const char* g_szServerName = "";

//这里的用户名和密码是FireBird默认值,对于服务器版,用你自己的密码
//对于嵌入式,就是这个(FireBird嵌入式版没有加密功能)。
const char* g_szUserName = "SYSDBA";
const char* g_szPassword = "masterkey";

int main()
{
IBPP::Database db = IBPP::DatabaseFactory(g_szServerName,
g_szDBName,
g_szUserName,
g_szPassword);
//建立数据库
db->Create(3);
//连接数据库
db->Connect();

IBPP::Transaction tr = IBPP::TransactionFactory(db);
tr->Start();
try{
IBPP::Statement st = IBPP::StatementFactory(db,tr);
//建立数据表
st->Execute(
"CREATE TABLE TESTTABLE("
" ID INTEGER NOT NULL PRIMARY KEY,"
" RNO VARCHAR(10) NOT NULL UNIQUE,"
" SHIFT VARCHAR(5) NOT NULL"
" CHECK(SHIFT IN('A','B','C','D')),"
" LINE CHAR(20) NOT NULL,"
" SL COMPUTED BY(SHIFT||'.'||LINE),"
" EMP CHAR(20)"
" )"
);
tr->CommitRetain();
//插入数据,华安和小强
st->Execute(
"INSERT INTO TESTTABLE(ID,RNO,SHIFT,LINE,EMP)"
"VALUES(1,'B9527','B','DAZHA','Hua,An')"
);
st->Execute(
"INSERT INTO TESTTABLE(ID,RNO,SHIFT,LINE,EMP)"
"VALUES(2,'B9528','B','ZHUANGSI','Xiao,Qiang')"
);

tr->CommitRetain();
//插入数据,石榴和祝枝山
st->Execute(
"INSERT INTO TESTTABLE(ID,RNO,SHIFT,LINE,EMP)"
"VALUES(3,'B9525','A','DAZHA','Shi,Liu')"
);
//SHIFT只能是ABCD,看看写X会有什么情况发生
st->Execute(
"INSERT INTO TESTTABLE(ID,RNO,SHIFT,LINE,EMP)"
"VALUES(4,'B9526','X','DAZHA','Zhu,ZiShang')"
);
tr->Commit();
}
catch(IBPP::SQLException &e){
cerr << e.what() << endl;
tr->Rollback();
}

tr->Start();
try{
IBPP::Statement st = IBPP::StatementFactory(db,tr);
st->Execute("SELECT RNO, EMP FROM TESTTABLE");
//显示SELECT得到的数据
while(st->Fetch())
{
string rno, emp;
st->Get("RNO",rno);
st->Get("EMP",emp);
cout << "RNO:" << rno << " EMP:" << emp << endl;
}
tr->Commit();
}
catch(IBPP::SQLException &e){
cerr << e.what() << endl;
tr->Rollback();
}
}

要让程序正确执行,就得保证程序能找到FireBird的动态链接库。对于嵌入式版本来说,下载后把压缩包中的fbembed.dll, firebird.conf,firebird.msg,ib_util.dll以及intl文件夹解压到你的程序目录中就可以运行了。对于服务器版,只要安装客户端并把fbclient.dll放到程序目录里就行(当然权限也要有啦)。

上例中使用IBPP的步骤是:

  1. 用工厂函数IBPP::DatabaseFactory生成IBPP::Database实例,然后调用它的Connect()成员变量连接数据库。
  2. 如果要建立数据库的话,使用Database::Create(int dialect)。其中dialect一般取值为3,它的含义如下:
含义
1 兼容InterBase 5.5及更早版本
2 1和3的过渡版本
3 InterBase 6.0及以后版本,可以使用分隔符,精确数字和时间格式
  1. 使用工厂函数IBPP::TransactionFactory生成IBPP::Transaction实例,然后用它的Start()开始事务,最后用Commit()来提交事务或Rollback()撤销事务。
  2. 使用工厂函数IBPP::StatementFactory生成IBPP::Statement实例,然后用它的Execute()来执行SQL命令。
  3. 值得注意的是,IBPP中的Database,Transaction,Statement等由工厂函数返回的数据都是智能指针类型,它们能够在退出有效域 时自动执行一些保证完整性的操作。(比如我们使用tr->Start()后即便不调用tr->Commit(),在超出tr变量作用域后系统 也会自动调用Commit的)

操作Blob型字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <iostream>
#include <algorithm>
#include <assert.h>
#include <ibpp.h>
using namespace std;

//数据库名,根据你的情况更改。
const char* g_szDBName = "d:\\test.fdb";
//服务器名,针对服务器版本。对于嵌入式,它应该是""
const char* g_szServerName = "";

//这里的用户名和密码是FireBird默认值,对于服务器版,用你自己的密码
//对于嵌入式,就是这个(FireBird嵌入式版没有加密功能)。
const char* g_szUserName = "SYSDBA";
const char* g_szPassword = "masterkey";

int main()
{
IBPP::Database db;
db = IBPP::DatabaseFactory(g_szServerName,
g_szDBName,
g_szUserName,
g_szPassword);
//连接数据库
db->Connect();

IBPP::Transaction tr = IBPP::TransactionFactory(db);
tr->Start();
try{
IBPP::Statement st = IBPP::StatementFactory(db,tr);
//建立数据表
st->ExecuteImmediate(
"CREATE TABLE BlobTable("
" ID INTEGER NOT NULL,"
" RES BLOB)"
);
//生成器
st->ExecuteImmediate(
"CREATE GENERATOR BlobTable_ID_Gen"
);
//触发器
st->ExecuteImmediate(
"CREATE TRIGGER BlobTable_BI_ID FOR BlobTable "
"ACTIVE BEFORE INSERT POSITION 0 "
"AS "
"BEGIN "
" IF(NEW.ID IS NULL) THEN "
" NEW.ID = GEN_ID(BlobTable_ID_Gen, 1); "
"END"
);
tr->CommitRetain();

st->Prepare("INSERT INTO BlobTable(RES) VALUES(?)");
assert(1 == st->Parameters());
//插入Blob,一块100字节的内存
{
IBPP::Blob res = IBPP::BlobFactory(db,tr);
res->Create();
char buf[100];
for(int i=0; i<100; i++) buf[i]=i;
res->Write(buf,sizeof(buf));
res->Close();
st->Set(1,res); //第一个问号
st->Execute();
}
//插入Blob,一串字符串
{
st->Set(1,string("HAHAHAHAHAHA..."));
st->Execute();
}
tr->Commit();
}
catch(IBPP::SQLException &e){
cerr << e.what() << endl;
tr->Rollback();
}

tr->Start();
try{
IBPP::Statement st = IBPP::StatementFactory(db,tr);
st->Execute("SELECT * FROM BlobTable");
//显示SELECT得到的数据
while(st->Fetch())
{
int id;
IBPP::Blob res = IBPP::BlobFactory(db,tr);

st->Get(1,id);
st->Get(2,res);

//显示Blob内容
res->Open();
int size;
res->Info(&size,NULL,NULL);
cout << id << " size is " << size << endl;
char c;
while(res->Read(&c,1))
{
cout << (int)c << ' ';
}
cout << endl;
}
tr->Commit();
}
catch(IBPP::SQLException &e){
cerr << e.what() << endl;
tr->Rollback();
}
}

本例连接例一建立的数据库,然后新建一个名为BlobTable的表,里面只有IDRES字段,而且还为ID字段建立了一个生成器和触发器,这样就可以 让FireBird自动为ID赋值了,具体内容可以看FireBird教程。与例一不同的是这里使用了IBPP::StatementExecuteImmediate()方法,它立即执行其中的SQL语句,而Execute()则有一个先准备、再执行的过程,对于只执行一次的SQL, 可以使用ExecuteImmediate(),如果执行次数较多,则建议使用Execute()

RES字段是一个Blob型字段,从本例可以看出给Blob字段赋值的方法是:

  1. IBPP::StatementPrepare()方法准备SQL插入语句,其中的Blob字段使用问号?代替。
  2. 用工厂函数IBPP::BlobFactory生成一个IBPP::Blob实例,然后分别调用它的Create(),Write()Close()输入数据,最后用IBPP::StatementSet()方法写入。
  3. 所有数据准备就绪后,执行IBPP::Statement的Execute()方法。

如果只想往Blob中存放文本数据,也可以直接用IBPP::StatementSet()方法写入std::string类型。

Blob字段取值的方法正好相反:

  1. IBPP::StatementExecute()方法或ExecuteImmediate()方法执行SELECT语句。
  2. 使用IBPP::StatementFetch()方法提取出当前行的数据。
  3. 使用IBPP::StatementGet()方法取出指定列的数据,如果是Blob类型,则使用IBPP::Blob实例作为输入。
  4. 最后分别调用IBPP::BlobOpen(),Read()Close()方法取出数据(IBPP::Blob也是一个智能指针类型,所以上例中没有显式地使用Close()关闭)。