C++數據庫操作之SOCI

SOCI是一個數據庫操作的庫,并不是ORM庫,它仍舊需要用戶編寫sql語句來操作數據庫,只是使用起來會更加方便,主要有以下幾個特點

  1. 以stream方式輸入sql語句
  2. 通過into和use語法傳遞和解析參數
  3. 支持連接池,線程安全

由此可見它只是一個輕量級的封裝,因此也有更大的靈活性,后端支持oracle,mysql等,后續示例均基于mysql

安裝

git項目地址https://github.com/SOCI/soci

推薦使用cmake編譯

git clone git@github.com:SOCI/soci.git
cd soci
mkdir build 
cd build
cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/opt/third_party/soci
make
sudo make install

基本查詢

假設有如下表單

CREATE TABLE `Person` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `first_name` varchar(64) NOT NULL DEFAULT '',
  `second_name` varchar(64) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

初始化session

using namespace soci;
session sql("mysql", "dbname=test user=your_name password=123456");

第一個參數為使用的后端數據庫類型,第二個參數為數據庫連接參數,可以指定的參數包括host port dbname user passowrd等,以空格分隔

insert

string first_name = "Steve";
string last_name = "Jobs";
sql << "insert into Person(first_name, last_name)"
    " values(:first_name, :last_name)", 
    use(first_name), use(last_name);
long id;
sql.get_last_insert_id("Person", id)

通過流的方式傳遞sql語句,用use語法傳遞參數

其中Person(first_name, last_name)為數據庫table名和column名,values(:first_name, :last_name)里的為參數的占位符,這里可以隨便書寫,get_last_insert_id函數可以獲取自增長字段的返回值

需要注意的是use函數里的參數的生命周期,切記不能將函數返回值作為use函數的參數

select

int id = 1;
string first_name;
string last_name;
sql << "select first_name, last_name from Person where id=:id ", 
    use(id), into(first_name), into(last_name);
if (!sql.got_data())
{
    cout << "no record" << endl;
}

這里根據id字段查詢first_name和last_name兩個字段,并通過into函數將數據復制給變量,got_data()方法可判斷是否有數據返回

當id為整數時,sql語句也可以寫作sql << "select balabala from Person where id=" << id,但當id為字符串時這樣寫會報錯,因此建議都采用use函數

如果查詢結果是多行數據,則需要使用rowset類型并自己提取

rowset<row> rs = (sql.prepare << "select * from Person");
for (rowset<row>::iterator it = rs.begin(); it != rs.end(); ++it)
{
    const row& row = *it;
    cout << "id:" << row.get<long long>(0)
        << " first_name:" << row.get<string>(1)
        << " last_name:" << row.get<string>(2) << endl;
  
}

這里get模版的參數類型必需和數據庫類型一一對應,varchar和text類型對應string,整數類型按如下關系對應

數據庫類型 soci類型
SMALLINT int
MEDIUMINT int
INT long long
BIGINT unsigned long long

update

int id = 1;
string first_name = "hello";
string last_name = "world";
sql << "update Person set first_name=:first_name, last_name=:last_name"
    " where id=:id", 
    use(first_name), use(last_name), use(id);

delete

int id = 1;
sql << "delete from Person where id=:id", use(id);

有時候我們需要關注delete操作是否真的刪除了數據,mysql本身也會返回操作影響的行數,可以采用如下方法獲取

statement st = (sql.prepare << "delete from Person where id=:id", use(id));
st.execute(true);
int affected_rows = st.get_affected_rows();

使用連接池

使用連接池可以解決多線程的問題,每個線程在操作數據庫時先從連接池取出一個session,這個session會被設置為鎖定,用完之后再換回去,設置為解鎖,這樣不同線程使用不同session,互不影響。session對象可以用連接池來構造,構造時自動鎖定,析構時自動解鎖

int g_pool_size = 3;
connection_pool g_pool(g_pool_size);
for (int i = 0; i < g_pool_size; ++i)
{
    session& sql = g_pool.at(i);
    sql.open("mysql", "dbname=test user=zhangmenghan password=123456");
}
session sql(g_pool);
sql << "select * from Person";

此時session sql(g_pool)的調用是沒有超時時間的,如果沒有可用的session,會一直阻塞,如果要設置超時時間,可以采用connection_pool的底層接口

session & at(std::size_t pos);
bool try_lease(std::size_t & pos, int timeout);
void give_back(std::size_t pos);

調用方式如下

size_t pos
if (!try_lease(pos, 3000)) // 鎖定session,設置超時為3秒
{
    return;
}
session& sql = g_pool.at(pos) // 獲取session,此時pos對應的session已被鎖定
/* sql操作 ... */
g_pool.give_back(pos); // 解鎖pos對應的session

需要注意的是,如果try_lease調用成功后沒有調用give_back,會一直鎖定對應的session,因此try_leasegive_back必需成對使用

事務

session對象提供了對事務的操作方法

void begin();
void commit();
void rollback();

同時也提供了封裝好的transaction對象,使用方式如下

{
    transaction tr(sql);

    sql << "insert into ...";
    sql << "more sql queries ...";
    // ...

    tr.commit();
}

如果commit沒有被執行,則transaction對象在析構時會自動調用session對象的rollback方法

ORM

soci可以通過自定義對象轉換方式從而在use和into語法中直接使用用戶對象

比如針對Person表單我們定義如下結構和轉換函數

struct Person
{
    uint32_t id;
    string first_name;
    string last_name;
}

namespace soci {
template<>
struct type_conversion<Person>
{
    typedef values base_type;
    static void from_base(const values& v, indicator ind, Person& person)
    {
        person.id = v.get<long long>("id");
        person.first_name = v.get<string>("first_name");
        person.last_name = v.get<string>("last_name");

    }
    static void to_base(const Person& person, values& v, indicator& ind)
    {
        v.set("id", (long long)person.id);
        v.set("first_name", person.first_name);
        v.set("last_name", person.last_name);
    }
};
}

需要注意的是這里get模板的參數類型必需和數據庫字段對應,對應關系見之前select的示例,對于整數類型,在set時最好也加上強轉并且和get一致,否則可能會拋異常std::bad_cast。get和set函數的第一個參數是占位符,占位符的名字不一定要和數據庫column名一致,但后續操作中values語法里的占位符必需和這里指定的一致

定義了type_conversion之后,后續在用到use和into語法時可直接使用Person對象,這時soci會根據占位符操作指定字段

insert

Person person;
person.first_name = "Steve";
person.last_name = "Jobs";
sql << "insert into Person(first_name, last_name)"
    " values(:first_name, :last_name)", use(person);

select

int id = 1;
Person person;
sql << "select * from Person where id=:id", use(id), into(person);

rowset<Person> rs = (sql.prepare << "select * from Person");
for (rowset<Person>::iterator it = rs.begin(); it != rs.end(); ++it)
{
    const Person& person = *it;
    // do something with person
}

update

person.id = 1;
person.first_name = "hello";
person.last_name = "world";
sql << "update Person set first_name=:first_name, last_name=:last_name"
    " where id=:id", use(person);

delete

Person person;
person.id = 1;
sql << "delete from Person where id=:id", use(person);

完整示例

https://github.com/handy1989/soci_test

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容