一个简单的例子
POCO
Data提供了一个抽象的数据层,以供用户方便的与不同的数据库交互,以下是一个完整的实例:
[code]
#include
"Poco/Data/Common.h"
#include
"Poco/Data/SQLite/Connector.h"
#include
using namespace
Poco::Data;
void init()
{
SQLite::Connector::registerConnector();
}
void shutdown()
{
SQLite::Connector::unregisterConnector();
}
int main(int argc, char*
argv[])
{
init();
Session ses("SQLite", "sample.db");
int count = 0;
ses << "SELECT COUNT(*) FROM PERSON",
into(count), now;
std::cout << "People in DB " <<
count;
shutdown();
}
[/code]
以上例子非常清晰明了,Poco/Data/Common.h包含了一些常用的数据库相关的引用,使用SQLite::Connector注册SQLite连接器后,我们可以通过SessionFactory创建SQLite会话。Session的构造函数包括两个参数:
[code]
Sesssion ses("SQLite",
"sample.db");
[/code]
与以下用法等价:
[code]
Session ses(SessionFactory::instance()::create("SQLite",
"sample.db"));
[/code]
操 作符<
statements给当前session,into(count)用于将SQL的查询结果保存到count变量中。在SQL
Statement后面城需以now结尾,否则SQL语句不会立即执行。为便于代码阅读,可以使用时带上Poco::Data命令空间。(例如:ses
<< "SELECT COUNT(*) FROM PERSON", Poco::Data::into(count),
Poco::Data::now; ) (http://www.libpoco.com翻译,转载请注明!)
以下手册分这几部分进行介绍:
a.
创建会话(session)
b. 从DB中读写数据(神奇的into,
use)
c. 使用statements
d.
使用容器(Collection) (数据,集合...)
e. 使用limit限定
f.
如何使用复杂的数据类型(如何将一个C++对象映射到数据库的表
创建会话
通常都是通过会话工厂(SessionFactory)的create方法来创建会话(session),
或通过带两个参数的session构造函数来创建。
Session create(const std::string&
connectorKey, const std::string&
connectionString);
第 一个参数用于指定数据库类型,其中POCO内置SQLite支持,
可通过ODBC驱动支持Oracle, SQLite, DB2,
SQLServer和PostgreSQL。第二个参数用于指定数据库连接参数(不同类型的数据库,参数格式不一样)。对于SQLite数据库来说,第二
个参数就是数据库文件的路径。
从DB中读写数据
可将变量的值插入到DB当中。假设数据库中有一个表,只有一个forename字段:
ForeName (Name
VARCHAR(30))
如果我们需要插入一个forename,可简单如下实现:
std::string
aName("Peter");
ses << "INSERT INTO
FORENAME VALUES(" << aName << ")", now;
虽然以上可以实现插入,但有个更好的方法就是使用占位符,并使用use语句将变量与占位符绑定,在执行时会用变量值替换占位符。占位符以
: 引导,使用占位符重写上面的例子如下:
std::string
aName("Peter");
ses << "INSERT INTO
FORENAME VALUES(:name)", use(aName), now;
在此例子当中,使用值Peter替换:name占位符。
在“statements”章节,将看到更多更好的使用占位符的方法。
从数据库获取数据的方法类似,可以使用 into
语句将数据库中的数据映射到C++中的对象,同时也支持使用一个默认值来替换数据库中的null.
std::string aName;
ses << "SELECT NAME FROM
FORENAME", into(aName), now; // aName的默认值为空字符串
ses << "SELECT NAME FROM
FORENAME", into(aName, "default"), now;
此外,也可以在一条SQL语句中同时使用use,into语句:
std::string aName;
std::string
match("Peter")
ses << "SELECT NAME FROM
FORENAME WHERE NAME=:name", into(aName), use(match),
now;
poco_assert (aName ==
match);
通常,数据库中的表不会这么简单,一般会有多个字段,因此也可以在SQL中使用多个into/use.
假设有一个Person表包括age, firstname,
lastname:
std::string
firstName("Peter";
std::string
lastName("Junior");
int age = 0;
ses << "INSERT INTO PERSON
VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age),
now;
ses << "SELECT (firstname,
lastname, age) FROM Person", into(firstName), into(lastName),
into(age), now;
此处需要注意的是into/use的顺序问题,第一个占位符对应第一个use,
第二个对应第二个等等。into语句也是类似,上例中的firstname也将输出到第一个into(firstname)当中。(http://www.libpoco.com翻译,转载请注明!)
处理NULL值
数据库中的字段值可以是NULL值,为应付NULL值,
into语句允许定义一个默认值,比如,假设age字段是可选的,我们可以通过into(age,-1)来设置age为NULL时返回-1:
std::string
firstName("Peter";
std::string
lastName("Junior");
int age = 0;
ses << "INSERT INTO PERSON
VALUES (:fn, :ln, :age)", use(firstName), use(lastName), use(age),
now;
ses << "SELECT (firstname,
lastname, age) FROM Person", into(firstName), into(lastName),
into(age, -1), now;
你也可以初始化age=-1来达到同样的效果,此方法不适合容器(Collection)类型,你必须使用第二个参数来初始化。否则,变量将按编译器来决定初始值。
使用Statements
我们已经在前面的章节注意到statement术语。你也许以为我们只使用了session对象,但事实上,我们已经接触到了statement,
让我们来看看session对象<
template
Statement Session::operator
<< (const T& t)
先
忽略template语句,操作符<
给任何变量,只是简单的使用now直接执行了statement,之后statement就消灭了。下面的例子将使用一个变量来保存statement变
量:
std::string
aName("Peter");
Statement stmt = ( ses <<
"INSERT INTO FORENAME VALUES(:name)", use(aName) );
注意右边的表达式必须要用括号包围,否则将无法编译。如果觉得上面的语法不太好看,可参考以下用法:
Statement
stmt(ses);
stmt << "INSERT INTO
FORENAME VALUES(:name)", use(aName);
将statement对象赋给一个变量后,我们控制statement啥时候执行:
std::string
aName("Peter");
Statement stmt = ( ses <<
"INSERT INTO FORENAME VALUES(:name)", use(aName) );
stmt.execute();
poco_assert
(stmt.done());
将调用execute方法后,将在数据库中执行上面的insert语句。stmt.done()可用来验证statement是否完全执行。
预定义的statement
未加now的statement即为预定义的statement:
Statement stmt = ( ses <<
"INSERT INTO FORENAME VALUES(:name)", use(aName) );
预定义statement的优势是可以优化性能,比如以下的循环:
std::string
aName();
Statement stmt = ( ses <<
"INSERT INTO FORENAME VALUES(:name)", use(aName) );
for (int i = 0; i < 100;
++i)
{
aName.append("x");
stmt.execute();
}
以上例子不需要创建和解析statement
100次,只需一次即可。使用use和占位符,可实现插入100个不同的值到数据库。当然,如果真的要插入大量数据到数据库,还有其它更好的方法。
特别注意
use语句使用的是变量的引用,因此,只可使用变量,不可以使用常量值。以下代码将失败(不同的编译器和环境会有不同的处理结果)
Statement stmt = (ses <<
"INSERT INTO PERSON VALUES (:fn, :ln, :age)", use("Peter"),
use("Junior"), use(4)); //错误!
stmt.execute();
以上常量值Petter,Junior,4
必须赋给一个变量,否则将在执行时出错。
配合容器使用
如需一次处理多个值,需要使用容器(collection)类。默认支持以下容器类:
a. vector(数组): 无特别要求
b. set:
类型需要支持
c. multiset:
需支持
d. map:
需支持()操作符,并且按key返回对象,注意:重复的key/value将忽略
e. multimap:
需支持()操作符,并按key返加对象.
以下是通过数组来批量插入DB的例子:
std::string
aName("");
std::vector data;
for (int i = 0; i < 100;
++i)
{
aName.append("x");
data.push_back(aName);
}
ses << "INSERT INTO
FORENAME VALUES(:name)", use(data), now;
对于set,multiset也可按上面的方式插入,但map,multimap不行(std::string没有()操作符).
同时需注意,use语句要求容量为非空(non-empty collections)!
现在来看看以下例子:
std::string aName;
ses << "SELECT NAME FROM
FORENAME", into(aName), now;
在前面的例子中是正常的,是因为数据库的此表只有一行记录。如果数据库中有多行记录,我们只提供了一个字符串变量来存储的话,上面的例子将失败并抛出异常。可用以下的方式改进:
std::vector names;
ses << "SELECT NAME FROM
FORENAME", into(names), now;
当然,除了vector外,这里也可以用set,
multiset.
Limit限定
使用容量可以非常方便的批处理数据,但如果数据库中的数据量巨大时,也将存在一定风险,可能会导致应用程序很少时间才得到响应。另外,你也可能需要更细粒度的控制你的查询,比如你只想获取一个子集的数据。
为解决此问题,我们可以使用limit限定。
假设我们查询了几千行数据到一个用户界面,为便于用户操作,我们可以将此数据进行分隔:
std::vector names;
ses << "SELECT NAME FROM
FORENAME", into(names), limit(50), now;
以上代码将只返回50行的数据。(当然也可能什么都不返回),并追加到
names这个容量中。如果想确保50行记录返回,需要设置limit的第二参数为true(默认为false):
std::vector names;
ses << "SELECT NAME FROM
FORENAME", into(names), limit(50, true), now;
当statement.donw()返回true后,通过statement返回的数据集将是完整的,并可用来遍历。在以下的例子中,我们假设数据库中有101行记录:
std::vector names;
Statement stmt = (ses <<
"SELECT NAME FROM FORENAME", into(names),
limit(50));
stmt.execute(); //names.size()
== 50
poco_assert
(!stmt.done());
stmt.execute(); //names.size()
== 100
poco_assert
(!stmt.done());
stmt.execute(); //names.size()
== 101
poco_assert
(stmt.done());
前面我们指出,如果没有数据返回的话,以上用法也是有效的。因此,在一个空的数据表中,执行以下语句是正常的:
std::string aName;
ses << "SELECT NAME FROM
FORENAME", into(aName), now;
为保证至少返回一个有效的数据,可使用lowerLimit语句:
std::string aName;
ses << "SELECT NAME FROM
FORENAME", into(aName), lowerLimit(1), now;
如果数据表为空的话,以上语句将抛出一个异常。如果查询正常,aName将被正确的初始化。注意,limit只是upperLimit的简写。如需单条遍历结果集,比如某个不想用容器,那么可以这样写:
std::string aName;
Statement stmt = (ses <<
"SELECT NAME FROM FORENAME", into(aName), lowerLimit(1),
upperLimit(1));
while
(!stmt.done())
stmt.execute();
还有一个更简便的方法,就是用range指令:
std::string aName;
Statement stmt = (ses <<
"SELECT NAME FROM FORENAME", into(aName), range(1,1));
while
(!stmt.done())
stmt.execute();
range还有第三个可选参数是boolean类型,用于指定upper
limit是否是强行限定,默认是false.
复合数据类型的映射
前面的例子,我们都是使用基本数据类型(整型,字符串)。现实中的场景可能不太一样,比如我们有一个Person的类:
class Person
{
public:
// default constructor+destr.
// getter and setter methods for all
members
[...]
bool operator
const
/// we
need this for set and multiset support
{
return
_socialSecNr < p._socialSecNr;
}
Poco::UInt64 operator()() const
/// we
need this operator to return the key for the map and
multimap
{
return
_socialSecNr;
}
private:
std::string _firstName;
std::string _lastName;
Poco::UInt64 _socialSecNr;
}
理想情况下,我们希望像字符串一样的使用Person类。为此,你需要创建一个TypeHandler的模板类。注意,此模板的声明必须与原始模板在同一个命令空间,比如Poco:Data.
此模板声明需实现以下方法:
namespace Poco {
namespace Data {
template <>
class TypeHandler
{
public:
static std::size_t size()
{
return 3;
// we handle three columns of the Table!
}
static void bind(std::size_t pos, const
Person& obj, AbstractBinder* pBinder)
{
poco_assert_dbg (pBinder != 0);
// the
table is defined as Person (FirstName VARCHAR(30), lastName
VARCHAR, SocialSecNr INTEGER(3))
// Note
that we advance pos by the number of columns the datatype uses! For
string/int this is one.
TypeHandler::bind(pos++, obj.getFirstName(), pBinder);
TypeHandler::bind(pos++, obj.getLastName(), pBinder);
TypeHandler::bind(pos++, obj.getSocialSecNr(),
pBinder);
}
static void prepare(std::size_t pos, const
Person& obj, AbstractPreparation* pPrepare)
{
poco_assert_dbg (pBinder != 0);
// the
table is defined as Person (FirstName VARCHAR(30), lastName
VARCHAR, SocialSecNr INTEGER(3))
// Note
that we advance pos by the number of columns the datatype uses! For
string/int this is one.
TypeHandler::prepare(pos++, obj.getFirstName(),
pPrepare);
TypeHandler::prepare(pos++, obj.getLastName(),
pPrepare);
TypeHandler::prepare(pos++, obj.getSocialSecNr(),
pPrepare);
}
static void extract(std::size_t pos, Person&
obj, const Person& defVal, AbstractExtractor*
pExt)
/// obj
will contain the result, defVal contains values we should use when
one column is NULL
{
poco_assert_dbg (pExt != 0);
std::string firstName;
std::string lastName;
Poco::UInt64 socialSecNr = 0;
TypeHandler::extract(pos++, firstName, defVal.getFirstName(),
pExt);
TypeHandler::extract(pos++, lastName, defVal.getLastName(),
pExt);
TypeHandler::extract(pos++, socialSecNr, defVal.getSocialSecNr(),
pExt);
obj.setFirstName(firstName);
obj.setLastName(lastName);
obj.setSocialSecNr(socialSecNr);
}
};
} } // namespace
Poco::Data
之后,你就可以把Person类与字符串一样的使用:
std::map people;
ses << "SELECT * FROM
Person", into(people), now;