级别: 中级 程永 (cyong@cn.ibm.com), 高级信息工程师, IBM
2008 年 8 月 04 日 IBM 的 DB2 V9.5 引入了角色和可信上下文的功能,角色和组最大的区别是,组是第三方软件控制的,比如 OS 或 LDAP 目录,而角色是由 DB2 控制的。角色通过提供与组等价的能力但没有相同的限制,简化了特权的管理。可信上下文是一个数据库对象,它为数据库与外部实体(例如,应用程序服务器)之间的连接定义信任关系。由于角色和可信上下文是 DB2 V9.5 的新特性,很多客户不知道该如何使用,本文将重点介绍 DB2 V9.5 关于角色和可信上下文的新特性以及相关的概念,并结合实际的例子帮助大家理解和提高。
简介
IBM 的 DB2 V9.5 引入了角色和可信上下文的功能,角色和组最大的区别是,组是第三方软件控制的,比如 OS 或 LDAP 目录,而角色是由 DB2 控制的。角色通过提供与组等价的能力但没有相同的限制,简化了特权的管理。可信上下文是一个数据库对象,它为数据库与外部实体(例如,应用程序服务器)之间的连接定义信任关系。
角色的概念其实在其他数据库中一直都有, DB2 V9.5 开始引入了角色的概念,含义和其他数据库中的基本一样,就是将一项或多项特权集中赋给角色,这样,我们可以使用 GRANT 语句将角色指定给用户、组、PUBLIC 或其他角色,也可以使用 REVOKE 命令取消赋予的角色,从而简化了数据库的权限管理。引入角色的目的主要是为了避免以前“组”所存在的限制。简单的理解角色,就是其具有组的功能,但是又没有“组”所存在的限制(在创建视图、触发器、具体化查询表(MQT)、静态 SQL 和 SQL 例程时,不能直接或间接给组授予特权和权限)。可以更新角色的特权,并且授予了该角色的所有用户都将接收更新,管理员不需要逐个更新每个用户的特权。指定给用户的所有角色将在该用户建立连接时启用,因此在用户连接时将考虑授予给角色的所有特权和权限。不能显式启用或禁用角色。 另外,角色和可信上下文相结合,通过使用 CREATE TRUSTED CONTEXT 或 ALTER TRUSTED CONTEXT 语句将它指定给可信上下文,另外,工作负载定义中的 SESSION_USER ROLE 数据库连接属性就是指会话用户所属的角色。
角色可以嵌套,也就是可以把一个角色赋予另一个角色,这样后一个角色就继承了前一个角色的各种特权。但是,需要注意的是,角色不能循环嵌套,也就是说,不能把一个角色赋予后一个角色后,再把后一个角色赋予前一个角色。如果出现循环嵌套将会返回错误(SQLSTATE 428GF)。角色也没有所有者,安全管理员可以使用 GRANT 语句的 WITH ADMIN OPTION 子句来将角色的管理权委托给另一个用户,以便其他用户可以控制角色成员资格。另外,在使用角色时,还存在一些限制,比如:
- 角色不能拥有数据库对象;
- 不能对角色授予安全管理员(SECADM)权限;
- 在创建某些数据库对象(比如包含静态 SQL 的程序包,视图,物化查询表 MQT,触发器,SQL 例程等)时,不能把其权限授予给组,也不能把具体这些数据库对象相关权限的角色授予给组。
可信上下文是我们显式声明的一个数据库对象,为数据库和外部实体(比如应用服务器)之间的连接定义信任关系。当新的连接符合已经存在的可信上下文定义时,这个连接就被认为是一个可信连接。可信上下文可以基于下列属性建立:
- 系统授权标识:表示建立数据库连接的用户 ;
- IP 地址(或域名):表示在其中建立数据库连接的主机 ;
- 数据流加密:表示用于数据库服务器与数据库客户机之间的数据通信的加密设置(如果此设置存在) 。
可信连接允许此可信连接的发起方获得一些额外的权限(比如,此连接的用户只具有连接数据库的权限,而可信上下文定义了符合此上下文的可信连接额外将获得新的角色,这样可信连接的用户就通过可信连接具有了额外的权限)。另外,显式可信连接还可以通过在连接时切换用户,获得其他权限。
本文将重点介绍 DB2 V9.5 关于角色和可信上下文的新特性以及相关的概念,并结合实际的例子帮助大家理解和提高。
我们将按照下列顺序介绍角色和可信上下文以及相关的功能:
- 构建数据库环境,创建创建示例数据库SAMPLE。
- 简要介绍一些角色的相关知识。
- 简要介绍可信上下文的相关知识。
构建数据库环境
首先我们在 WINDOWS XP 环境下安装 DB2 ESE V9.5,安装完成后,默认创建了实例 DB2,默认情况下其只拥有一个节点(单分区数据库),节点编号为 0。我们打开一个 DB2CLP 窗口,发出 DB2SAMPL 命令,创建示例数据库 SAMPLE,具体如清单 1 所示:
清单1 .创建示例数据库 SAMPLE
C:\> db2sampl
Starting the DB2 instance...
Creating database " SAMPLE " . . .
Connecting to database " SAMPLE " . . .
Creating tables and data in schema " RHETTE " . . .
Creating tables with XML columns and XML data in schema " RHETTE " . . .
Stopping the DB2 instance . . .
' db2sampl ' processing complete .
|
命令成功完成,这样我们创建了示例数据库 SAMPLE。继续在当前 DB2CLP 窗口发出 DB2LEVEL 命令,查看当前数据库的版本信息,需要注意的是,DB2 代码发行版是否是" SQL09050 ",角色和可信上下文都是在 DB2 V9.5 开始的,具体如清单2所示:
清单 2 .查看示例数据库 SAMPLE 的版本情况
C:\> db2level
DB21085I 实例 " DB2 " 使用 " 32 " 位和 DB2 代码发行版 " SQL09050 ",级别标识为
" 03010107 "。
参考标记为 " DB2 v9.5.0.808 "、" s071001 " 和 " NT3295 ",修订包为 " 0 "。
产品使用 DB2 副本名 " DB2COPY1 " 安装在 " C:\PROGRA~1\IBM\SQLLIB " 中。
|
角色
角色是将一项或多项特权集中在一起的数据库对象,可以使用 GRANT 语句将角色指定给用户、组、PUBLIC 或其他角色。在 DB2 V9.5 引入角色的目的,就是为了避免组所存在的限制,使其既具有组的功能,又不存在组的限制。都有哪些特权可以授予角色呢?简单的说,就是除了安全管理员(SECADM)权限外,其他的任何数据库权限和特权都可以赋予角色,包括:
- DBADM、LOAD 和 IMPLICIT_SCHEMA 数据库权限 ;
- CONNECT、CREATETAB、CREATE_NOT_FENCED、BINDADD CREATE_EXTERNAL_ROUTINE 或 QUIESCE_CONNECT 数据库权限 ;
- 任何数据库对象特权(包括 CONTROL) 。
在我们的示例数据库 SAMPLE中,存在两个表,EMPLOYEE 和 PRODUCT。雇员 SEAN 和 DIAN 都属于部门 A00,假如部门 A00 的人都具有对表 PRODUCT 的查询权限,我们可以通过创建角色的方式,来实现这个需求。
在当前 DB2CLP 窗口中,用 DB2ADMIN 用户连接上示例数据库 SAMPLE,授予 RHETTE 用户安全管理员权限,再用 RHETTE 用户连接示例数据库 SAMPLE,发出 CREATE ROLE 命令,创建角色 ROLE_A00,通过 GRANT 语句把表 PRODUCT 的 SELECT 权限赋予角色 ROLE_A00,并把角色 ROLE_A00 授予给部门 A00 的雇员SEAN 和 DIAN,具体如清单3所示:
清单3 .创建角色 ROLE_A00,并授予给雇员 SEAN 和 DIAN
C:\> db2 connect to sample user db2admin using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = DB2ADMIN
本地数据库别名 = SAMPLE
C:\> db2 GRANT SECADM ON DATABASE TO USER RHETTE
DB20000I SQL命令成功完成。
C:\> db2 connect to sample user rhette using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = RHETTE
本地数据库别名 = SAMPLE
C:\> db2 create role ROLE_A00
DB20000I SQL命令成功完成。
C:\> db2 grant select on table product to role role_a00
DB20000I SQL命令成功完成。
C:\> db2 grant role role_a00 to user SEAN , user DIAN
DB20000I SQL命令成功完成。
|
命令成功完成。这样我们就创建了角色 role_a00,把表 PRODUCT 的 SELECT 权限赋予角色 role_a00,并把角色 ROLE_A00 授予给部门 A00 的雇员 SEAN 和 DIAN。此时假如雇员 SEAN 离开了部门 A00,可以通过 REVOKE 语句显式的收回其授权,具体如清单 4 所示:
清单4 .收回雇员 SEAN 的角色 ROLE_A00
C:\> db2 revoke role role_a00 from user SEAN
DB20000I SQL命令成功完成。
|
角色可以嵌套,也就是可以把一个角色赋予另一个角色,这样后一个角色就继承了前一个角色的各种特权。但是,需要注意的是,角色不能循环嵌套,也就是说,不能把一个角色赋予后一个角色后,再把后一个角色赋予前一个角色。如果出现循环嵌套将会返回错误(SQLSTATE 428GF)。
我们继续在当前 DB2CLP 窗口,创建三个角色,分别是 R1、R2、R3,并把角色R1授予给角色 R2,把角色R2授予给角色 R3,当我们试图把角色 R3 授予给 R1 时,系统报 SQLSTATE 428GF(SQLCODE 0796N),将角色 " R3 " 授权给角色 " R1 " 无效,因为这会导致循环。具体如清单 5 所示:
清单5 .角色循环嵌套出错
C:\> db2 create role r1
DB20000I SQL命令成功完成。
C:\> db2 create role r2
DB20000I SQL命令成功完成。
C:\> db2 create role r3
DB20000I SQL命令成功完成。
C:\> db2 grant role r1 to role r2
DB20000I SQL命令成功完成。
C:\> db2 grant role r2 to role r3
DB20000I SQL命令成功完成。
C:\> db2 grant role r3 to role r1
DB21034E 该命令被当作 SQL 语句来处理,因为它是无效的“命令行处理器”命令。在
SQL 处理期间,它返回:
SQL0796N 将角色 " R3 " 授权给角色 " R1 " 无效,因为这会导致循环。
|
在撤销角色的特权时,有时会导致从属数据库对象(比如,视图、程序包、触发器等)变得无效或不可用。在当前 DB2CLP 窗口中,我们继续使用 RHETTE 用户(具有安全管理员权限)连接示例数据库 SAMPLE,发出 CREATE ROLE 命令,创建角色 R4,并把角色 R4 授予给系统用户 TEST,然后把表 ORG 的 SELECT 和 INSERT 特权授予给角色 R4,具体如清单 6 所示:
清单6 .撤销角色特权,会导致从属数据库对象失效或不可用示例
C:\> db2 connect to sample user rhette using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = RHETTE
本地数据库别名 = SAMPLE
C:\> db2 create role r4
DB20000I SQL命令成功完成。
C:\> db2 grant role r4 to user test
DB20000I SQL命令成功完成。
C:\> db2 grant select on table org to role r4
DB20000I SQL命令成功完成。
C:\> db2 grant insert on table org to role r4
DB20000I SQL命令成功完成。
|
命令成功完成。这样我们就把角色 R4 创建完成,并把表 ORG 的 select 和 insert 权限赋予给了角色 R4,同时把角色 R4 授予给了用户 TEST。
下面我们打开一个新的 DB2CLP 窗口,称之为窗口 2,原来打开的 DB2CLP 窗口我们称之为窗口 1。在窗口 2 中用 TEST 用户连上示例数据库 SAMPLE,发出 CREATE VIEW 命令,创建视图 V_ORG,并创建基于 ORG 表的存储过程 P1,具体如清单 7 所示:
清单7 .撤销角色特权,会导致从属数据库对象失效或不可用示例
C:\> db2 connect to sample user test using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = TEST
本地数据库别名 = SAMPLE
C:\> db2 create view v_org as select * from rhette.org
DB20000I SQL命令成功完成。
C:\> db2 -td# -vf crt_procedure.sql
CREATE PROCEDURE P1 ()
RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c1 CURSOR FOR
SELECT * FROM RHETTE.ORG;
OPEN c1;
END
DB20000I SQL命令成功完成。
|
命令成功完成。这样我们就创建了基于表 ORG 的视图 V_ORG,并创建了基于表 ORG 的存储过程 P1,在窗口 2 中,查询视图 V_ORG,调用存储过程 P1,发现其都正常,具体如清单8所示:
清单8 .查询视图 V_ORG,调用存储过程 P1
C:\> db2 call p1()
返回状态 = 0
C:\> db2 select * from v_org
DEPTNUMB DEPTNAME MANAGER DIVISION LOCATION
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 Head Office 160 Corporate New York
15 New England 50 Eastern Boston
20 Mid Atlantic 10 Eastern Washington
38 South Atlantic 30 Eastern Atlanta
42 Great Lakes 100 Midwest Chicago
51 Plains 140 Midwest Dallas
66 Pacific 270 Western San Francisco
84 Mountain 290 Western Denver
8 条记录已选择。
|
如果我们撤销角色 R4 对表 ORG 的 SELECT 特权,由于视图定义者 TEST 通过角色 R4 获得对表 ORG 的 SELECT 特权,所以视图 TEST.V_ORG 变的不可用,并且程序包 P1 也变的无效。在窗口 1 中发出 REVOKE 命令,取消角色 R4 对表 ORG 的 SELECT 特权,并在窗口 2 中发出查询视图 V_ORG,调用存储过程 P1 地命令,具体如清单 9 所示:
清单9_1 .在窗口 1 中撤销角色 R4 对表 ORG 的 SELECT 特权
C:\> db2 revoke select on table org from role r4
DB20000I SQL命令成功完成。
|
清单9_2 .在窗口 2 查询视图 V_ORG,调用存储过程 P1
C:\> db2 call p1()
SQL0727N 隐式系统操作类型 " 1 " 期间出错。对该错误返回的信息包括 SQLCODE
" -551 "、SQLSTATE " 42501 " 和消息标记 " TEST|SELECT|RHETTE.ORG "。 SQLSTATE = 56098 。
C:\> db2 select * from v_org
SQL0575N 不能使用视图或具体化查询表 " TEST.V_ORG ",因为它已经标记为不可用。
SQLSTATE = 51024 。
|
如清单 9_2 所示,由于取消了角色 R4 对表 ORG 的 SELECT 特权,使得基于表 ORG 的存储过程 P1 无效,基于表 ORG 的视图 V_ORG 不能使用。
下面我们来看一下如果撤销 DBADM 权限,会对数据库对象产生什么样的影响。在窗口 1 中,使用 RHETTE 用户连接示例数据库 SAMPLE,创建角色 R5,把 DBADM 权限授予给角色 R5,并把角色 R5 授予给用户 TEST(需要删掉示例数据库中已经存在的触发器 DO_NOT_DEL_SALES),具体如清单 10 所示:
清单10 .在窗口 1 中创建角色 5,授予其 DBADM 权限,并把角色 R5 授予给用户 TEST
C:\> db2 connect to sample user rhette using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = RHETTE
本地数据库别名 = SAMPLE
C:\> db2 create role r5
DB20000I SQL命令成功完成。
C:\> db2 grant dbadm on database to role r5
DB20000I SQL命令成功完成。
C:\> db2 grant role r5 to user test
DB20000I SQL命令成功完成。
C:\> db2 create table test ( id int )
DB20000I SQL命令成功完成。
|
命令成功完成。下面我们在窗口 2 中使用 TEST 用户连接示例数据库 SAMPLE,创建基于表 STAFF 的视图 V_STAFF,创建基于表 STAFF 的存储过程 P2,创建基于表 STAFF 的触发器 TRIG1,具体如清单 11 所示:
清单11 .在窗口2中创建视图 V_STAFF,存储过程 P2,触发器 TRIG1
C:\> db2 connect to sample user test using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = TEST
本地数据库别名 = SAMPLE
C:\> db2 create view v_staff as select * from rhette.staff
DB20000I SQL命令成功完成。
C:\> db2 -td# -vf crt_procedure.sql
CREATE PROCEDURE P2 ()
RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c1 CURSOR FOR
SELECT * FROM RHETTE.STAFF;
OPEN c1;
END
DB20000I SQL命令成功完成。
C:\> db2 -tvf crt_trigger.sql
CREATE TRIGGER TRIG1 AFTER DELETE ON RHETTE.STAFF FOR EACH STATEMENT MODE DB2SQL INSERT
INTO RHETTE.TEST VALUES (1)
DB20000I SQL命令成功完成。
|
命令成功完成。继续在窗口 2 中查看视图 V_STAFF,调用存储过程 P2,发现其都正常。删除 RHETTE.STAFF 表中的一条记录,再查看 TEST 表,我们发现其多了一条记录,证明触发器正常,具体如清单 12 所示:
清单12.在窗口 2 中查看视图 V_STAFF,调用过程 P2,触发触发器 TRIG1
C:\> db2 select * from v_staff
ID NAME DEPT JOB YEARS SALARY COMM
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10 Sanders 20 Mgr 7 98357.50 -
20 Pernal 20 Sales 8 78171.25 612.45
30 Marenghi 38 Mgr 5 77506.75 -
40 O'Brien 38 Sales 6 78006.00 846.55
50 Hanes 15 Mgr 10 80659.80 -
60 Quigley 38 Sales - 66808.30 650.25
70 Rothman 15 Sales 7 76502.83 1152.00
80 James 20 Clerk - 43504.60 128.20
90 Koonitz 42 Sales 6 38001.75 1386.70
100 Plotz 42 Mgr 7 78352.80 -
110 Ngan 15 Clerk 5 42508.20 206.60
120 Naughton 38 Clerk - 42954.75 180.00
130 Yamaguchi 42 Clerk 6 40505.90 75.60
140 Fraye 51 Mgr 6 91150.00 -
150 Williams 51 Sales 6 79456.50 637.65
160 Molinare 10 Mgr 7 82959.20 -
. . . . . . . . . . .
35 条记录已选择。
C:\> db2 call p2()
返回状态 = 0
C:\> db2 delete from rhette.staff where id = 10
DB20000I SQL命令成功完成。
C:\> db2 select * from rhette.test
ID
- - - - - - - - - - -
1
1 条记录已选择。
|
此时我们在窗口 1 中撤销用户 TEST 的角色 R5,也就是说,间接的取消用户 TEST 的数据库管理员权限(DBADM),在窗口 2 中查看视图 V_STAFF,发现其正常,调用存储过程 P2,发现存储过程 P2 无效。再在窗口 1 中删除 RHETTE.STAFF 表中的一条记录,再查看 TEST 表,我们发现其多了一条记录,证明触发器正常。具体如清单 13 所示:
清单13_1 .在窗口 1 中取消用户 TEST 的角色 R5
C:\> db2 revoke role r5 from user test
DB20000I SQL命令成功完成。
|
清单13_2 .在窗口 2 中查看视图 V_STAFF,调用过程 P2,触发触发器 TRIG1
C:\> db2 select * from v_staff
ID NAME DEPT JOB YEARS SALARY COMM
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
20 Pernal 20 Sales 8 78171.25 612.45
30 Marenghi 38 Mgr 5 77506.75 -
40 O'Brien 38 Sales 6 78006.00 846.55
50 Hanes 15 Mgr 10 80659.80 -
60 Quigley 38 Sales - 66808.30 650.25
70 Rothman 15 Sales 7 76502.83 1152.00
80 James 20 Clerk - 43504.60 128.20
90 Koonitz 42 Sales 6 38001.75 1386.70
100 Plotz 42 Mgr 7 78352.80 -
110 Ngan 15 Clerk 5 42508.20 206.60
120 Naughton 38 Clerk - 42954.75 180.00
130 Yamaguchi 42 Clerk 6 40505.90 75.60
140 Fraye 51 Mgr 6 91150.00 -
150 Williams 51 Sales 6 79456.50 637.65
160 Molinare 10 Mgr 7 82959.20 -
. . . . . . . . . . .
34 条记录已选择。
C:\> db2 call p2()
SQL0727N 隐式系统操作类型 " 1 " 期间出错。对该错误返回的信息包括 SQLCODE
" -551 "、SQLSTATE " 42501 " 和消息标记 " TEST | SELECT | RHETTE.STAFF "。
SQLSTATE = 56098
|
清单13_3 .在窗口1中删除STAFF表的一条记录
C:\> db2 delete from rhette.staff where id = 50
DB20000I SQL命令成功完成。
C:\>db2 select * from test
ID
- - - - - - - - - - -
1
1
5 条记录已选择。
|
命令成功完成。由清单 13 我们可以看出,撤销用户 TEST 的角色 R5(间接取消用户 TEST 的 DBADM 权限),会造成存储过程 P2 无效,对视图 V_STAFF 和触发器 TRIG1 没有影响。
如果想把角色中成员资格的管理和控制权委托给其他人,安全管理员(SECADM)可以使用带 WITH ADMIN OPTION 选项的 GRANT ROLE 语句。WITH ADMIN OPTION 子句使另一个用户有权将角色中的成员资格授予给其他用户、撤销角色的其他成员在该角色中的成员资格以及注释但不删除该角色。
在窗口 1 中,由用户 RHETTE 用户连上示例数据库,发出 CREATE ROLE 语句,创建角色 R6,并使用带 WITH ADMIN OPTION 选项的 GRANT ROLE 语句将此角色授予给用户 TEST,具体如清单 14 所示:
清单14 .在窗口 1 中创建角色 R6,并授予给用户 TEST
C:\> db2 connect to sample user rhette using passw0rd
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = RHETTE
本地数据库别名 = SAMPLE
C:\> db2 create role r6
DB20000I SQL命令成功完成。
C:\> db2 grant role r6 to user test with admin option
DB20000I SQL命令成功完成。
|
命令成功完成。此时,用户 TEST 具有角色 R6 成员资格的管理权,可以将角色中的成员资格授予给其他用户、撤销角色的其他成员在该角色中的成员资格以及注释但不删除该角色。下面我们在窗口 2,使用 TEST 用户连上示例数据库 SAMPLE,可以通过 GRANT ROLE 语句将角色 R6 授予给其他用户,比如 ADMINISTRATOR,也可以撤销其他用户在角色中的成员资格,具体如清单 15 所示:
清单15 .在窗口 2 中将角色 R6 授予给用户 ADMINISTRATOR
C:\> db2 connect to sample user test using chengy7810
数据库连接信息
数据库服务器 = DB2 / NT 9.5.0
SQL 授权标识 = TEST
本地数据库别名 = SAMPLE
C:\> db2 grant role r6 to user administrator
DB20000I SQL命令成功完成。
C:\> db2 revoke role r6 from user administrator
DB20000I SQL命令成功完成。
|
命令成功完成。用户 TEST 可以将角色 R6 中的成员资格授予给其他用户、撤销角色的其他成员在该角色中的成员资格以及注释但不删除该角色,但是不能删除该角色或者将 WITH ADMIN OPTION 授予给另一个用户,这两个功能只能是安全管理员才可以执行。另外,由于用户 TEST 没有安全管理员权限,所以 TEST 用户不能撤销角色 R6 的用户的角色管理权,而安全管理员可以撤销某用户对角色 R6 的角色管理特权。
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | 程永是 IBM 软件集团中国区合作伙伴技术支持(BPTS)高级信息工程师。BPTS 团队专注于帮助合作伙伴提升 IBM 软件平台的技能,构建行业解决方案,辅助方案实施,建立成功客户案例并分享最佳实践经验。程永在数据库和信息集成领域有 6 年以上经验,在 DB2 规划设计、开发部署、性能调试以及 WII(Websphere Information Integration)信息整合等方面有丰富的经验。 |
对本文的评价
|