odb 使用指南(三)持久化对象的处理

背景

最近一直在看odb官网上odb-manual,由于是全英文文档,仔细看过一遍之后虽然感觉基本了解,但是隔几天之后再翻开时又要逐一回忆当初理解的一些细节,毕竟不是母语,没有那种一看就条件反射式的想起来,所以把一些key word记录下来,避免下次重复花时间来消化知识。对于一个coder,能够用代码来阐述的,就尽量不用文字,所以在记录过程中尽量通过代码来展示一些api的用法。

使用说明

本文基于odb官方手册为指导,通过自己的理解之后用尽可能通俗易懂的文字来阐述相关用法。站在应用编程的角度,将文档中常用知识点、易错点进行翻译整理。同时本文也弱化掉了一些不常用的知识点,基于这种考虑主要是由于本文的立足点是为了使编程人员尽快上手开发,如果将一些不常用的知识点也罗列其中会显得繁杂且冗余,所以如果在开发过程中遇到本文未提及的点可以回到odb官方手册进行查询。

持久化对象的处理

  1. Objects and Values: Objects属于具有全局唯一标识的实体entity的对象,相当于数据库中的某一条记录,具有id标识符。而Values被称为值对象,这些对象不会单独存在于数据库中,而是存在于实体entity对象的某一个成员变量中。

  2. 事务(Transaction): 事务是原子(Atomic),一致(consistent),隔离(isolated)和持久(durable)(ACID)的工作单元。事务的代码结构如下:

    #include 
    transaction t (db.begin ())
    // Perform database operations.
    t.commit ();
    

    odb::transaction 类有如下接口:

    namespace odb
    {
        class transaction
        {
        public:
            typedef odb::database database_type;
            typedef odb::connection connection_type;
            explicit transaction (transaction_impl*, bool make_current = true);
            transaction ();
            void reset (transaction_impl*, bool make_current = true);
            void commit ();
            void rollback ();
            database_type& database ();
            connection_type& connection ();
            bool finilized () const;
        public:
            static bool has_current ();
            static transaction& current ();
            static void current (transaction&);
            static bool reset_current ();
            // Callback API.
            //
        public:
        ...
        };
    }
    

    解释说明:

    • commit():事务提交
    • rollback(): 事务回滚,当然如果没有显示支持需要commit或rollback的话,当transaction类析构时会自动回滚。
    • database(): 返回当前事务的database
    • connection(): 返回当前事务的connection
    • current(): 返回线程的active态的transaction,如果没有active transaction的话,则会抛出odb::not_in_transaction的异常,如果我们想check一下是否有一个有效的transaction在这个线程中,我们可以使用has_current()接口来实现。
    • transaction的构造函数输入参数make_current如果为false,则不会自动创建active transaction,之后在使用时通过current()接口人为创建。
    • reset_current(): 清除当前的active transaction

    以下是针对同一个线程多个transaction的实例代码

    transaction t1 (db1.begin ()); // Active transaction.
    transaction t2 (db2.begin (), false); // Not active.
    // Perform database operations on db1.
    transaction::current (t2); // Deactivate t1, activate t2.
    // Perform database operations on db2.
    transaction::current (t1); // Switch back to t1.
    // Perform some more database operations on db1.
    t1.commit ();
    transaction::current (t2); // Switch to t2.
    // Perform some more database operations on db2.
    t2.commit ();
    
    • reset(): 允许我们重新使用相同的transaction实例来实现多个数据库的事务操作。通常在我们commit()之后后需要发起事务的场景,示例代码如下:
    transaction t (db.begin ());
    for (size_t i (0); i < n; ++i)
    {
        // Perform a database operation, such as persist an object.
        // Commit the current transaction and start a new one after
        // every 100 operations.
        //
        if (i % 100 == 0)
        {
            t.commit ();
            t.reset (db.begin ());
        }
    }
    t.commit ();
    

    当然我们还需要注意事务会带来另一个潜在的风险,示例代码如下:

    void update_age (database& db, person& p)
    {
        transaction t (db.begin ());
        p.age (p.age () + 1);
        db.update (p);
        t.commit ();
    }
    

    这段代码如果事务提交失败时,就会回滚,相当于数据库什么都没改,但是内存数据p.age却已经被修改了,而编程人员还未发现此问题,这种问题应该尽量在编码过程中避免。我们可以使用下面的代码来解决:

    void update_age (database& db, unsigned long id)
    {
        transaction t (db.begin ());
        auto_ptr<person> p (db.load<person> (id));
        p.age (p.age () + 1);
        db.update (p);
        t.commit ();
    }
    

    当然,还有一种解决方案是当出现异常时,在catch的地方重新从数据库中load数据到内存,实例代码如下:

    void update_age (database& db, person& p)
    {
        try
        {
            transaction t (db.begin ());
            p.age (p.age () + 1);
            db.update (p);
            t.commit ();
        }
        catch (...)
        {
            transaction t (db.begin ());
            db.load (p.id (), p);
            t.commit ();
            throw;
        }
    }
    
  3. 持久化对象存库(persist)

    database::persist()函数的模板如下:

    template <typename T>
    typename object_traits<T>::id_type persist (const T& object);
    
    template <typename T>
    typename object_traits<T>::id_type persist (const object_traits<T>::const_pointer_type& object);
    
    template <typename T>
    typename object_traits<T>::id_type persist (T& object);
    
    template <typename T>
    typename object_traits<T>::id_type persist (const object_traits<T>::pointer_type& object);
    

    实例代码如下:

    person john ("John", "Doe", 33);
    shared_ptr<person> jane (new person ("Jane", "Doe", 32));
    transaction t (db.begin ());
    db.persist (john);
    unsigned long jane_id (db.persist (jane));
    t.commit ();
    cerr << "Jane’s id: " << jane_id << endl;
    
  4. 加载持久化对象(load)

    database::load()函数模板如下:

    template <typename T>
    typename object_traits<T>::pointer_type load (const typename object_traits<T>::id_type& id);
    
    template <typename T>
    void load (const typename object_traits<T>::id_type& id, T& object);
    

    如果load失败(比如你传的id数据库不存在)则会抛出odb::object_not_persistent异常。
    示例代码如下:

    transaction t (db.begin ());
    auto_ptr<person> jane (db.load<person> (jane_id));
    db.load (jane_id, *jane);
    t.commit ();
    

    当前如果我们已经load过持久化对象到内存中时,我们希望重新load一下,我们可以使用reload函数,reload函数不会与缓存中的数据进行交互,即直接从数据库中取数据,而不会从缓存中取。函数模板如下:

    template <typename T>
    void reload (T& object);
    
    template <typename T>
    void reload (const object_traits<T>::pointer_type& object);
    

    当我们不确定我们的id在数据库中是否存在时,我们可以通过find()函数来检查,函数模板如下:

    template <typename T>
    typename object_traits<T>::pointer_type find (const typename object_traits<T>::id_type& id);
    
    template <typename T>
    bool find (const typename object_traits<T>::id_type& id, T& object);
    

    第一个find()输入id,返回指针类型,如果id没有找到则返回null,第二个find()输入id,返回bool值,如果bool值为true,则object为数据库的对象实例,如果bool值为false,则表明没有找到对应id的数据

  5. 持久化对象的更新(update)

    先看下database::update()函数的模板

    template <typename T>
    void update (const T& object);
    
    template <typename T>
    void update (const object_traits<T>::const_pointer_type& object);
    
    template <typename T>
    void update (const object_traits<T>::pointer_type& object);
    

    下面通过一个银行账户转账的例子来讲述update()函数的使用,代码如下

    void transfer (database& db, unsigned long from_acc, unsigned long to_acc, unsigned int amount)
    {
        bank_account from, to;
        transaction t (db.begin ());
    
        db.load (from_acc, from);
    
        if (from.balance () < amount)
            throw insufficient_funds ();
    
        db.load (to_acc, to);
    
        to.balance (to.balance () + amount);
        from.balance (from.balance () - amount);
    
        db.update (to);
        db.update (from);
    
        t.commit ();
    }
    

    上面的例子是将from, to从数据库load到指定对象,我们也可以动态的分配一块内存,然后将内存的数据update到数据库中,实例如下:

    void transfer (database& db, unsigned long from_acc, unsigned long to_acc, unsigned int amount)
    {
        transaction t (db.begin ());
        shared_ptr<bank_account> from (db.load<bank_account> (from_acc));
    
        if (from->balance () < amount)
            throw insufficient_funds ();
    
        shared_ptr<bank_account> to (db.load<bank_account> (to_acc));
    
        to->balance (to->balance () + amount);
        from->balance (from->balance () - amount);
    
        db.update (to);
        db.update (from);
    
        t.commit ();
    }
    
  6. 删除持久化对象(delete)

    database::erase_query()函数的模板如下:

    template <typename T>
    void erase (const T& object);
    
    template <typename T>
    void erase (const object_traits<T>::const_pointer_type& object);
    
    template <typename T>
    void erase (const object_traits<T>::pointer_type& object);
    
    template <typename T>
    void erase (const typename object_traits<T>::id_type& id);
    

    使用举例:

    person& john = ...
    shared_ptr<jane> jane = ...
    unsigned long joe_id = ...
    transaction t (db.begin ());
    db.erase (john);
    db.erase (jane);
    db.erase<person> (joe_id);
    t.commit ();
    

    我们也可以通过erase_query()函数来删除多个匹配的数据库对象,当然使用它的前提是在odb的编译选项中得有–generate-query,erase_query()函数的模板如下:

    template <typename T>
    unsigned long long erase_query ();
    
    template <typename T>
    unsigned long long erase_query (const odb::query<T>&);
    

    第一个函数会删除所有类型为T的数据,第二个函数根据odb::query提供的查询表达式来匹配,样例程序如下:

    typedef odb::query<person> query;
    transaction t (db.begin ());
    db.erase_query<person> (query::last == "Doe" && query::age < 30);
    t.commit ();
    

    与query()函数不同,在调用delete_query()时,我们不能在查询表达式中使用指向对象的成员。但是,我们仍然可以将与指针对应的成员用作具有指向对象的id类型的普通对象成员。这使我们可以比较对象ID并测试指针是否为NULL.接下来我们会通过例子来详细说明

    typedef odb::query<employee> query;
    transaction t (db.begin ());
    employer& e = ... // Employer object to be deleted.
    db.erase_query<employee> (query::employer == e.id ());
    db.erase (e);
    t.commit ();
    

    假设employee对象的成员包含有指向employer对象e的指针,现在我们需要将要删除employer对象,为了避免出现employee中指向employer的指针找不到对象的情况,我们在删除employer对象之前,先删除那些把employer作为成员变量的对象,这里我们通过query来查询一下所有employee对象中,指定e的全部删除。之后再删除e本身

  7. 执行本地sql语句(execute)

    某些场景中,我们可能需要直接使用sql语句来操作数据库,odb提供了相关的api接口。database::execute()函数的重载版本如下:

    unsigned long long execute (const char* statement);
    unsigned long long execute (const std::string& statement);
    unsigned long long execute (const char* statement, std::size_t length)
    

    使用举例:

    transaction t (db.begin ());
    db.execute ("DROP TABLE test");
    db.execute ("CREATE TABLE test (n INT PRIMARY KEY)");
    t.commit ();
    

上一篇 odb 使用指南(二)Hello World
下一篇 odb 使用指南(四)数据库查询

你可能感兴趣的:(odb 使用指南(三)持久化对象的处理)