转自:http://blog.csdn.net/angelseyes/article/details/3962133
在C语言程序代码中直接嵌入SQL语句,使数据库编程变得非常简单明了,而且嵌入式SQL是一种标准,代码不需要很多的修改就能移植到支持嵌入式SQL的数据库系统上去,但这同时也是一个缺点,许多数据库系统不提供嵌入式SQL的预编译器。
1.1 编译
编译过程分为两步,第一步,对带有嵌入式SQL的C代码程序(通常此程序以.pc结尾,简称PC代码)使用proc做一次预编译,将里面的嵌入式SQL转化为代表数据库功能调用的C代码。第二步,使用C编译器将C代码编译连接成可执行文件。
配置文件
proc首先读取$ORACLE_HOME/precomp/pcscfg.cfg,里面的选项与命令行选项作用完全相同,这样可以让proc使用变得简洁。但从减少系统配置步骤、保持应用迁移的灵活性的角度出发,建议对此文件不作修改,将各种选项写在程序的编译文件中。
命令行选项:
sys_include:系统头文件目录,如/usr/include,/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include, /usr/lib/gcc-lib/i386-redhat-linux/2.96/include ,$ORACLE_HOME/precomp/public,$(ORACLE_HOME)/rdbms/public -I$(ORACLE_HOME)/rdbms/demo,前3个通常在配置文件中已经写入
include:自定义的应用头文件目录
sqlcheck:SQL语句检查方式,语法(SYNTAX)和语义(SEMANTICS)。
语法:仅检查SQL语句的语法结构
语义:除语法检查外,还检验数据库对象和宿主变量的有效性
userid:访问数据库途径,在sqlcheck为语义检查时必须指定,如dbuser/oracle@oradb
iname:输入pc文件名
oname:输出c文件名
如果省略iname、oname,只需输入pc文件名,缺省转化成同名后缀为.c的C文件。
threads:线程支持,yes或no
例子
proc sqlcheck=semantic userid=dbuser/oracle threads=yes sys_include=$(ORACLE_HOME)/precomp/public sys_include=$(ORACLE_HOME)/rdbms/public sys_include=$(ORACLE_HOME)/rdbms/demo include=$IN threads=yes iname=emp.pc oname=emp.c //$IN是自定义的应用文件目录
gcc -I$(ORACLE_HOME)/precomp/public -I$(ORACLE_HOME)/rdbms/public -I$(ORACLE_HOME)/rdbms/demo –g –c –o emp.o emp.c
参见proc/single/Makefile。
1.2 SQL语句
建议设立一个与表结构一一对应的结构定义,作为宿主结构,下面的所有例子几乎都会用到关于表emp的宿主结构(emp_t emp)。
参见proc/single/db.h中emp_t和emp建表脚本proc/single/emp.sql。
1.2.1 内部类型与宿主类型对应
下面是最常用的内部数据类型和宿主变量类型的对应关系:
内部数据类型 | 宿主变量类型 |
char(n) | char[n+1] |
varchar2(n) | char[n+1] |
number(n<=6) | int |
number(n>6) | double |
number(m,n) | double |
date | char[15] |
1.2.2 连接和断开
最简单的形式
这是最为常用的形式:应用程序不指明任何数据源,Oracle库从环境变量ORACLE_SID中得到数据库实例名,在本机连接此实例。应用程序只要输入数据库用户名和口令即可。
strcpy(dbuser,”dbuser”); strcpy(password,”oracle”);
EXEC SQL connect :dbuser identified by :password; //dbuser和password是char[n]的宿主变量。
也可以将用户名和口令写在一起。
strcpy(dbuser_password,”dbuser/oracle”);
EXEC SQL connect :dbuser_password;
跨机连接
首先在$ORACLE_HOME/network/admin/tnsnames.ora中添加对方数据库信息,取得一个连接名。
假定已经在tnsnames.ora中配好了名为oradb2的连接。
strcpy(dbstring, “oradb2”);
EXEC SQL connect :dbuser identified by :password using :dbstring;
多个连接
可以在一个应用程序中同时使用两个以上的数据库连接,为区分不同的连接,要为每个连接起一个名字。
strcpy(dbuser,”dbuser1”); strcpy(password,”oracle1”);
strcpy(dbconnect,”dbconnect1”);
EXEC SQL connect :dbuser identified by :password at :dbconnect;
strcpy(dbuser,”dbuser2”); strcpy(password,”oracle2”);
strcpy(dbconnect,”dbconnect2”);
EXEC SQL connect :dbuser identified by :password at :dbconnect;
在多连接的情况下,每句sql都要指明连接名。
EXEC SQL at :dbconnect select …; //下面不再讨论多连接,仅仅说明其使用方法
断开
应用程序无论正常还是非正常退出,数据库连接自动中断,也可以显式关闭。
EXEC SQL commit work release;
1.2.3 事务
提交
EXEC SQL commit;
回滚
EXEC SQL rollback;
1.2.4 标准SQL语句
select
EXEC SQL * into :emp from emp where no=:emp->no;
EXEC SQL select name,to_char(upd_ts,’yyyymmddhh24miss’) into :emp->name,:emp->upd_ts from emp where no=:emp->no;
lock
EXEC SQL select * into :emp from emp where no=:emp->no for update;
update
EXEC SQL update emp set duty=:emp->duty,upd_ts=sysdate where no=:emp->no;
delete
EXEC SQL delete from emp where no=:emp->no;
insert
EXEC SQL insert into emp values (:emp);
EXEC SQL insert into emp values (:emp->name,:emp-> name,:emp->age,:emp->duty,:emp->salary,:emp->to_date(upd_ts,’yyyymmddhh24miss’));
cursor
EXEC SQL declare emp_cur cursor for select * from emp where age>25;
EXEC SQL open emp_cur;
EXEC SQL fetch emp_cur into :emp;
EXEC SQL close emp_cur;
1.2.5 动态SQL语句
在输出域或输入查询条件,甚至表名,不能事先确定的时候,可以引入动态sql语句的方法。
非查询语句
专指insert,update,delete等无结果集的sql语句。
不带宿主变量:
最直接的做法,处理一条纯粹的sql语句,不需要任何变化,每次都完整地从sql解析走到执行。
strcpy(sql,”delete from emp where no=0”);
EXEC SQL executeimmediate :sql;
查询条件使用宿主变量:
目的是为了只进行一次解析,以后只需绑定不同输入就能直接执行。
strcpy(sql,”delete from emp where no=:no”);
EXEC SQL prepare sql_stmt from :sql;
emp.no=0;
EXEC SQL execute sql_stmt using :emp.no;
emp.no=1;
EXEC SQL execute sql_stmt using :emp.no;
查询语句
查询必须使用游标方式。
strcpy(sql,”select * from emp where duty=:duty and age>:age”);
strcpy(emp.duty,”1”); emp.age=20;
EXEC SQL prepare sql_stmt from :sql;
EXEC SQL declare sql_cur cursor for sql_stmt;
EXEC SQL open sql_cur using :emp.duty,:emp.age;
EXEC SQL fetch sql_cur into :emp;
EXEC SQL close sql_cur;
1.2.6 数组操作
利用宿主数组可以用一次sql操作处理成批记录,降低了应用程序与oracle服务进程之间的通讯开销,也减少了sql语句解析次数,这样能提高数据库应用程序的效率,对update,delete和insert尤为有效。
宿主数组
假定要操作表emp,定义宿主数组:
double a_no[50]
char a_name[50][21];
CURSOR
目的在于:试图在一次fetch中选出批量数据以减少fetch的次数。但fetch过程本身消耗资源不大,所以使用宿主数组对效率提高有限。
int rec_count; //本次条数
int accu_count; //累计条数
EXEC SQL declare emp_cur FOR select no,name from emp where age>25;
EXEC SQL open emp_cur;
EXEC SQL fetch emp_cur into :a_no,:a_name; //rec_count保存当前fetch的记录条数,最多为a_no,a_name中较少的结构个数
rec_count=sqlca.sqlerr[2]-accu_account; //sqlca.sqlerr[2]记录cursor累积读取的记录条数,这样与上次累积数之差就是本次读取的条数
accu_count=sqlca.sqlerr[2]; // accu_count保存累积fetch的记录条数
当最后一次取完时,sqlca.sqlcode为1403,即记录不存在,注意count可能会大于0,这里的“记录不存在”只是表明“没有填满宿主结构”。
SELECT
EXEC SQL select no,name into :a_no,:a_name from emp where age>25;
不能用for :rec_count子句进行操作记录条数控制,即不可指定选出若干条记录,因为有“执行此语句若干次”和“一次选出若干条记录”的歧义,需要条数控制应使用CURSOR。
DELETE
在a_no中准备N条待删除记录的no,也就是主键。
a_no[0]=0; a_no[1]=1; a_no[2]=2; …
rec_count=N;
EXEC SQL for :rec_count delete from emp where no=:a_no;
UPDATE
假定要更新一部分记录的name域。
在a_no中准备N条待更新记录的no,在a_name中准备N个新的name,两者必须一一对应。
a_no[0]=0; strcpy(a_name[0],”职员0”);
a_no[1]=1; strcpy(a_name[1],”职员1”);
…
a_no[N-1]=N-1; strcpy(a_name[N-1],”职员N-1”);
rec_count=N;
EXEC SQL for :rec_count update emp set name=:a_name where no=:a_no;
INSERT
为了说明问题,这里对emp表作了一些改变,假定除no和name域外都有缺省值。
在a_no,a_name中准备N条记录的no和name,两者必须一一对应。
a_no[0]=0; strcpy(a_name[0],”职员0”);
a_no[1]=1; strcpy(a_name[1],”职员1”);
…
a_no[N-1]=N-1; strcpy(a_name[N-1],”职员N-1”);
rec_count=N;
EXEC SQL for :rec_count insert into emp(no,name) values(:a_no,:a_name);
宿主结构数组
宿主结构数组其实是宿主变量组合起来的数组,由于update set语句的特殊性,所以宿主结构数组仅能用于SELECT,FETCH和INSERT
定义部分域结构组合emp_part_def a_emp_part[50],和a_no[50]形成混合模式。
double a_no[50];
typedef struct
{
char name [21];
int age;
} emp_part_def;
emp_part_def a_emp_part[50];
SELECT
EXEC SQL select no,name,age into :a_no,:a_emp_part from emp where age>25;
CURSOR
iAccuCount=0;
EXEC SQL DECLARE emp_part_cur FOR select name,age from emp where age>25;
EXEC SQL OPEN emp_part_cur;
EXEC SQL FETCH emp_part_cur into :a_emp_part;
rec_count=sqlca.sqlerr[2]-accu_count;
accu_count=sqlca.sqlerr[2];
INSERT
emp_def a_emp[50];
在a_emp中准备N条记录。
a_emp[0].no=0; strcpy(a_emp[0].upd_ts,”20020101000000”); …
a_emp[1].no=1; strcpy(a_emp[1].upd_ts,”20020101000000”); …
…
a_emp[N-1].no=N-1; strcpy(a_emp[N-1].upd_ts,”20020101000000”); …
rec_count=N;
EXEC SQL for :rec_count insert into emp values(:a_emp);
1.3 编程框架
1.3.1 总体原则
为了使程序模块划分更加简洁清晰,提高应用程序的可移植性,所以采取将主程序模块和数据库功能模块分离的方法,即不在主程序逻辑中出现嵌入式sql语句,而把这些语句归并到数据库功能模块中去,对主程序而言,展现的是某种功能。同时在数据库模块上再进一步把通用功能和定制功能分割开来。大致框架如下:
定制功能PC模块 |
通用功能头文件 |
定制功能头文件 |
通用功能PC模块 |
主程序 |
可执行文件 |
应用部分 |
数据库部分 |
连接 |
定制功能C模块 |
通用功能C模块 |
通用功能头文件和通用功能模块参见proc/single /dbcom.h和proc/single/dbcom.pc,定制功能头文件和定制功能模块参见proc/single/dbfunc.h和proc /single/dbfunc.pc,编译连接方法参见proc/single/Makefile。
1.3.2 单线程和多线程
由于嵌入式SQL语句被预编译成全局性的C数据结构和函数调用,所以在单线程条件下不会有任何执行问题,程序编写十分简单,参见proc/single/。
而多线程条件就复杂很多,为了保证多个线程能够安全互斥地使用全局数据结构,必须给每个线程一个表明身份的上下文标志(context),在进行sql操作时彼此能区别开来。
每个线程使用一个数据库连接,凭借自己的id使用专有的数据结构,包括上下文标志(context)等,此数据结构是数据库连接数组(a_thrd_conn)的一个成员,参见dbcom.h中thrd_conn_t和dbcom.c中a_thrd_conn。这样做的目的在于:统一存放连接有关数据,使线程引用变得简单。如下所示:
数据库连接数组(thrd_conn_t a_thrd_conn) |
上下文标志 |
index=1 |
index=2 |
线程1 |
线程2 |
数据库连接1 |
数据库连接2 |
引用专有数据 |
加入多线程支持
在proc的命令行或配置文件pcscfg.cfg中写入threads=yes,表明为待编译的程序提供thread-safe功能,参见proc/multi/Makefile。
proc thread=yes …
应用程序中最前面必须指明本程序支持多线程,参见proc/multi/multi.c和proc/multi/dbcom.pc中DbsEnableThread。
EXEC SQL enable threads;
代码大致流程
每个线程使用线程编号(thrd_index)引用数据库连接数组。
在建立数据库连接的时候,分配上下文标志,参见proc/multi/dbcom.pc中DbsConnectThread。
EXEC SQL context allocate :a_thrd_conn[thrd_index].context;
断开时,释放上下文标志,参见DbsDisconnThread。
EXEC SQL context free :a_thrd_conn[thrd_index].context;
在每次sql操作前使用上下文标志(由于proc的一些原因,threads=yes条件下的sql语句在被proc转换时使用上面离其最近的use所指明的context,所以保险做法是每句sql语句前使用use)。
EXEC SQL context use : a_thrd_conn[thrd_index].context
EXEC SQL update emp set …;