前言
MYSQL C API 的常见操作可以分为两类: query 和 statement 。 query 着重的是返回结果集; statement 则着重执行结果和 affected row 。当然如果想忽略执行结果和 affected row ,也可以用 query 来执行 statement 的任务。最常见的是 query 执行 SELECT 语句而 statement 执行 INSERT/UPDATE/DELETE 和存储过程。 MYSQL 的 API 为 mysql_real_query , mysql_stmt_init/mysql_stmt_prepare/mysql_stmt_execute 。详细资料请参考 http://dev.mysql.com/doc/refman .
设计思想
对 query 而言,标准步骤为:
1) 构造 SQL;
2) 执行 SQL;
3) 解析结果集, 对 MYSQL 是 MYSQL_ROW ,实际为 char**;
4) 异常处理。
可变的部分为 SQL 和结果集解析,因为 SELECT 可以 SELECT 多个 COLUMN ;但骨架操作高度一致。正好符合模板的设计思想。于是定义了一个模板函数。
//template function to run a query sql and save the results into a container
template <class H, class T>
bool query(H& rh, const string& query_sql, vector<T>& array)
此模板有两个模板参数:
1) 一行结果集的处理类;
2) 一行结果集的容器类。
class ExampleContainer:
{
public:
int col1;
string col2;
unsigned char* col3;
int buffer_len;
};
class ExampleRowHandler
{
public :
bool handle_row(MYSQL_ROW row, unsigned long* lengths, ExampleContainer& ec);
};
实际使用时的代码为:
ostringstream oss;
oss << “SELECT col1, col2, col3 FROM example”;
ExampleRowHandler ech;
vector< ExampleContainer> ecs;
if(!query(ech, oss.str(), ecs))
{
throw DBGoneException("failed to execute query:" + oss.str());
}
而一行的处理就是比较个案的代码。
bool ExampleRowHandler:: handle_row(MYSQL_ROW row, unsigned long* lengths, ExampleContainer& ec)
{
try
{
register int i = 0;
if(!get_from_string(ec.col1, row[i], lengths[i]))
{
throw STLException("failed to get col1");
}
i++;
if(!get_from_string(ec.col2, row[i], lengths[i]))
{
throw STLException("failed to get col2");
}
i++;
ec.col3 = new unsigned char[lengths[i]];
ec.buffer_len = lengths[i];
if(!get_from_string(ec.col3, row[i], lengths[i]))
{
throw STLException("failed to get col3");
}
}
catch(STLException& dbe)
{
}
}
对于只 SELECT 一个 COLUMN 的情况,完全可以定义一个模板类做处理。
template <class T>
class RowHandlerOneC
{
public:
bool handle_row(MYSQL_ROW row, unsigned long* lengths, T& t)
{
if(!get_from_string(t, row[0], lengths[0]))
{
cerr << "failed to get ts from string." << endl;
return false;
}
return true;
}
};
注意到这里对 char** 的处理用到了模板函数 get_from_string 。此模板函数实际是用标准库函数的流操作替代了 C 的 sscanf 。
//tools to get msyql results; typedef char** MYSQL_ROW
//use the string stream to convert those string into the specific type
template <class T>
bool get_from_string(T& t, const char* s, int length)
{
istringstream iss(string(s, length));
iss >> t;
return !iss.fail();
}
对于一类特殊的类型, unsigned char ,此函数出现截断的问题( ’/0’ )。所以必须提供一个完全特化版本。
//the full specicailized version for string converter
//unsigned char array is special because possible '/0' exist.
//if you handle it as a string, the array will be broken.
template <>
bool get_from_string<unsigned char*>(unsigned char*& t, const char* s, int length)
{
for(int i = 0; i < length; i ++)
{
t[i] = (unsigned char) s[i];
}
return true;
}
现在列出完整的代码。
//template function to run a query sql and save the results into a container
template <class H, class T>
bool query(H& rh, const string& query_sql, vector<T>& array)
{
try
{
if(m_pMySQL == NULL)
{
throw STLException("mysql client connection is not initialized.");
}
if(mysql_real_query(m_pMySQL, query_sql.c_str(), query_sql.length()))
{
throw STLException(query_sql + " failed. Error: " + mysql_error(m_pMySQL));
}
m_pMySQLRes = mysql_store_result(m_pMySQL);
if(!m_pMySQLRes)
{
if(mysql_field_count(m_pMySQL) == 0)
{
cerr << "NULL result set, row affected: " << mysql_affected_rows(m_pMySQL) << endl;
}
else
{
throw STLException("failed to get the result.");
}
}
else
{
int num_rows = mysql_num_rows(m_pMySQLRes);
if(num_rows == 0)
{
cerr << "got result set, rows affected: " << mysql_affected_rows(m_pMySQL) << endl;
}
else
{
ubit32 num_fields = mysql_num_fields(m_pMySQLRes);
for(int i = 0; i < num_rows; i ++)
{
MYSQL_ROW row = mysql_fetch_row(m_pMySQLRes);
unsigned long *lengths = mysql_fetch_lengths(m_pMySQLRes);
if(row && lengths)
{
Holder<T> h;
T t = h.get_inst();
if(!rh.handle_row(row, lengths, t))
{
throw STLException("failed to handle rows.");
}
else
{
array.push_back(t);
}
}
}
}
}
catch(STLException& dbe)
{
if(!m_pMySQLRes)
{
mysql_free_result(m_pMySQLRes);
}
return false;
}
return true;
}
必须注意到一个细节:模板类 Holder 。如果模板函数 query 传入是指针类型,如果直接直接定义 T t 就会出现指针未初始化的问题。你也可以对指针类型做特化处理,但大部分代码就会相同,完全没有必要。定义一个模板类就要解决这个问题:对这个模板类做特化,代码不会重复。
template<class T>
class Holder
{
public:
T get_inst()
{
return t;
}
private:
T t;
};
template<class T>
class Holder<T*>
{
public:
T* get_inst()
{
return new T();
}
};
后记
对 statement 完全也可以这样封装,把常用的动作都抽象出来定义诸如 INSERT, UPDATE, DELETE, STORED PROCEDURE 的执行和结果处理。