飞艳小屋

程序--人生--哲学___________________欢迎艳儿的加入

BlogJava 首页 新随笔 联系 聚合 管理
  52 Posts :: 175 Stories :: 107 Comments :: 0 Trackbacks

ORACLE Advanced SQL
这篇文档对深入研究Oracle有很大的用处。下面分别从以下几个方面介绍。
Transaction Management,joins,Subquerys,Optimizer,Indexs, Enhancement to other SQL operations
如果您身边就有Oracle系统,那么您也可以通过运行本篇文章的例子来做一些试验。
l 事务处理(Transaction Management):
事务的四大特性原子性、隔离性、独立性,持续性。仅仅通过字面理解理解事务处理可能不是太直观。那么,我们可以借用Jim Gray的描述来理解——“ 事务概念是分布式计算的一个重要抽象。在某一个层次实现了事务,那么所有跟高层次上会有一个简化的失败语义(要么全做,要么全不做),并且错误处理也会简单得多。 ”
在Oracle中通过设置ISOLATION LEVEL来避免几乎所有数据库都可能遇到的问题——脏读(一个事务读取了另一个事务写的但是尚未提交的数据)、不一致分析(一个事务前后读取了不同的数据)和幻象数据读取(例如,我要执行一个事务查询,该事务查询的任务是查询13:00这个时间点的数据,但是这个事务执行时间可能需要一个小时,但是在这期间由于还有正常的数据操作。所以,当我在执行完事务查询时可能得到的数据就是14: 00的数据了,我们称这种现象为幻象读取)。
我们知道标准的SQL99提出的事务隔离级别有四种,分别是READ UNCOMMITED、 READ COMMITED、REPEATABLE READ、SERIALIZABLE。ORACLE分别实现了READ COMMITED和 SERIALIZABLE。从这一点当中我们可以看出,ORACLE默认是避免脏读的(当然这也不一定如果在执行完DML语句完以后如果没有做 COMMIT操作可能也会出现脏读的情况。但是,我还没有遇到这种情况,读者可以自己试验。)例句  SET TRANSACTION ISOLATION LEVEL [READ COMMITED| SERIALIZABLE]。 READ COMMITED可以避免脏读,这也是ORACLE的默认选项。但是,它不可避免我们提出的不一致分析和幻象读取的问题。那么我们可以将其隔离级别设置为SERIALIZABLE这样我们就可以避免不一致分析和幻象读取的问题。那么,既然SERIALIZABLE级别这么好,那么我们是不是任何事务都要设置呢?答案是否定的,我们知道世界万物都是有其有利的一面就一定有其不利的一面。那么它的不利一面我们可以通过分析SERIALIZABLE的实现机制来看看。
 
从图中可以看出我们要实现SERIALIZABLE需要在系统中建立一个UNDO表空间来存放已经过时的数据。我们可以根据我们最大执行事务的执行时间来估算一个UNDO段的大小。显然,我们要是实现SERIALIZABLE就必须要另外的空间来建立UNDO表空间。也就是说我们要实现SERIALIZABLE是通过空间来换取避免不一致分析和幻象读取的。
l Joins操作
在叙述下面的内容之前先介绍一下我的机器环境:
硬件环境:P4 2.8G,MEMORY 526M,80G硬盘。
软件环境:Windows 2000,ORACLE 9.2.0.1
首先您要建立一个有足够量数据的表,我建立一个有100万行以上的数据的表
建表sql语句如下:
DROP SEQUENCE SEQ_EMP
/
CREATE SEQUENCE SEQ_EMP START WITH 1000000
/
DROP TABLE EMPLOYEE
/
CREATE TABLE EMPLOYEE
(EMPNO NUMBER(8),
 ENAME VARCHAR2(10),
 SAL   NUMBER(5),
 COMM  NUMBER(5),
 DEGREE NUMBER(1),
 HIREDATE DATE,
 DEPTNO NUMBER(2),
 ID_NO  VARCHAR2(18),
 BIRTHDAY DATE,
 CONSTRAINT PK_EMPLOYEE PRIMARY KEY(EMPNO)
)
/
CREATE OR REPLACE TRIGGER TRI_INS_EMPLOYEE 
 BEFORE INSERT ON EMPLOYEE
 FOR EACH ROW
DECLARE
 V_EN EMPLOYEE.EMPNO%TYPE;
BEGIN
 V_EN:=:NEW.EMPNO;
 
  :NEW.COMM:=CEIL(:NEW.EMPNO/500000)*1000;
  :NEW.SAL:=(4-MOD(:NEW.EMPNO,4))*1000;
  :NEW.DEGREE:=CASE WHEN MOD(:NEW.EMPNO,4)=0 THEN NULL 
                   WHEN MOD(:NEW.EMPNO,10)<7 THEN 1
                   WHEN MOD(:NEW.EMPNO,10)<9 THEN 2
                   ELSE 3
              END;
  :NEW.DEPTNO:=CASE WHEN :NEW.EMPNO<1000000 THEN 10
                    WHEN :NEW.EMPNO<1500000 THEN 20
                    WHEN :NEW.EMPNO<2000000 THEN 30
                    ELSE 40
               END;
  :NEW.ID_NO:=REPLACE('21010119'||(7+MOD(V_EN,3))||MOD(V_EN,10)||TO_CHAR(1+MOD(V_EN,12),'00')||TO_CHAR(MOD(V_EN,20)+1,'00')||0||TO_CHAR(MOD(V_EN,100),'00')||MOD(V_EN,2),' ',NULL);

  :NEW.ID_NO:=REPLACE(:NEW.ID_NO,' ',NULL);

 :NEW.BIRTHDAY:=TO_DATE(SUBSTR(:NEW.ID_NO,7,8),'YYYYMMDD');
END;
/
INSERT INTO EMPLOYEE (EMPNO)
 VALUES(SEQ_EMP.NEXTVAL)
/
/
INSERT INTO EMPLOYEE (EMPNO) (SELECT SEQ_EMP.NEXTVAL FROM EMPLOYEE)
/
    在做好以上准备以后我们就可以进行我们以后的实验了。
在ORACLE中包括很多中连接方式EQUIJOINS、 SELF JOINS、 CARTESIAN PRODUCTS、 INNER JOINS、 OUTER JOINS、 ANTIJOINS, SEMIJOINS。
由于我的个人水平有限我不能全部介绍,我只能将自己理解的内容介绍个大家。
下面就仅仅介绍有关反连接的问题。
反连接(ANTIJOINS)通过 SET AUTOTRACE TRACE打开跟踪执行策略我们分别比较以下的语句。
============================================================
SELECT ENAME FROM EMP WHERE DEPTNO NOT IN(
   SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');

SELECT ENAME FROM EMP WHERE NOT EXISTS(
   SELECT 1 FROM DEPT WHERE DEPT.DEPTNO=EMP.DEPTNO AND LOC='NEW YORK');
我们可以发现,运用NOT IN 要比 NOT EXISTS要快一些但是有个比较大的问题,就是在上面的实验中我们假设EMP表和DEPT表中没有字段内容为NULL的数据。如果我们要查询的表中有为NULL的数据的时候,那么我们在运用NOT IN操作会发现查出的数据是不准确的情况。结论:在您为选择 NOT IN 和 NOT EXISTS时而苦恼时请优先选择运用NOT EXISTS语句。
接下来是有关JOIN的一些实验。
下面我们做如下试验
比较JOIN与IN & 子查询以及EXISTS & 子查询 它们三者之间的执行效率

--12.04秒
SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
--4.02秒
SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
--4.02秒
SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
DEPT.DEPTNO AND LOC='NEW YORK');

