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());
(责任编辑:admin) |