[Poco]数据库操作简介

POCO Data User Guide

按Poco的文档,本文缩写、改编、注释POCO Data User Guide的内容,介绍Poco的数据库操作。原文见http://pocoproject.org/docs/00200-DataUserManual.html。

先介绍一个简单例子,无须解释,其意自明。

#include "Poco/Data/Common.h"
#include
"Poco/Data/SQLite/Connector.h"
#include
<iostream>
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();
}
Session

表示与数据库的连接,每个Session有两个要素,一是Poco表示数据库种类的类型标识,二是连接每种数据库所需的一个字符串。注意,实际的软件中这两个参数一般不会硬编码在代码中。

以SQLite为例

Session ses("SQLite", "sample.db");

Session ses(SessionFactory::instance()::create(
"SQLite","sample.db"));

读取和写入数据:占位符placeholder,use(),into()

写入操作中使用placeholder和use()

std::string aName("Peter");
ses
<< "INSERT INTO FORENAME VALUES(:name)", use(aName), now;

读取操作中使用placeholder和into()

std::string aName;
ses
<< "SELECT NAME FROM FORENAME", into(aName), now; // the default is the empty string
ses << "SELECT NAME FROM FORENAME", into(aName, "default"), now;

也可以同时使用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);

一般情况下,数据表有多列,用如下形式

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;

注意,placeholder与use或into的顺序是一一对应的。

Statement用于表示SQL语句

原型

template <typename T>
Statement Session::
operator << (const T& t)

两种赋值方式,第一种方式会立即执行语句

std::string aName("Peter");
Statement stmt
= ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );

第二种赋值方式可以把语句保存起来,稍后再执行

std::string aName("Peter");
Statement stmt
= ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
stmt.execute();
poco_assert (stmt.done());

注意,包含占位符的语句只是一个字符串,Statement可以缓存起来,并可以稍后执行,再使用时可以复用一些语句,可以选择Statement的执行时机。于是有了所谓的predefined statement

Predefined Statement

所谓predefined statement即是句尾没有使用now的语句,如

Statement stmt = ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );


用法如下

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();
}

通过这样的操作,可以把一个复杂的算法用SQL语句实现,并包装成一个C++的函数,用数据库的操作完成大量的计算,这是一个非常有用的功能,比如上面的一段代码可以转化成函数形式

void foo(const vector<string> & str)
{
std::
string aName();
Statement stmt
= ( ses << "INSERT INTO FORENAME VALUES(:name)", use(aName) );
for (vector<string>::const_iteartor it = str.begin(), e = str.end(); it != e; ++it)
{
aName.append(
*it);
stmt.execute();
}
}

不愧为用C++封的数据操作,与C++代码几乎无缝连接,下面讲到使用C++的容器类型时,这种特征更加明显。

说明:

use()的输入参数是一个引用,而不可以是一个常量,下面的代码是错的

Statement stmt = (ses << INSERT INTO PERSON VALUES (:fn, :ln, :age)", use("Peter"), use("Junior"), use(4)); //ERR!
stmt.execute();

对容器类型的支持

如果与操作DB是使用容器类型,Poco要求对放入容器中的类型具有如下的要求

vector: no requirements

set: the < operator must be supported by the datatype. Note that duplicate key/value pairs are ignored.

multiset: the < operator must be supported by the datatype

map: the () operator must be supported by the datatype and return the key of the object. Note that duplicate key/value pairs are ignored.

multimap: the () operator must be supported by the datatype and return the key of the object

用容器类接收或是导出数据的例子如下:

std::string aName("");
std::vector
<std::string> data;
for (int i = 0; i < 100; ++i)
{
aName.append(
"x");
data.push_back(aName);
}
ses
<< "INSERT INTO FORENAME VALUES(:name)", use(data), now;

导出数据的例子,注意,如果数据表中有多个表项,确用一个字符串接受,会抛出异常。

std::vector<std::string> names;

ses << "SELECT NAME FROM FORENAME", into(names), now

The limit clause

数据库中的表项很多,具体取回多少表项用limit clause提定,共有四个limit, lowerLimit, upperLimit和rang

limit

std::vector<std::string> names;
ses
<< "SELECT NAME FROM FORENAME", into(names), limit(50), now

lowerLimit

std::string aName;
ses
<< "SELECT NAME FROM FORENAME", into(aName), lowerLimit(1), now;

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();
将复杂数据结构映射成数据表

将对象转化成数据表,有两种思路,一是面向对象的数据库,即在数据库支持部分面向对象的操作,二是用编译语言实现对对象到数据表的映射,称为object relation map,即ORM,实现时关键时把变量名映射为数据表的字段名,所以ORM在脚本语言中比较容易实现,因为有些脚本语言有自反机制。

Poco实现了一种映射,虽不是特别优雅,但对C++来讲已经足够好了。思路是为每一个类实现一个存取类对象的模板,很类似于序列化操作。举例如下,如果类定义是

class Person
{
public:
// default constructor+destr.
// getter and setter methods for all members
[...]
bool operator <(const Person& p) 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;
}

为了方便读写这个类对象,要实现一个类模板

namespace Poco {
namespace Data { // 类模板要放在Poco::Data中
template <>
class TypeHandler<class Person>
{
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<std::string>::bind(pos++, obj.getFirstName(), pBinder);
TypeHandler
<std::string>::bind(pos++, obj.getLastName(), pBinder);
TypeHandler
<Poco::UInt64>::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<std::string>::prepare(pos++, obj.getFirstName(), pPrepare);
TypeHandler
<std::string>::prepare(pos++, obj.getLastName(), pPrepare);
TypeHandler
<Poco::UInt64>::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
<std::string>::extract(pos++, firstName, defVal.getFirstName(), pExt);
TypeHandler
<std::string>::extract(pos++, lastName, defVal.getLastName(), pExt);
TypeHandler
<Poco::UInt64>::extract(pos++, socialSecNr, defVal.getSocialSecNr(), pExt);
obj.setFirstName(firstName);
obj.setLastName(lastName);
obj.setSocialSecNr(socialSecNr);
}
};
} }
// namespace Poco::Data