通过上面的实验我们可以得出运用 IN或者 EXISTS操作的效率要高于运用JOIN操作的语句。我们知道ORACLE在执行用户的查询请求是 ORACLE会选择一些查询策略来完成操作,但是有时候ORACLE的选择是很愚蠢的。比如,我要做一个简单的连接查询的操作 SELECT * FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;通过跟踪执行策略我们会发现 ORACLE可能选择一个很慢的策略。那么,遇到这种情况该如何处理呢?我们可以通过两种方式来处理。第一,我们可以为该查询添加注释让它执行我们要求的策略。比如我们可以这么写SELECT /*+ use_rule…(EMPLOYEE EMP)  */* FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;(use_rule您可以选择您需要的策略。例如HASH JOIN等其他的执行策略)。例如,我们为上面这个查询语句选择下面两种策略:
1. SELECT /*+USE_HASH(EMPLOYEE,DEPT)*/* FROM EMPLOYEE,DEPT WHERE EMPLOYEE.DEPTNO=DEPT.DEPTNO;
2. SELECT /*+USE_MERGE(EMP,DEPT)*/* FROM EMP JOIN DEPT ON EMP.DEPTNO=DEPT.DEPTNO;
第二,就是,为该表添加统计资料分析信息ANALYZE TABLE EMPLOYEE COMPUTE STATISTICS;这样我们在对该表查询时ORACLE就可以选择一个较优的执行策略了。
通过以上的实验我们可以得出以下结论:ORACLE系统在执行DML语句的时候可能不会选择其最优的执行策略,这需要我们通过为DML语句添加注释强迫 ORACLE选择一个我们人为指定的执行策略或者通过为表、试图等添加统计资料分析信息来使ORACLE在执行DML时选择较优的执行策略。
l 子查询(Subquery)
这部分内容我们将会做一系列的实验,最后我们从实验的结果当中得出使用子查询的情况。我并不是要介绍如何使用子查询,而是通过使用子查询来分析ORACLE的执行策略等信息。
实验1:比较JOIN、IN以及EXISTS三种执行方式的执行时间
--12.04秒
SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
--4.02秒
SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
--4.02秒
SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
DEPT.DEPTNO AND LOC='NEW YORK');

通过实验1我们得出结论:使用EXISTS、IN语句要比JOIN语句执行效率高。


实验2:表较几个语句的查询效率

--27.07秒
SELECT * FROM EMPLOYEE 
WHERE SAL>(SELECT AVG(SAL) FROM EMPLOYEE);

--27.03秒
SELECT * FROM EMPLOYEE A 
WHERE SAL>
(SELECT AVG(SAL) FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);

--39秒
SELECT * FROM EMPLOYEE A,
(SELECT DEPTNO,AVG(SAL) AS SAL1 
FROM EMPLOYEE GROUP BY DEPTNO) B
WHERE A.DEPTNO=B.DEPTNO AND 
A.SAL>B.SAL1 WHERE SAL>(SELECT AVG(SAL)
 FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);

--22.05秒
SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
YY.SAL-XX.SAL1 AS NEWSAL FROM EMPLOYEE YY,
(SELECT DEPTNO,AVG(SAL) AS SAL1 FROM EMPLOYEE 
GROUP BY DEPTNO) XX 
WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;

--26.06秒
SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
YY.SAL-XX.SAL1 AS NEWSAL,XX.SAL1,XX.SAL2 
FROM EMPLOYEE YY,
(SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO) XX 
WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;

通过实验2我们可以得出结论:
1. 在执行子查询尽可能将语句向连接的方式上靠。
2. 在子查询中可以将相关子查询转为连接的形式。
3. 在做子查询时select * 要比select 列名 的语句慢得多。
另外,在ORACLE中我们也可以运用WITH语句代替子查询以提高语句的可读性。下面是运用with语句的例子。
实验3:运用WITH语句
WITH 
  SUB_TABLE AS (
    SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO)
SELECT A.EMPNO,A.ENAME,E.DEPTNO,A.SAL,A.SAL-SUB_TABLE.SAL11,
        SUB_TABLE.SAL1,SUB_TABLE.SAL2
 FROM EMPLOYEE A,SUB_TABLE WHERE 
    A.DEPTNO=SUB_TABLE.DEPTNO AND A.SAL>SUB_TABLE.SAL1;

WITH 
SUB_TABLE AS (
  SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 
FROM EMPLOYEE GROUP BY DEPTNO)
SELECT *     
FROM EMPLOYEE,SUB_TABLE WHERE 
EMPLOYEE.DEPTNO=SUB_TABLE.DEPTNO AND EMPLOYEE.SAL>SUB_TABLE.SAL1;
l 索引(Index)
我们知道索引的使用可以提高查询速度,这是我以前对索引的理解。但是使用索引也有副作用。我们知道在ORACEL中查询一条记录时如果没有索引的情况下,它的执行方式如下所示
 
如果我要做查询 select C2 from 表 where C2=6 那么在ORACLE中系统会从最后一条扫描一直将表整个扫描一遍以后,这个查询动作才算完成。从中我们可以看出没有索引的表的记录是无序存放的。相反如果我们对这个表在列C2建立一个索引以后它的查询执行如下所示:
 
我们可以看到在查询是系统如果选用该索引的话那么ORACLE将会查找一些有序的数据,那么我们的查询速度将会大大地提高。
上面我们描述的是查询一列数据时的情况,那么如果查询所有数据呢,请看下图所示
 
如果我们带上索引查询,ORACLE首先会找到索引然后找到在基表中记录的位置。显然这样比直接在表中查询要慢。但是这个结论对不对呢?下面将会做一些实验说明这个问题。
在进行以下的实验值前首先,您需要作如下准备:
在EMPLOYEE表的SAL列上建立一个索引
CREATE INDEX IND_EMPLOYEE_SAL ON EMPLOYEE(SAL);
在EMPLOYEE表的SAL,DEGREE两个列上建立一个联合索引
CREATE INDEX IND_EMPLOYEE_SALandDEGREE 
ON EMPLOYEE(SAL,DEGREE);
实验1:使用索引与不使用索引

SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
   EMPLOYEE WHERE SAL=1000;
--13.03秒
SELECT /*+NO_INDEX(EMPLOYEE)*/
* FROM 
   EMPLOYEE WHERE SAL=1000;
