按Poco的文档,本文缩写、改编、注释POCO Data User Guide的内容,介绍Poco的数据库操作。原文见http://pocoproject.org/docs/00200-DataUserManual.html。
先介绍一个简单例子,无须解释,其意自明。
#include"Poco/Data/Common.h"#include"Poco/Data/SQLite/Connector.h"#includeusingnamespacePoco::Data;voidinit()
{
SQLite::Connector::registerConnector();
}voidshutdown()
{
SQLite::Connector::unregisterConnector();
}intmain(intargc,char*argv[])
{
init();
Session ses("SQLite","sample.db");intcount=0;
ses<
std::cout<
shutdown();
}
Session
表示与数据库的连接,每个Session有两个要素,一是Poco表示数据库种类的类型标识,二是连接每种数据库所需的一个字符串。注意,实际的软件中这两个参数一般不会硬编码在代码中。
以SQLite为例
Session ses("SQLite","sample.db");
或
Session ses(SessionFactory::instance()::create("SQLite","sample.db"));
读取和写入数据:占位符placeholder,use(),into()
写入操作中使用placeholder和use()
std::stringaName("Peter");
ses<
读取操作中使用placeholder和into()
std::stringaName;
ses<
也可以同时使用use()和into()
std::stringaName;
std::stringmatch("Peter")
ses<
poco_assert (aName==match);
一般情况下,数据表有多列,用如下形式
std::stringfirstName("Peter";
std::stringlastName("Junior");intage=0;
ses<
注意,placeholder与use或into的顺序是一一对应的。
Statement用于表示SQL语句
原型
templateStatement Session::operator<
两种赋值方式,第一种方式会立即执行语句
std::stringaName("Peter");
Statement stmt=( ses<
第二种赋值方式可以把语句保存起来,稍后再执行
std::stringaName("Peter");
Statement stmt=( ses<
stmt.execute();
poco_assert (stmt.done());
注意,包含占位符的语句只是一个字符串,Statement可以缓存起来,并可以稍后执行,再使用时可以复用一些语句,可以选择Statement的执行时机。于是有了所谓的predefined statement
Predefined Statement
所谓predefined statement即是句尾没有使用now的语句,如
Statement stmt=( ses<
用法如下
std::stringaName();
Statement stmt=( ses<
{
aName.append("x");
stmt.execute();
}
通过这样的操作,可以把一个复杂的算法用SQL语句实现,并包装成一个C++的函数,用数据库的操作完成大量的计算,这是一个非常有用的功能,比如上面的一段代码可以转化成函数形式
voidfoo(constvector&str)
{
std::stringaName();
Statement stmt=( ses<::const_iteartor it=str.begin(), e=str.end(); it!=e;++it)
{
aName.append(*it);
stmt.execute();
}
}
不愧为用C++封的数据操作,与C++代码几乎无缝连接,下面讲到使用C++的容器类型时,这种特征更加明显。
说明:
use()的输入参数是一个引用,而不可以是一个常量,下面的代码是错的
Statement stmt=(ses<
对容器类型的支持
如果与操作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::stringaName("");
std::vector<:string>data;for(inti=0; i<100;++i)
{
aName.append("x");
data.push_back(aName);
}
ses<
导出数据的例子,注意,如果数据表中有多个表项,确用一个字符串接受,会抛出异常。
std::vector<:string> names;
ses << "SELECT NAME FROM FORENAME", into(names), now
The limit clause
数据库中的表项很多,具体取回多少表项用limit clause提定,共有四个limit, lowerLimit, upperLimit和rang
limit
std::vector<:string>names;
ses<
lowerLimit
std::stringaName;
ses<
upperLimit
std::stringaName;
Statement stmt=(ses<
stmt.execute();
range
std::stringaName;
Statement stmt=(ses<
stmt.execute();
将复杂数据结构映射成数据表
将对象转化成数据表,有两
种思路,一是面向对象的数据库,即在数据库支持部分面向对象的操作,二是用编译语言实现对对象到数据表的映射,称为object relation
map,即ORM,实现时关键时把变量名映射为数据表的字段名,所以ORM在脚本语言中比较容易实现,因为有些脚本语言有自反机制。
Poco实现了一种映射,虽不是特别优雅,但对C++来讲已经足够好了。思路是为每一个类实现一个存取类对象的模板,很类似于序列化操作。举例如下,如果类定义是
classPerson
{public://default constructor+destr.//getter and setter methods for all members[...]booloperator
}
Poco::UInt64operator()()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;
}
为了方便读写这个类对象,要实现一个类模板
namespacePoco {namespaceData {//类模板要放在Poco::Data中template<>classTypeHandler{public:staticstd::size_t size()
{return3;//we handle three columns of the Table!}staticvoidbind(std::size_t pos,constPerson&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<:string>::bind(pos++, obj.getFirstName(), pBinder);
TypeHandler<:string>::bind(pos++, obj.getLastName(), pBinder);
TypeHandler<:uint64>::bind(pos++, obj.getSocialSecNr(), pBinder);
}staticvoidprepare(std::size_t pos,constPerson&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<:string>::prepare(pos++, obj.getFirstName(), pPrepare);
TypeHandler<:string>::prepare(pos++, obj.getLastName(), pPrepare);
TypeHandler<:uint64>::prepare(pos++, obj.getSocialSecNr(), pPrepare);
}staticvoidextract(std::size_t pos, Person&obj,constPerson&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::stringfirstName;
std::stringlastName;
Poco::UInt64 socialSecNr=0;
TypeHandler<:string>::extract(pos++, firstName, defVal.getFirstName(), pExt);
TypeHandler<:string>::extract(pos++, lastName, defVal.getLastName(), pExt);
TypeHandler<:uint64>::extract(pos++, socialSecNr, defVal.getSocialSecNr(), pExt);
obj.setFirstName(firstName);
obj.setLastName(lastName);
obj.setSocialSecNr(socialSecNr);
}
};
} }//namespace Poco::Data
这个设计方案的好处在于,实现序列化的类不必干涉原有类的设计,缺点是依赖于字段在数据表中的相对位置,而数据库是建立在关系代数基础上的,并不重视这种位置(顺便提一句Wt中实现的映射则比这种实现方法好得多)。这种设计并不十分巧妙。
使用时如下:
std::map<:uint64 person>people;
ses<
RecordSet
从介绍中可以看出,这个是
Poco::Data设计的一个核心类型,从中也可以猜出序列化具体类型时有可能就是用本类实现的。该类可以操纵数据库操作结果的结果,抽象为一个二维表
(这是和数据库的概念有出入的,数据库的概念是集合,而不是二维表格,也就是说列之间是无序的,但RecordSet类依赖这种顺序)。
Statement select(session);
select<
select.execute();
RecordSet rs(select);boolmore=rs.moveFirst();while(more)
{for(std::size_t col=0; col
{
std::cout<()<
}
std::cout<<:endl>
more=rs.moveNext();
}
如果结合limit使用则如下
Statement select(session);
select<
RecordSet rs(select);while(!select.done())
{
select.execute();boolmore=rs.moveFirst();while(more)
{for(std::size_t col=0; col
{
std::cout<()<
}
std::cout<<:endl>
more=rs.moveNext();
}
}
Tuples
前面介绍了一个类对象如何序列化为数据库中的一组数据,是比较麻烦的,但利用Tuples就不那么麻烦了:
typedef Poco::Tuple<:string std::string>Person;
typedef std::vectorPeople;
People people;
people.push_back(Person("Bart Simpson","Springfield",12));
people.push_back(Person("Lisa Simpson","Springfield",10));
Statement insert(session);
insert<
Statement select(session);
select<
{
std::cout<get<0>()<get<1>()<get<2>()<<:endl>
}
从这个思路出发,可以设计一个存取对象的方法,这样C++的类定义与在数据库中的建表的工作就可以放在一起了,并可以保持一致
classPerson
{public:
typedef Poco::Tuple<:string std::string>PersonData;
Person(constPersonData&data)
{
_data=data;
}
Person(Session&session)
{
Statement select(session);
select<
Statement insert(session);
insert<
}private:
PersonData _data
};constmapPersonField={ {“Name”,0}, {“Address”,1}, {“Age”,2} };//最好在此外可以触发一个建立数据表的操作
Session Pooling
用于保存与数据库的连接,应用程序只起动少数几个连接,能重用时尽量重用,保存在SessionPool中,这样就把对一个数据库的访问(session)做为全局变量在整个应用程序中使用。
SessionPool pool("ODBC","...");//...Session sess(pool.get());