SOCI是一個數(shù)據(jù)庫操作的庫笨觅,并不是ORM庫铆农,它仍舊需要用戶編寫sql語句來操作數(shù)據(jù)庫譬挚,只是使用起來會更加方便,主要有以下幾個特點
- 以stream方式輸入sql語句
- 通過into和use語法傳遞和解析參數(shù)
- 支持連接池梗夸,線程安全
由此可見它只是一個輕量級的封裝,因此也有更大的靈活性号醉,后端支持oracle反症,mysql等,后續(xù)示例均基于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
基本查詢
假設(shè)有如下表單
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");
第一個參數(shù)為使用的后端數(shù)據(jù)庫類型畔派,第二個參數(shù)為數(shù)據(jù)庫連接參數(shù)铅碍,可以指定的參數(shù)包括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語法傳遞參數(shù)
其中Person(first_name, last_name)
為數(shù)據(jù)庫table名和column名胞谈,values(:first_name, :last_name)
里的為參數(shù)的占位符吏奸,這里可以隨便書寫啡浊,get_last_insert_id
函數(shù)可以獲取自增長字段的返回值
需要注意的是use
函數(shù)里的參數(shù)的生命周期,切記不能將函數(shù)返回值作為use
函數(shù)的參數(shù)
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;
}
這里根據(jù)id字段查詢first_name和last_name兩個字段森枪,并通過into
函數(shù)將數(shù)據(jù)復(fù)制給變量配紫,got_data()
方法可判斷是否有數(shù)據(jù)返回
當id為整數(shù)時径密,sql語句也可以寫作sql << "select balabala from Person where id=" << id
,但當id為字符串時這樣寫會報錯笨蚁,因此建議都采用use
函數(shù)
如果查詢結(jié)果是多行數(shù)據(jù)睹晒,則需要使用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模版的參數(shù)類型必需和數(shù)據(jù)庫類型一一對應(yīng),varchar和text類型對應(yīng)string括细,整數(shù)類型按如下關(guān)系對應(yīng)
數(shù)據(jù)庫類型 | 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);
有時候我們需要關(guān)注delete操作是否真的刪除了數(shù)據(jù)伪很,mysql本身也會返回操作影響的行數(shù),可以采用如下方法獲取
statement st = (sql.prepare << "delete from Person where id=:id", use(id));
st.execute(true);
int affected_rows = st.get_affected_rows();
使用連接池
使用連接池可以解決多線程的問題奋单,每個線程在操作數(shù)據(jù)庫時先從連接池取出一個session锉试,這個session會被設(shè)置為鎖定,用完之后再換回去览濒,設(shè)置為解鎖呆盖,這樣不同線程使用不同session拖云,互不影響。session對象可以用連接池來構(gòu)造应又,構(gòu)造時自動鎖定宙项,析構(gòu)時自動解鎖
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)
的調(diào)用是沒有超時時間的,如果沒有可用的session株扛,會一直阻塞尤筐,如果要設(shè)置超時時間,可以采用connection_pool的底層接口
session & at(std::size_t pos);
bool try_lease(std::size_t & pos, int timeout);
void give_back(std::size_t pos);
調(diào)用方式如下
size_t pos
if (!try_lease(pos, 3000)) // 鎖定session洞就,設(shè)置超時為3秒
{
return;
}
session& sql = g_pool.at(pos) // 獲取session盆繁,此時pos對應(yīng)的session已被鎖定
/* sql操作 ... */
g_pool.give_back(pos); // 解鎖pos對應(yīng)的session
需要注意的是,如果try_lease
調(diào)用成功后沒有調(diào)用give_back
旬蟋,會一直鎖定對應(yīng)的session油昂,因此try_lease
和give_back
必需成對使用
事務(wù)
session對象提供了對事務(wù)的操作方法
void begin();
void commit();
void rollback();
同時也提供了封裝好的transaction對象,使用方式如下
{
transaction tr(sql);
sql << "insert into ...";
sql << "more sql queries ...";
// ...
tr.commit();
}
如果commit沒有被執(zhí)行倾贰,則transaction對象在析構(gòu)時會自動調(diào)用session對象的rollback方法
ORM
soci可以通過自定義對象轉(zhuǎn)換方式從而在use和into語法中直接使用用戶對象
比如針對Person表單我們定義如下結(jié)構(gòu)和轉(zhuǎn)換函數(shù)
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模板的參數(shù)類型必需和數(shù)據(jù)庫字段對應(yīng)冕碟,對應(yīng)關(guān)系見之前select的示例,對于整數(shù)類型躁染,在set時最好也加上強轉(zhuǎn)并且和get一致鸣哀,否則可能會拋異常std::bad_cast
。get和set函數(shù)的第一個參數(shù)是占位符吞彤,占位符的名字不一定要和數(shù)據(jù)庫column名一致我衬,但后續(xù)操作中values
語法里的占位符必需和這里指定的一致
定義了type_conversion
之后,后續(xù)在用到use和into語法時可直接使用Person對象饰恕,這時soci會根據(jù)占位符操作指定字段
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);