利用索引查询的时间的数据由于我的粗心大意弄丢了,但是我记得运用索引的查询要比没有运用索引的查询要慢一些。

实验2:单列索引与多列索引之间的区别
//////////////////////////////////////////////////
////单列索引的情况
--13.04秒 USE INDEX IND_EMPLOYEE_SAL
SELECT * FROM EMPLOYEE WHERE SAL=1000;

--13.04秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE SAL=1000;

--19秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM 
    EMPLOYEE WHERE SAL=1000;

//////////////////////////////////////////////////
////单列索引的情况
--22秒   USE TABLE ACCESS 
SELECT * FROM EMPLOYEE WHERE DEGREE=1;




--22秒     USE TABLE ACCESS 
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE DEGREE=1;

--29秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM 
    EMPLOYEE WHERE DEGREE=1;

//////////////////////////////////////////////////
////多列索引的情况
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT * FROM EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

--9秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM 
    EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

//////////////////////////////////////////////////
////多列索引的情况
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT * FROM EMPLOYEE WHERE DEGREE=1 AND SAL=1000;

--9秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE DEGREE=1 AND SAL=1000;



--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM 
    EMPLOYEE WHERE DEGREE=1 AND SAL=1000;

//////////////////////////////////////////////////
////多列索引的情况
--3.4秒 USE INDEX IND_EMPLOYEE_SAL
SELECT * FROM EMPLOYEE WHERE SAL=1000 AND COMM=2000;

--3.4秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE SAL=1000 AND COMM=2000;

--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM 
    EMPLOYEE WHERE SAL=1000 AND COMM=2000;

实验3:CLUSTER
CREATE CLUSTER DD_DDMX (ID NUMBER(5)) 
TABLESPACE users ;

CREATE TABLE DD
(ID NUMBER(5) PRIMARY KEY,
 DDTIME DATE)
CLUSTER DD_DDMX(ID);

CREATE TABLE DDMX
(ID NUMBER(5),
 PROID VARCHAR2(20),
 PRO VARCHAR2(30))
CLUSTER DD_DDMX(ID);

CREATE INDEX DD_DDMX_index 
  ON CLUSTER DD_DDMX
  TABLESPACE indx;
实验4:BITMAP INDEX
CREATE BITMAP INDEX INDBIT_EMPLOYEE_SAL ON EMPLOYEE(SAL)
TABLESPACE INDX;

--13.04秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE SAL=1000;

--12.07秒  USE INDBIT_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE INDBIT_EMPLOYEE_SAL)*/
* FROM 
    EMPLOYEE WHERE SAL=1000;
实验5:分区索引
分区索引的原理如下所示:

 
我们知道在磁道在磁盘上寻址的I/O操作的开销是相当大的,如果我们建立了分区索引将其索引放在不同的磁盘上那么可以大大节省I/0开销提高我们的查询速度。


ALTER TABLE EMP
  ADD CONSTRAINT PK_EMP
PRIMARY KEY(EMPNO) USEING INDEX TABLESPACE INDX;
ALTER TABLE EMPLOYEE
  DROP CONSTRAINT PK_EMPLOYEE;

ALTER TABLE EMPLOYEE
  ADD CONSTRAINT PK_EMPLOYEE
PRIMARY KEY(EMPNO) USING INDEX TABLESPACE INDX;

CREATE INDEX IND_EMPLOYEE_BIRTHDAY 
  ON EMPLOYEE(BIRTHDAY)
GLOBAL PARTITION BY RANGE(BIRTHDAY)
(PARTITION P1 VALUES LESS THAN (DATE '1980-01-01') TABLESPACE USERS,
 PARTITION P2 VALUES LESS THAN (DATE '1990-01-01') TABLESPACE INDX,
 PARTITION P3 VALUES LESS THAN (DATE '2000-01-01') TABLESPACE USERS,
 PARTITION P4 VALUES LESS THAN(MAXVALUE) TABLESPACE INDX);

CREATE TABLE wage
    ( empno              NUMBER, 
      year_month     INT NOT NULL,
      opdate              DATE)
  PARTITION BY RANGE (year_month)
    ( PARTITION wage_q1 VALUES LESS THAN (199701) 
        TABLESPACE users,
      PARTITION wage_q2 VALUES LESS THAN (199702) 
        TABLESPACE users,
      PARTITION wage_q3 VALUES LESS THAN (199703) 
        TABLESPACE users,
      PARTITION sales_q4 VALUES LESS THAN (199704) 
        TABLESPACE users); 
-- Local Partitioned Index
CREATE INDEX IND_WAGE(year_month)
LOCAL
 (PARTITION P1,
  PARTITION P2,
  PARTITION P3,
  PARTITION P4);

以上是我们关于索引的一些实验信息,通过该实验我们可以得出以下结论:
1. 索引是为了提高查询速度,排序数据。
2. 索引不见得总能提高速度。
3. 查询结果占总记录数越少越可以用索引。
4. 分区索引 将数据分散到多个物理位置上来提高其IO的功能。
5. 全表扫描有时性能也不错。
6. 在统计资料不完整时未必选择正确的索引。
7. 有时候添加索引可能会降低速度。
8. 索引适合于结果行占所有行的比很小的时候运用 经验值为比为5%左右为好,但是还可能还有许多其他情况影响查询速度。
9. 值是唯一的时候运用索引可以提高查询速度。
10. 当统计资料不完整的时候,查询速度会很慢。可以通过对索引进行统计分析来调整。
11. where条件中列的顺序不影响其查询速度。
12. 多列索引中的后面的列最好不要运用索引。
13. 多列索引中其列的顺序是不同的。不同的顺序可能造成查询速度不一样。
14. 多列索引中第一列是有顺序的,但是其后面的列是没有顺序。
15. where条件查询的列中只要有索引,那么oracle会选择运用索引。
16. 当查询的结果大于所有行的记录数的10%时,那么最好不要运用索引。
17. 小表用索引反而会降低速度。
18. 索引需要DBA经常删除重新建立,因为索引结构变化过多可能造成以后的查询变慢。
19. DML语句会影响索引结构大的变化,所以经常作DML语句的表可以考虑不用索引。
20. CLUSTER索引仅仅用于只读的的表多用于cluster表。
21. 维图索引多用于行多但是值的类型很少的情况。
22. 表和索引要放在两个不同的表空间上。
l Enhancement to other SQL operations
下面的内容是介绍一些有关ORACLE技术的一些比较杂的内容。
一、 层次查询
在同一个表中的不同记录有着直接或者间接的关系。
例如,我们查询EMP表的信息如下:
  从图中我们可以看到ENAME是’SMITH’的领导是编号为7902的人。而7902的领导是编号为7566的人。7566的领导是编号为7839的人。那么这样的关系我们就可以通过运用层次查询就可以查询出来。
层次查询实验
SELECT * FROM EMP
CONNECT BY PRIOR MGR=EMPNO
  START WITH ENAME='SMITH';

SELECT * FROM EMP
CONNECT BY PRIOR EMPNO=MGR
  START WITH ENAME='SMITH';