这个设计方案的好处在于,实现序列化的类不必干涉原有类的设计,缺点是依赖于字段在数据表中的相对位置,而数据库是建立在关系代数基础上的,并不重视这种位置(顺便提一句Wt中实现的映射则比这种实现方法好得多)。这种设计并不十分巧妙。

使用时如下:

std::map<Poco::UInt64, Person> people;
ses
<< "SELECT * FROM Person", into(people), now;
RecordSet

从介绍中可以看出,这个是Poco::Data设计的一个核心类型,从中也可以猜出序列化具体类型时有可能就是用本类实现的。该类可以操纵数据库操作结果的结果,抽象为一个二维表(这是和数据库的概念有出入的,数据库的概念是集合,而不是二维表格,也就是说列之间是无序的,但RecordSet类依赖这种顺序)。

Statement select(session);
select
<< "SELECT * FROM Person";
select.execute();
RecordSet rs(select);
bool more = rs.moveFirst();
while (more)
{
for (std::size_t col = 0; col < cols; ++col)
{
std::cout
<< rs[col].convert<std::string>() << " ";
}
std::cout
<< std::endl;
more
= rs.moveNext();
}

如果结合limit使用则如下

Statement select(session);
select
<< "SELECT * FROM Person", range(0, 10);
RecordSet rs(select);
while (!select.done())
{
select.execute();
bool more = rs.moveFirst();
while (more)
{
for (std::size_t col = 0; col < cols; ++col)
{
std::cout
<< rs[col].convert<std::string>() << " ";
}
std::cout
<< std::endl;
more
= rs.moveNext();
}
}
Tuples

前面介绍了一个类对象如何序列化为数据库中的一组数据,是比较麻烦的,但利用Tuples就不那么麻烦了:

typedef Poco::Tuple<std::string, std::string, int> Person;
typedef std::vector
<Person> People;
People people;
people.push_back(Person(
"Bart Simpson", "Springfield", 12));
people.push_back(Person(
"Lisa Simpson", "Springfield", 10));
Statement insert(session);
insert
<< "INSERT INTO Person VALUES(:name, :address, :age)", use(people), now;
Statement select(session);
select
<< "SELECT Name, Address, Age FROM Person", into(people), now;
for (People::const_iterator it = people.begin(); it != people.end(); ++it)
{
std::cout
<< "Name: " << it->get<0>() <<
", Address: " << it->get<1>() <<
", Age: " << it->get<2>() <<std::endl;
}

从这个思路出发,可以设计一个存取对象的方法,这样C++的类定义与在数据库中的建表的工作就可以放在一起了,并可以保持一致

class Person
{
public:
typedef Poco::Tuple
<std::string, std::string, int> PersonData;
Person(
const PersonData & data)
{
_data
= data;
}
Person(Session
& session)
{
Statement select(session);
select
<< "SELECT Name, Address, Age FROM Person", into(_data), now;
//用 PersonField改成更灵活的形式
}
void save(Session & session) const
{
Statement insert(session);
insert
<< "INSERT INTO Person VALUES(:name, :address, :age)", use(_data), now;
}
private:
PersonData _data
};
const map<string, int> PersonField = { {“Name”, 0}, {“Address”, 1}, {“Age”, 2} };
// 最好在此外可以触发一个建立数据表的操作
Session Pooling

用于保存与数据库的连接,应用程序只起动少数几个连接,能重用时尽量重用,保存在SessionPool中,这样就把对一个数据库的访问(session)做为全局变量在整个应用程序中使用。

SessionPool pool("ODBC", "...");
// ...
Session sess(pool.get());

posted on 2011-02-15 23:52  zhihuichien  阅读(5499)  评论(1编辑  收藏  举报

导航