先show一下Obotcha中声明数据库Table的方法:
DECLARE_SIMPLE_CLASS(Student) IMPLEMENTS(SqlRecord) {
DECLARE_TABLE_2(Student,age,name);
DECLARE_INT_DATA_MEMBER(Student,age);
DECLARE_STRING_DATA_MEMBER(Student,name);
};
PUBLISH_TABLE(Student);
所有的数据库Table对应的class都需要继承SqlRecord,方便以后做package操作。
DECLARE_TABLE_2:这个宏是用来声明Table名称和column列名的。第一个参数是Table名字,第二个参数开始是Table的列名。
DECLARE_INT_DATA_MEMBER:声明了column的类型为INT。第一个是Table名字,第二个是列名字。
DECLARE_STRING_DATA_MEMBER:同上,是申明String类型的。
PUBLISH_TABLE:发布这个table。
通过上述这个定义以后,就完成了和数据库中Student表的映射,前提:数据库中Student表名,列名要和这里声明的一直,否者反射的时候是无法找到对应的赋值函数的。
接下来,我们如果需要做一个查询,就可以使用下面的代码:
ArrayList<SqlRecord> ss = c->query(createString("select * from student;"),RECORD_BUILDER(Student));
int size = ss->size();
for(int index = 0;index<size;index++) {
Student v = ss->get(index);
printf("name is %s \n",v->getname()->toChars());
printf("age is %d \n",v->getage());
}
其中RECORD_BUILDER是用来给底层创建Student实例而传入的builder。总体来看基本上就是一个简单的ORM了。哈哈。
说完用法,我们来分析一下具体的实现方法:
一.DECLARE_INT_DATA_MEMBER 宏分析
#define DECLARE_INT_DATA_MEMBER(Table,Member) \
private:int Member; \
public: int get##Member() {return Member;} \
public: void set##Member(int v) {\
Member = v;\
} \
public: static void set##Member(void *t,char *v) { \
_##Table* _t = (_##Table *)t;\
_t->Member = atoi(v);\
}\
这个宏以传入的列名声明了成员变量,此外还扩展了该成员变量的get/set函数。
二.DECLARE_TABLE_2宏分析:
#define DECLARE_TABLE_2(Table,X,Y) \
public:static std::map<std::string,sql_data_set> funcmap; \
private:sql_data_set funclist_##Table[2];\
public:static void preInitialize() { \
_##Table::funcmap.insert(std::pair<std::string,sql_data_set>(#X,_##Table::set##X));\
_##Table::funcmap.insert(std::pair<std::string,sql_data_set>(#Y,_##Table::set##Y));\
}\
DECLARE_SQL_ASSIGNMENT(Table,2)
funcmap实际上是用来存放<函数名,函数指针>的一个map。底层每次都会调用根据col的列名到这个map中去查找获取DECLARE_INT_DATA_MEMBER中声明的set接口指针。
preInitialize函数就是将set接口指针和col名作为键值对存到map中。
三.DECLARE_SQL_ASSIGNMENT 宏分析
#define DECLARE_SQL_ASSIGNMENT(Table,Size) \
public: static int isInit;\
public: sql_data_set getSqlAssignment(int index,std::string col) { \
printf("get trace1,index is %d \n",index);\
if(Size <= index) { \
return nullptr;\
}\
if(_##Table::funclist_##Table[index] != nullptr) { \
printf("get trace2 \n");\
return _##Table::funclist_##Table[index]; \
}\
if(_##Table::isInit == 0) { \
_##Table::preInitialize();\
_##Table::isInit = 1;\
}\
std::map<std::string,sql_data_set>::iterator ite = _##Table::funcmap.find(col);\
printf("get trace3 \n");\
if(ite != _##Table::funcmap.end()) { \
_##Table::funclist_##Table[index] = ite->second;\
printf("get trace4 \n");\
return ite->second;\
}\
printf("get trace4 \n");\
return nullptr;\
}\
getSqlAsignment的处理就是先根据col名字找到相关的函数指针,funclist是用来做缓存的,后续可以直接根据列id获取,减少hashmap的查找。
四.PUBLISH_TABLE宏分析
#define PUBLISH_TABLE(Table) \
std::map<std::string,sql_data_set> _##Table::funcmap;\
int _##Table::isInit = 0;\
DECLARE_SIMPLE_CLASS(Build##Table) IMPLEMENTS(SqlRecordBuilder){ \
public :SqlRecord create() { \
sp<_##Table> data;\
_##Table *t = new _##Table();\
data.set_pointer(t);\
return data;\
}\
};\
PUBLISH宏实际上是实现了一个sqlrecordbuilder的实例,sql底层就是调用这个create函数来生产对应实例。
相关代码参看:
https://github.com/wangsun1983/Obotcha/blob/master/util/sql/include/SqlRecord.hpp