SELECT LEVEL,ENAME FROM EMP
CONNECT BY PRIOR EMPNO=MGR
  START WITH ENAME='SMITH';

SELECT MID,PARENTID,AMOUNT,SYS_CONNECT_BY_PATH(MID,'/') PATH
FROM MATERIAL
   WHERE STOCK=1
        CONNECT BY PRIOR MID=PARENTID
                          START WITH MID='1001';
二、 分组查询
就是我们平时运用的group语句.
分组查询实验
SELECT DEPTNO,JOB,COUNT(*)
  FROM EMP
   GROUP BY ROLLUP(DEPTNO,JOB);

SELECT DEPTNO,JOB,COUNT(*)
  FROM EMP
   GROUP BY cube(DEPTNO,JOB);

SELECT DEPTNO,JOB,TO_CHAR(HIREDATE,'YYYY'),COUNT(*)
  FROM EMP
   GROUP BY GROUPING SETS((DEPTNO,JOB),
(DEPTNO,TO_CHAR(HIREDATE,'YYYY')),());
三、 并行执行的SQL语句
并行执行的SQL语句实验

--19.09秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 1);
SELECT * FROM EMPLOYEE WHERE SAL=1000;

--18秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 2);
SELECT * FROM EMPLOYEE WHERE SAL=1000;

--19秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 4);
SELECT * FROM EMPLOYEE WHERE SAL=1000;

--20.3秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 6);
SELECT * FROM EMPLOYEE WHERE SAL=1000;

--19秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 10);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
四、 实体化视图
在进行以下的实验之前,我们需要作如下准备:
建立一个普通视图:
CREATE VIEW V_EMPLOYEE
AS SELECT SUM(SAL) AS C FROM EMPLOYEE;
建立实体化视图:
CREATE MATERIALIZED VIEW 名字 AS 查询;

CREATE MATERIALIZED VIEW V_M_EMPLOYEE 
        AS SELECT SUM(COMM) AS C FROM EMPLOYEE;
实体化视图与普通视图的比较

--20.04秒
SELECT * FROM V_EMP;

--0.01秒
SELECT * FROM V_M_EMPLOYEE;

通过以上的实验,我们可以得出结论:实体化视图不包含新的数据,查询速度很快。
如果需要实体化视图包含新的数据我们可以通过
手工刷新:
   EXEC DBMS_MVIEW.REFRESH('V_M_EMPLOYEE','CF');  CF -- 完全快速刷新
        自动刷新:
//每个表可以创建一个实体化视图日志。
CREATE MATERIALIZED VIEW LOG ON 表名
WITH(列名列表),
    ROWID INCLUDING NEW VALUES;
       //创建自动刷新的实体化视图
        CREATE MATERIALIZED VIEW 名字
        BUILD IMMEDIATE
〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT 
--REFRESH FAST 仅仅支持Insert语句的刷新
             AS ...
运用REFRESH COMPLETE 适合于update,delete操作较少的表。并且试验发现运用
REFRESH COMPLETE时COMMIT操作会很慢!
五、 查询重写技术(QUERY REWRITE)
表面上看是在查询表,但是oracle实际上是去查询实体化视图的技术。
在下面的试验中我们也可以看出实际上无论是查询表还是视图oracle都会转向查询实体化视图 MV_EMP。
对于反复执行的汇总查询存放起来、节省查询时间,多用于汇总的计算。
      
//创建查询重写技术
        CREATE MATERIALIZED VIEW 名字
               BUILD IMMEDIATE
    〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT 
--REFRESH FAST 仅仅支持Insert语句的刷新
        ENABLE QUERY REWRITE
             AS ...
运用查询重写技术的例子
CREATE MATERIALIZED VIEW MV_EMP
    BUILD IMMEDIATE
        REFRESH FAST ON COMMIT
           ENABLE QUERY REWRITE
                AS SELECT SUM(COMM) AS C FROM EMPLOYEE;

ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE;

--0.00秒
-- EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
SELECT SUM(COMM) FROM EMPLOYEE;

--0.00秒
--EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
SELECT * FROM V_MEP;

经过手工刷新以后查询速度会变慢!
六、 分布式技术
分布式技术包括分布式数据库、DB LINK、分布式查询以及分布式事务管理。这里我们仅仅介绍有关DB LINK的内容。
DB LINK的实验
GRANT CREATE DATABALSE LINK TO SCOTT;

CONN SCOTT/TIGER

CREATE DATABASLE LINK OEM_LINK
  CONNECT TO SCOTT IDENTIFIED BY TIGER
    USING 'AAA';

SELECT ENAME FROM EMP@OEM_LINK

通过以上实验我们得出结论:如果在执行分布式事务的时候那么网络一定要保持正常,如果网络断开就会发生死锁的情况。这时要进行在服务端和客户端分别将锁删除即可解决。


七、 DML语句
INSERT INTO ALL
   INTO 表1 VALUES(列名列表)
   ...
   INTO 表2 VALUES(列名列表)
子查询;

DML语句的实验
CREATE TABLE TEST_DWRS
(DEPTNO NUMBER(2),
 RENSHU NUMBER(10)
);

CREATE TABLE TEST_DWGZ
(DEPTNO NUMBER(2),
 AVGSAL NUMBER(7,2)
);

INSERT ALL
  INTO TEST_DWRS VALUES (DEPTNO,RENSHU)
  INTO TEST_DWGZ VALUES (DEPTNO,AVGSAL)
SELECT DEPTNO DEPT,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
  FROM EMP GROUP BY DEPTNO;

INSERT ALL
  WHEN 条件1 THEN
     INTO 表名1 VALUES(列名列表)
  ...
  WHEN 条件2 THEN
     INTO 表名2 VALUES(列名列表)
子查询;
INSERT ALL
  WHEN REN_SHU>1000 THEN
   INTO TEST_DWRS VALUES(DEPTNO,RENSHU)
  WHEN AVG_SAL>2000 THEN
INTO TEST_DWGZ VALUES(DEPTNO,AVGSAL)
SELECT DEPTNO,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
  FROM EMP GROUP BY DEPTNO;
八、 外部表(EXTERNAL TABLES)
外部表是我们应用中可能会常常碰到的问题之一。例如,我需要将文本文件的数据导入到ORACLE数据库中我们就可以利用此项技术。
假设外部文件有如下信息:
 
我们可以将此信息转为以逗号分割的一些信息存为test.txt文本文件。
然后我们在ORACLE中建立一个订单表BILL(BILL_ID 订单编号,BILL_D订单日期)
以及订单明细表BILL_MX(BILL_ID 订单编号,P 产品编号,PAMOUNT 产品数量)。
我们下面的实验就是通过将外部的文件信息导入到BILL表和BILL_MX表中去。
首先简单介绍一下创建外部表的方法:
第一步,创建一个DIRECTORY对象
CREATE DIRECTORY 名字 AS '路径'
        CREATE DIRECTORY test_dir AS 'D:\1';
第二步,创建外部表

