Poco数据库操作用户手册(一)


一个简单的例子
POCO Data提供了一个抽象的数据层,以供用户方便的与不同的数据库交互,以下是一个完整的实例:
[code]
#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();
}
[/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]

操作符<<用于发送SQL 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 <typename T>
Statement Session::operator << (const T& t)
先忽略template语句,操作符<<的返回类型是statement。前面的例子当中,我们未将session返回的statement赋给任何变量,只是简单的使用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: 类型需要支持<操作符,注意:重复的key/value将忽略
c.  multiset: 需支持<操作符
d. map: 需支持()操作符,并且按key返回对象,注意:重复的key/value将忽略
e. multimap: 需支持()操作符,并按key返加对象.

以下是通过数组来批量插入DB的例子:
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;

对于set,multiset也可按上面的方式插入,但map,multimap不行(std::string没有()操作符). 同时需注意,use语句要求容量为非空(non-empty collections)!  现在来看看以下例子:
std::string aName;
ses << "SELECT NAME FROM FORENAME", into(aName), now;
在前面的例子中是正常的,是因为数据库的此表只有一行记录。如果数据库中有多行记录,我们只提供了一个字符串变量来存储的话,上面的例子将失败并抛出异常。可用以下的方式改进:
std::vector<std::string> names;
ses << "SELECT NAME FROM FORENAME", into(names), now;
当然,除了vector外,这里也可以用set, multiset.

Limit限定
使用容量可以非常方便的批处理数据,但如果数据库中的数据量巨大时,也将存在一定风险,可能会导致应用程序很少时间才得到响应。另外,你也可能需要更细粒度的控制你的查询,比如你只想获取一个子集的数据。 为解决此问题,我们可以使用limit限定。
假设我们查询了几千行数据到一个用户界面,为便于用户操作,我们可以将此数据进行分隔:
std::vector<std::string> names;
ses << "SELECT NAME FROM FORENAME", into(names), limit(50), now;
以上代码将只返回50行的数据。(当然也可能什么都不返回),并追加到 names这个容量中。如果想确保50行记录返回,需要设置limit的第二参数为true(默认为false):
std::vector<std::string> names;
ses << "SELECT NAME FROM FORENAME", into(names), limit(50, true), now;
当statement.donw()返回true后,通过statement返回的数据集将是完整的,并可用来遍历。在以下的例子中,我们假设数据库中有101行记录:
std::vector<std::string> 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 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;
}

理想情况下,我们希望像字符串一样的使用Person类。为此,你需要创建一个TypeHandler的模板类。注意,此模板的声明必须与原始模板在同一个命令空间,比如Poco:Data. 此模板声明需实现以下方法:
namespace Poco {
namespace 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

之后,你就可以把Person类与字符串一样的使用:
std::map<Poco::UInt64, Person> people;
ses << "SELECT * FROM Person", into(people), now;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值