外部表的实验
//创建外部表
CREATE TABLE BILL_EXTERNAL
(BILL_ID VARCHAR2(8),
 BILL_D DATE,
 P1 VARCHAR2(10),
 P1_AMOUNT VARCHAR2(10),
 P2 VARCHAR2(10),
 P2_AMOUNT VARCHAR2(10),
 P3 VARCHAR2(10),
 P3_AMOUNT VARCHAR2(10))
ORGANIZATION EXTERNAL
(TYPE ORACLE_LOADER
 DEFAULT DIRECTORY test_dir
 ACCESS PARAMETERS
  (RECORDS DELIMITED BY NEWLINE
   FIELDS TERMINATED BY ','
   (BILL_ID CHAR,
    BILL_D CHAR DATE_FORMAT 
        DATE MASK "YYYYMMDD",
    P1 CHAR,
    P1_AMOUNT CHAR,
    P2 CHAR,
    P2_AMOUNT CHAR,
    P3 CHAR,
    P3_AMOUNT CHAR
      
  
  LOCATION ('TEST.TXT')
);

//导入数据
INSERT ALL
  WHEN BILL_ID<>0 THEN
   INTO BILL VALUES(BILL_ID,BILL_D)
  WHEN P1<>0 THEN
        INTO BILL_MX VALUES(BILL_ID,P1,P1_AMOUNT)
  WHEN P2<>0 THEN
        INTO BILL_MX VALUES(BILL_ID,P2,P2_AMOUNT)
  WHEN P3<>0 THEN
        INTO BILL_MX VALUES(BILL_ID,P3,P3_AMOUNT)
SELECT * FROM BILL_EXTERNAL;
九、 日期类型
日期类型的实验

SELECT SYSDATE + TO_YMINTERVAL('01-02') FROM DUAL;
SELECT SYSTIMESTAMP + TO_DSINTERVAL('01 01:02:01') FROM DUAL;

SELECT SYSTIMESTAMP + TO_YMINTERVAL('10-3') +
       TO_DSINTERVAL('05 07:00:00') FROM DUAL;
十、 自定义数据类型
自定义数据类型我们将会简单地介绍有关记录和集合的内容。
记录类型的实验

ORACLE 对象可以理解为JAVA中的类的概念。
   


CREATE TYPE T_REC AS OBJECT(
       A NUMBER,
       B NUMBER
   ;

CREATE TABLE RTEST(A NUMBER,
                   B NUMBER,
                   C T_REC);

INSERT INTO RTEST VALUES(1,2,T_REC(10,20));

CREATE TYPE EMP_INFORMATION AS OBJECT(
    ADDR VARCHAR2(50),
    EMAIL VARCHAR2(50),
    PHONE VARCHAR2(11));

CREATE TABLE EMP1(
  EMPID VARCHAR2(10),
  EMPNAME VARCHAR2(20),
  INFO EMP_INFORMATION);

INSERT INTO EMP1(EMPID,EMPNAME,INFO) 
  SELECT '0001','SHI',
        EMP_INFORMATION('DALIAN','SHIBU@163.COM','240560560') 
   FROM DUAL;
集合类型的实验
CREATE TYPE 类型名 AS TABLE OF 元素类型名
CREATE TABLE...
  (....
    C1 (NESTED TABLE的类型名),
   ...
  
CREATE TYPE NTA AS TABLE OF NUMBER;
CREATE TABLE NTEST (A NUMBER,
  B NUMBER,
  C NTA)
NESTED TABLE C STORE AS NTT;
INSERT INTO NTEST VALUES(1,2,NTA(100,200,300));
实验:创建一个工资表 职工编号,工资,补贴其中补贴是嵌套表,每个职工的都可以有多种补贴数量不定
CREATE TYPE BT_VALUE AS OBJECT(BT_NAME VARCHAR2(20),
                                BT_JE NUMBER(4));

CREATE TYPE BT_LIST AS TABLE OF BT_VALUE;

CREATE TABLE GZB(EMPNO NUMBER(4),SAL NUMBER(4), BT BT_LIST)
   NESTED TABLE BT STORE AS BT_TAB;

INSERT INTO GZB VALUES(1001,1000,BT_LIST(BT_VALUE('JTFEE',500),
                                         BT_VALUE('TELFEE',200)
                                        
                       
UPDATE TABLE(SELECT BT FROM GZB WHERE EMPNO=1001) SET BT_JE=150 WHERE BT_NAME='JTFEE';
 
SELECT EMPNO,SAL,(SELECT SUM(BT_JE) FROM TABLE(BT)) BT_JE
 FROM GZB;

至此,有关的ORACLE Advanced  SQL的内容就介绍完了。通过,这篇文档的总结我也体会到数据库的学习与应用我们还有很长的路要走。

2005-9-3
分布式计算---JAVA技术

运用JAVA技术实现分布式计算



目前,运用JAVA技术实现分布式计算的技术主要有RMI、CORBA以及Scoket通信三方面技术,下面就这三方面技术分别做一下比较。



     RMI



RMI技术远程调用,是基于RPC技术发展而来的。其开发过程基本由下面几个过程



1.         定义远程接口



2.         实现远程接口



3.         编写使用远程对象



4.         生成stub(客户代理)以及skeletom(服务器实体)



5.         启动注册表并且注册对象



6.         运行服务器和客户



下面就分别对其简单介绍



1.        定义远程接口



 



package shi.rmi;



public interface RemoteShiInterface extends java.rmi.Remote



{



          String message(String message)throws java.rmi.RemoteException;



}



接口必须继承于java.rmi.Remote;并且定义方法



必须抛出java.rmi.RemoteExcetption的异常。



2.        实现远程接口



 



package shi.rmi;



import java.rmi.Naming;



import java.rmi.server.UnicastRemoteObject;



import java.rmi.RemoteException;



import java.rmi.RMISecurityManager;



public class RemoteObject extends UnicastRemoteObject implements RemoteShiInterface

{



          String name;



          public RemoteObject(String name)throws RemoteException



          {



                    super();



                    this.name=name;



          }



          public String message(String message)throws RemoteException



          {



                            String returnString="My Name is :"+name+",thank for your message:"+message;



                    System.out.println("Returning:"+returnString);



                    System.out.println("hello stonewall";



return "My Name is:"+name+",thanks for your message:"+message+"stonewall";



          }



public static void main(String args[])



          {



                    //System.setSecurityManager(new RMISecurityManager());



                    try



                    {



                              String myName="RMI";



                    RemoteObject theServer=new RemoteObject(myName);



                              Naming.rebind("//192.168.1.169:1099/RMI",theServer);



                              System.out.println("Ready to continue";



                    }catch(Exception e)



                    {



          System.out.println("An Exception occured while creating server";



          System.out.println(e);



                    }



          }



}



 



必须注意的就是运用红笔标记出来的代码。基本上就是按照此种样式来编写的。另外,Naming rebind(“URL”,””)其中URL 必须制定,而且还要指定其访问端口如果不指定RMI默认端口是1099。



3.        编写使用远程对象



package shi.rmi;



import java.rmi.RMISecurityManager;



import java.rmi.Naming;



public class RemoteClient



{



          public static void main(String args[])



          {



                    //System.setSecurityManager(new RMISecurityManager());



                    try



                    {



RemoteShiInterface server=(RemoteShiInterface)Naming.lookup("//192.168.1.169:1099/RMI";



                    String serverString=server.message("Hello There";



                    System.out.println("The server says:\n"+serverString);



                    }catch(Exception e)



                    {



                              System.out.println(e);



                    }



          }



}



 



System.setSecurityManager(new RMISecurityManager()) 安全管理的代码,如果把它注释掉,那么需要建立一个安全策略文件,比如文件名 policy.txt



Grant {



     permission java.security.AllPermission “”,””;



};



运行程序形式如下:



D:\RMISample\server>java -Djava.security.policy=policy.txt RemoteObject



D:\RMISample\client>java -Djava.security.policy=policy.txt RemoteClient



如果注释就可以直接运行:



                            Java RemoteObject     java RemoteClient



4.        生成stub(客户代理)以及skeletom(服务器实体)



rmic RemoteObject



5.        启动注册表并且注册对象



start rmiregistry 1099



6.        运行服务器和客户



java RemoteObject



java RemoteClient



 



 



 



 



     Socket



Socket编程相对来说就比较简单,服务器端利用ServerSocket的accept()方法来倾听客户端发出的请求。如果,希望客户端倾听网络中多台机器发出的请求,那么可以将Socket放到一个Thread中去。下面分别列出服务器端和客户端代码。



 



服务器端代码:



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Server.java



 



package shi.socket;



import java.io.*;



import java.net.*;



 



public class Server extends ServerSocket



{



       private static final int SERVER_PORT = 10000;



 



       public Server() throws IOException



       {



              super(SERVER_PORT);



              try{



                     while (true){



                            Socket socket = accept();



                            new CreateServerThread(socket);



                  }



           }catch (IOException e){



                  e.printStackTrace();



           }



              finally{



                     close();



              }



       }



}



 



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CreateServerThread.java



package shi.socket;



 



import java.net.*;



import java.io.*;



 



public class CreateServerThread extends Thread



{



       private Socket client;



       private BufferedReader in;



       private PrintWriter out;



       private static InetAddress serverAddr;



       public CreateServerThread(Socket s) throws IOException{client = s;



       in=new BufferedReader(new InputStreamReader(client.getInputStream(), "GB2312");



              out = new PrintWriter(client.getOutputStream(), true);



              out.println("--- Welcome ---";



              start();



       }



 



       public void run(){   



              try {



                    serverAddr =  InetAddress.getByName(null);



                    while (true) {  



                      String str = in.readLine();



                      if (str.equals("bye") break;



                      System.out.println("Echoing: " + str);



                      out.println(serverAddr + " : " + str);



                    }



                    out.println("---Good Bye---";



                    System.out.println("closing...";



                  } catch (IOException e) {



                  } finally {



                    try {



                           client.close();



                    } catch(IOException e) {e.printStackTrace();}



                  }



       }



       public static void main(String args[]) throws IOException{



       serverAddr =  InetAddress.getByName(null);



       System.err.println(serverAddr.toString() +"--"+ "Create server Thread...";



       new Server();



       }



}



 



客户端代码:



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Client.java



package shi.socket;



import java.io.*;



import java.net.*;



 



public class Client



{



              Socket socket;



              BufferedReader in;



              PrintWriter out;



 



              public Client()



              {



                            try



                            {



                                          InetAddress addr = 



                                                InetAddress.getByName("192.168.1.169";



                                          socket = new Socket(addr, 10000);



in = new BufferedReader(new InputStreamReader(socket.getInputStream()));



                                          out = new PrintWriter(socket.getOutputStream(),true);



                            System.err.println(addr.toString() +"--"+ "Create Client Thread...";



                                          System.out.println(in.readLine());



                                          for (int i=0;i<10;i++){



                                                        String str = addr.toString() + " : " + "The ";



                                                        out.println(str + i);



                                                        System.out.println(in.readLine());



                                          }



                                          out.println("bye";



                                          System.out.println(in.readLine());



                            }catch(Exception e){



                                          e.printStackTrace();



                            }finally{



                                          System.out.println("closing...";



                                          try {



                                                        in.close();



                                          } catch (IOException e1) {



                                                        e1.printStackTrace();



                                          }



                                          out.close();



                                          try {



                                                        socket.close();



                                          } catch (IOException e2) {



                                                        e2.printStackTrace();



                                          }



                            }



              }



 



              public static void main(String[] args){



                                          new Client();



                            }



}



 





 



      // wait for invocations from clients



      orb.run();



    } 



       



      catch (Exception e) {



        System.err.println("ERROR: " + e);



        e.printStackTrace(System.out);



      }



         



      System.out.println("HelloServer Exiting ...";



       



  }



}



客户端代码:



 



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// HelloClient.java



 



import shi.corba.*;



import org.omg.CosNaming.*;



//import org.omg.CosNaming.NamingContextPackage.*;



import org.omg.CORBA.*;



 



public class HelloClient



{



  static Hello helloImpl;



 



  public static void main(String args[])



    {



      try{



        // create and initialize the ORB



              ORB orb = ORB.init(args, null);



 



        // get the root naming context



        org.omg.CORBA.Object objRef = 



                  orb.resolve_initial_references("NameService";



        // Use NamingContextExt instead of NamingContext. This is 



        // part of the Interoperable naming Service.  



        NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);



 



        // resolve the Object Reference in Naming



        String name = "Hello";



        helloImpl = HelloHelper.narrow(ncRef.resolve_str(name));



 



        System.out.println("Obtained a handle on server object: " + helloImpl);



        System.out.println(helloImpl.sayHello());



        helloImpl.shutdown();



 



              } catch (Exception e) {



          System.out.println("ERROR : " + e) ;



                e.printStackTrace(System.out);



                }



    }



 



}



 



 



 



 



 



     综述:



分析以上三种技术,我个人认为CORBA适合大的网络应用。Socket适合于网络间小数据量传输。RMI远程方法调用,不仅适合于网络间的小型应用,而且RMI的扩展要好于Socket通信。



 

2005-7-16
关于COM Interop学习体会心得


COM interop理论/实践
在.NET框架下,开发人员可以通过COM interop tools 将COM组件导入导一个应用中去,一旦导入成功,那么我么就可以非常容易地调用COM接口给我们所提供的方法了。
A .NET Framework developer can incorporate COM components into a managed application by using COM interop tools to import the relevant COM types. Once imported, the COM types are ready to use.
一、 COM interop 概述:
COM Interop 看上去象是介乎于COM和.Net之间的一条纽带,一座桥梁。为了保持向后兼容,COM Interop可以使得.Net程序在不修改原有COM组件的前提下方便的访问COM组件。这一点是非常重要的。事实上,全球的COM组件的代码量估计可能有数十亿行,拥有这些COM组件的公司不可能重写这些组件,所以COM Interop的存在为有此需求的开发者提供了很好的解决方案。
COM和.NET之间存在着非常大的差异,为了使两者可以有机的结合在一起进行协同工作,COM Interop中实际存在着2种桥接方式。一种是RCW,Runtime Callable Wrapper;另一种是 CCW,COM Callable Wrapper。RCW是在运行时通过CLR从Interop装配件(Interop Assembly)的元数据中获取相关信息动态的实例化而得到的。使用者将感觉不到自己是在调用COM组件,一切都是这么的自然,和调用一个.Net组件没有任何区别。  
需要注意的是,一个COM组件(指的是一个实例,即一个DLL文件)由且仅由一个RCW负责维护。那么这儿有一个问题了,对于一个COM组件的不同版本,是不是就会有不同的RCW与之相对应呢?答案是肯定的。那有些朋友会说,.Net中的组件不是已经解决了COM中的“DLL HELL”问题了吗?按上面的说法,似乎并没有得到解决嘛?这儿我要说的是,在.Net中导入一个COM组件的不同版本,是会出现此类问题。解决此类问题的方法是使用PIA (Primary Interop Assembly)。
.Net提供三种方法来导入一个COM组件
l 通过Visual Studio .Net提从的“添加引用”功能
l 通过tlbimp.exe实现
l 使用System.Runtime.InteropServices.TypeLibConverter类编程
下面就分别介绍COM的封装、HRESULTs and Exceptions、继承、聚合和包容、如何运用COM interop来生成发出事件和处理事件以及System.Runtime.InteropServices命名空间几个重要的概念
1. COM的封装(COM Wrappers)
l 在一般的语言(诸如C++)当中在客户端我们一定要控制该COM对象的生命周期
l 客户端的COM对象的方法在C++中的调用通过产生该对象的实例同时获得该对象的接口指针,通过接口指针来访问该对象的方法。在.NET框架下则可以直接通过函数的映射来获得 (Clients of .NET objects can obtain a description of an object's functionality using Reflection.)
l 在.NET框架下的运行环境中.NET可以在内存中为.NET重新对象分配内存使用。(NET objects reside in memory managed by the .NET Framework execution environment.)
为了解决上述问题,.NET提供一个COM Wrappers.它可以使得Managed Code 和 Unmanaged Code可以很好结合在一起。COM Wrappers两种桥接方式RCW(runtime callable wrapper)和CCW (COM callable wrapper)其中RCW是将Managed的客户端与Unmanaged的服务器端联接起来的;CCW是将 Unmanaged的客户端与 Managed的服务器端联接起来的。
2. HRESULTs and Exceptions
在COM编程中我们通过HRESULT来判断所做的操作是否成功,在.NET框架下我们通过抛不同的异常(Throw Exceptions)来捕捉错误。
注MSDN给我们列出了HRESULT不同值与.NET的不同异常的对照表。(.NET Framework Developer’s Guide—HRESULTs and Exceptions)
3. 继承、聚合和包容(Inheritance,Aggregation and Containment)
继承:.NET提供一些标准的接口,用户在定义接口时,可以继承这些接口。
聚合:.NET也支持COM提供的聚合的概念即,外对象将内对象的接口暴露在用户面前。
包容:
    通过在外对象的构造函数中创建内对象的实例,这样客户端就可以通过该实例获得接口进行调用接口的各个方法。
4. 如何运用COM interop来生成发出事件和处理事件
在以下内容中,将要描述有关COM对象出接口与事件接收器的连接机制。
关于COM对象的出接口与事件接收器之间的连接机制与在描述COM原理与应用中的机制是一样的,即COM对象声明一个出接口,在事件接收器中表示该接口的实现方法。一旦,COM对象与事件接收器的连接建立好以后,那么客户端就可以随时接收到COM对象服务器端的事件、消息。下面我们从C#的服务器端和事件接收器两个方面来描述这个问题。
Handling Events Raised by a COM Source(描述COM源对象是如何产生一个事件的)
Raising Events Handled by a COM Sink(通过接收器来处理事件)
5. System.Runtime.InteropServices命名空间
System.Runtime.InteropServices是一个有关访问COM对象以及在.NET框架下的本地API函数。在创建COM接口时经常要运用这个命名空间。
二、 C#接口编程
     下面将从接口的定义、接口的访问、接口的实现以及接口的转换编程这些方面来阐述运用C#进行接口编程的方法。
1. 接口的定义
接口的声明:
[attributes] [modifiers] interface identifier [:base-list] {interface-body}[;]
l attributes(可选):附加的定义性信息。
l · modifiers(可选):允许使用的修饰符有new和四个访问修饰符。分别是:new、public、protected、internal、 private。在一个接口定义中同一修饰符不允许出现多次,new修饰符只能出现在嵌套接口中,表示覆盖了继承而来的同名成员。The public,  protected, internal, and private修饰符定义了对接口的访问权限。 
l 指示器和事件。
l identifier:接口名称。 
l base-list(可选):包含一个或多个显式基接口的列表,接口间由逗号分隔。 
l interface-body:对接口成员的定义。 
l 接口可以是命名空间或类的成员,并且可以包含下列成员的签名: 方法、属性、索引器 。 
l 一个接口可从一个或多个基接口继承。
接口的主体:
interface-body:  {   interface-member-declarationsopt   }
接口可以包含一个和多个成员,这些成员可以是方法、属性、索引指示器和事件,但不能是常量、域、操作符、构造函数或析构函数,而且不能包含任何静态成员。接口定义创建新的定义空间,并且接口定义直接包含的接口成员定义将新成员引入该定义空间。
   2. 接口的访问
C#中的CASTS来代替QueryInterface
(Using Casts Instead of QueryInterface)
在C++中客户端需要通过QueryInterface来获得COM对象的接口指针。在C#编程中却不必这么麻烦。我们可以直接将COM对象对应到相应的COM接口上。如果我们在程序中对应错误,那么在运行时C#会抛出异常。
   3. 接口的实现
显示地实现接口成员即可以直接利用类来实现接口的成员函数如:
    // Since the .NET Framework interface and coclass have to behave as 
    // COM objects, we have to give them guids.
    [Guid("DBE0E8C4-1C61-41f3-B6A4-4E2F353D3D05"]
    public interface IManagedInterface
    {
       int PrintHi(string name);
    }
    [Guid("C6659361-1625-4746-931C-36014B146679"]
    public class InterfaceImplementation : IManagedInterface
    {
       public int PrintHi(string name)
       {
          Console.WriteLine("Hello, {0}!", name);
           return 33;
          }
    }
通过为Coclass的方法来实现接口如:
[Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"
        InterfaceType(ComInterfaceType.InterfaceIsDual)] 
    interface IMediaControl   // Cannot list any base interfaces here 
    { // COM methods     }
// Declare FilgraphManager as a COM coclass:
     [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770"
     class FilgraphManager   // Cannot have a base class or
                                 // interface list here.
     { 
         // Cannot have any members here 
         // NOTE that the C# compiler will add a default constructor
         // for you (no parameters).
   }
其中类FilgraphManager实现了ImediaControl的方法其中ComImport--它将类标记为在外部实现的 COM 类。
   4. 接口的转换编程
运用C#开发COM组件服务器端
l 声明一个COM接口(Declaring a COM Interface):
首先声明一个COM接口我们必须要明确该接口是继承IUnknown or IDispatch或者其他接口。运用C#描述这方面内容必须使用C#提供的属性InterfaceType(InterfaceType – 表明接口是继承于 IUnknown or IDispatch)
其次,为了使COM接口中出现成员函数,程序中也要指出ComImport and Guid属性。(Guid—表明接口或者coclass的唯一标示号;ComImport—它将类标记为在外部实现的 COM 类。)
注:coclass是(简称组件对象类――component object class)被包含在DLL或EXE中,并且包含着一个或者多个接口的代码。客户端通过创建该对象的实例来获得COM对象的接口。
例子代码:
[Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"
        InterfaceType(ComInterfaceType.InterfaceIsDual)] 
    interface IMediaControl   // Cannot list any base interfaces here 
    {
// COM methods
     }
l 声明一个组件对象类(Declaring a COM coclass)
1. 该类不可以继承于其他类。
2. 也不可以实现任何接口。
3. 必须有一个Guid来唯一标示该类
例子代码:
// Declare FilgraphManager as a COM coclass:
     [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770"
//"E436EBB3-524F-11CE-9F53-0020AF0BA770"标示号在系统注册表中可以找到,说明//此处是调用外部的COM类来实现接口ImediaControl的。
     class FilgraphManager   // Cannot have a base class or
                                 // interface list here.
     { 
         // Cannot have any members here 
         // NOTE that the C# compiler will add a default constructor
         // for you (no parameters).
  }
运用C#开发COM组件客户端
l C#中的CASTS来代替QueryInterface
(Using Casts Instead of QueryInterface)
在C++中客户端需要通过QueryInterface来获得COM对象的接口指针。在C#编程中却不必这么麻烦。我们可以直接将COM对象对应到相应的COM接口上。如果我们在程序中对应错误,那么在运行时C#会抛出异常。
例子代码:
// Create an instance of a COM coclass:
FilgraphManager graphManager = new FilgraphManager();
// See if it supports the IMediaControl COM interface. 
// Note that this will throw a System.InvalidCastException if 
// the cast fails. This is equivalent to QueryInterface for 
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;
// Now you call a method on a COM interface: 
mc.Run();
l C#运用抛异常的机制来代替HRESULT
在创建COM对象实例以及获得接口时可能都会出现问题,在C++中我们通过调用HRESULT来判断所做的操作是否成功。但是,在C#编程中我们就不必那么麻烦,C#会抛出诸如System.COMException的异常。
三、关于C#中几个工具的用法
1. Tlbimp.exe的用法:
该工具适合将*.tlb的类库文件可以转换成*.dll的文件,同时该工具还可以将一个dll输出到一个新的dll文件当中去。
例:tlbimp myTest.tlb
输出 MYTESTLIB.dll的动态联接库文件

tlbimp  myTest.tlb  /out:myTest.dll
输出生成 myTest.dll的文件
tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll
将quartz.dll包含到QuartzTypeLib.dll中去。
2. Ildasm工具可以查看*.dll, *.exe, .obj, .lib 文件
例如可以查看.dll的具体情况比如接口、实现类以及成员函数等信息。
如:可以通过命令Ildasm *.dll来查看具体生成的dll的情况。
3. Regasm工具用于将dll动态联接库注册到注册表中的操作
如:Regasm QuartzTypeLib.dll
就将QuartzTypeLib.dll注册到注册表中去。
如果要解除刚才的注册操作可以运用
Regasm /unregister QuartzTypeLib.dll命令即可。
4. CSC工具的使用是编译C#文件*.cs为exe或者dll文件的工具
如:csc File.cs 就是将File.cs编译为File.exe文件
csc /out:my.exe File.cs将File.cs文件编译为my.exe
csc /target:library File.cs就是将File.cs编译为File.dll文件
5.guidgen工具用于生成Guid号
在C#中使用工具
$\Microsoft Visual Studio .NET 2003\Common7\Tools\$下的工具
guidgen.exe生成一个随机的GUID号。
三、 运用C#开发COM的组件的简单例子
功能概述此例子非常简单就是在服务器端写一个具有现实Hello, **的方法
客户端通过调用服务器端的PrintHi(String name)函数显示出 Hello, **的信息。
服务器端代码:
//Copyright (C) 2000 Microsoft Corporation.  All rights reserved.
// ShiServer.cs
// compile with: /target:library
using System;
using System.Runtime.InteropServices;
namespace ShiServer
{
// Since the .NET Framework interface and coclass have to behave as 
// COM objects, we have to give them guids.
[Guid("976F8704-6E29-4b67-AC4F-A5B1226D1F49"]
public interface IshiInterface
{
int PrintHi(string name);
}
[Guid("2BDD9B83-E4FC-432a-BBE1-F71C05723AB6"]
public class IshiImplementation : IshiInterface
{
public int PrintHi(string name)
{
Console.WriteLine("Hello, {0}!", name);
return 33;
}
}
}
客户端代码:
//Copyright (C) 2000 Microsoft Corporation.  All rights reserved.
// shi.cs
// Build with "csc /R:ShiServer.dll shi.cs"
using System;
class MainClass 

    /************************************************************ 
show Hello,sname
    **************************************************************/ 
    public static void Main(string[] args) 
    { 
        // input a name
string sname = args[0];
        // Create instance of ShiServer
        // (Calls CoCreateInstance(D3E09FD9-B987-47f3-89CB-95EFB4D68583,
        // NULL, CLSCTX_ALL, IID_IUnknown, &myHello).
        // Returns null on failure):         
        ShiServer.IshiImplementation myHello=
              new ShiServer.IshiImplementation();
        // QueryInterface for the IshiInterface interface:
        ShiServer.IshiInterface myIhello=
            (ShiServer.IshiInterface)myHello;
              
        // Call some methods on a COM interface 
myIhello.PrintHi(sname);
    }
}
输入: shi stonewall   输出:Hello, stonewall

注:以上资料均来自于
MSDN Library –January 2002 
TiTle: C# Programmer’s Reference   COM Interop Tutorials
COM interop 概述 ::URL::http://www.xmlasp.net/n966c13.aspx
What’s new for Interop in .NET Framework v2.0?       
::URL::http://www.dotnetinterop.com/features/default.aspx?q=Whidbey
C#COM接口编程
::URL::http://tech.ccidnet.com/pub/disp/Article?columnID=295&articleID=40725&pageNO=1

posted on 2007-04-27 09:51 天外飞仙 阅读(2382) 评论(1)  编辑  收藏 所属分类: Oracle

Feedback

# re: 研究Oracle 2008-11-21 12:29 过路人
好长  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: