DO070 Hand_PLSQL_框架_应用指南
本文档详细描述了在 客户化项目开发中SQL/PLSQL的一些规范和开发指南,用于指导开发员遵守开发标准、使用开发模板、快速进入开发角色。
本文内容包括:
1、 对业务表维护API模板
2、 并发程序的模板
3、 如何调试代码
使用框架和不使用框架相比,在刚开始使用的时候,会觉得更麻烦。但是习惯了也就不麻烦了。
问题是:我们为什么要使用框架?要深刻的理解这个问题,我们先看两个故事。这个故事在当前目录的Story 目录下。看完这两个故事,我们可以理解了,如果Oracle 的代码没有Debug功能。我们如何去分析解决问题? 那将是不可设想的。
我们公司的客户越来越多,客户化开发也越来越多。随之而来的是可能产生的问题也越来越多。我们的Hotline 所接到的问题中,有很大一部分是客户化开发的问题。而解决这些问题的人,一般都不是当初开发的人。而是在hotline 工作的人。可想而知,我们的代码没有Debug 功能,没有Debug的习惯。以后出了问题,让他们怎么查?
基于以上原因,我们建议我们的代码都要使用我们的开发框架,因为这个框架是一个具备Debug功能的框架。和Oracle 的Debug框架是一致的。写出来的代码风格跟Oracle是一致的。
三个Package介绍
1、 HAND_DEBUG.pck
2、 HAND_API.pck
3、 HAND_CONC_UTL.pck
这三个package都不大,熟读以后有助于我们使用其中的Api.
业务表维护模板
对于业务表,必须提供公开函数来维护,禁止外部程序直接操作表,一般规则如下图所示:
1、Table API 实现表基本操作:Insert、Lock、Update、Delete。
2、Private API调用TableAPI来操作表,该层实现验证表的必输字段,同时通过调用其它API提供的Public API来实现维护表的业务逻辑。
3、Public API是公开的API,供外部程序调用,该层只做对传入参数作简单验证;然后调用Private API。
4、对于所有的验证,提供对立的Valiate API
5、对于简单的业务表,如基础数据,该数据只在Form中维护,可以只建立TableAPI。
6、各程序包的命名规则:
Table API:表名 + ‘_PKG’ (table api)
Private API:表名 + ‘_PVT’ (private api)
Public API:表名 + ‘_PUB’ (public api)
Valiate API:表名 + ‘_UTL’ (validate api)
对于多个API间相互调用图例:
以下介绍各API的模板和使用方法
Table API
该程序包一般包括以下几个过程:insert_row、lock_row、update_row、delete_row
包头定义:
PROCEDURE insert_row( x_row_id in out varchar2, x_primary_key in out number, p_field1 in … … ); PROCEDURE lock_row( p_primary_key in number, p_field1 in … … ); PROCEDURE update_row( p_primary_key in number, p_field1 in … … ); PROCEDURE delete_row( p_primary_key in number);
对于表中含有object_version_number,其过程lock_row可简化为 PROCEDURE lock_row( p_primary_key in number, p_object_version_number in number );
|
包体如下:
PROCEDURE insert_row( x_row_id in out varchar2, x_primary_key in out number, p_field1 in … … ) IS CURSOR C IS SELECT ROWID FROM <TABLE> WHERE <primary_key> = p_primary_key; BEGIN INSERT INTO <TABLE> ( field1, field2, … ) VALUES ( p_field1, p_field2, … );
OPEN c; END insert_row;
PROCEDURE lock_row( p_primary_key in number, p_field1 in … … ) IS CURSOR c1 IS SELECT field1,field2,… from <TABLE> where <primary_key> = p_primary_key FOR UPDATE OF <primary_key> NOWAIT; tlinfo c1%ROWTYPE; BEGIN OPEN c1; FETCH c1 INTO tlinfo; IF (c1%NOTFOUND) THEN CLOSE c1; fnd_message.set_name('FND', 'FORM_RECORD_DELETED'); app_exception.raise_exception; END IF; CLOSE c1;
IF (tlinfo.field1 = p_field1 or (tlinfo.field1 is null and p_field1 is null)) AND (tlinfo.field2 = p_field2 or (tlinfo.field2 is null and p_field2 is null)) … THEN NULL; ELSE fnd_message.set_name('FND', 'FORM_RECORD_CHANGED'); app_exception.raise_exception; END IF; END lock_row;
PROCEDURE update_row( p_primary_key in number, p_field1 in … … ) IS BEGIN UPDATE <TABLE> SET field1 = p_field1, field1 = p_field1, … WHERE <primary_key> = p_primary_key;
IF (SQL%NOTFOUND) THEN RAISE NO_DATA_FOUND; END IF; END update_row;
PROCEDURE delete_row( p_primary_key in number) IS BEGIN DELETE FROM <TABLE> WHERE <primary_key> = p_primary_key;
IF (SQL%NOTFOUND) THEN RAISE NO_DATA_FOUND; END IF; END delete_row; 对于表中含有object_version_number,其过程lock_row可简化为 PROCEDURE lock_row( p_primary_key in number, p_object_version_number in number ) CURSOR c1 IS SELECT object_version_number from <TABLE> where <primary_key> = p_primary_key FOR UPDATE OF <primary_key> NOWAIT; tlinfo c1%ROWTYPE; BEGIN OPEN c1; FETCH c1 INTO tlinfo; IF (c1%NOTFOUND) THEN CLOSE c1; fnd_message.set_name('FND', 'FORM_RECORD_DELETED'); app_exception.raise_exception; END IF; CLOSE c1;
IF (tlinfo.object_version_number = p_object_version_number) THEN NULL; ELSE fnd_message.set_name('FND', 'FORM_RECORD_CHANGED'); app_exception.raise_exception; END IF; END lock_row;
|
Private API / Public API 的过程模板
为统一风格、简化程序、提供统一的例外处理和调试功能,该程序包中的API使用以下模板
PROCEDURE create_temp |
1、过程必要的参数
参数名 | 类型 | 数据类型 | 描述 |
|
|
|
|
P_API_VERSION | IN | NUMBER | API版本号,用于检查API版本 |
P_INIT_MSG_LIST | IN | VARCHAR2 | 是否初始化消息队列 |
P_COMMIT | IN | VARCHAR2 | 是否自动提交更改 |
X_RETURN_STATUS | OUT | VARCHAR2 | 返回API执行状态 S 成功 E 错误 U 例外 |
X_MSG_COUNT | OUT | VARCHAR2 | 返回错误消息的个数 |
X_MSG_DATA | OUT | VARCHAR2 | 返回第一个错误信息的内容 |
其中:
P_COMMIT:对于无数据更新操作可以不要,对于这类的API,需要将g_api_type设置为NULL,且调用HAND_API.end_activity不要传递p_commit参数。
验证API
所有的验证API只返回验证的结果状态,验证的错误信息直接加入消息队列。
当验证不通过时,使用HAND_API.set_message将验证错误信息加入到消息队列。
验证API可以不处理例外,而由调用者处理。
以下提供两种处理方式,一个不带例外处理,一个带例外处理。
不带例外处理:
PROCEDURE validate_currency_code( p_currency_code IN VARCHAR2, x_return_status OUT NOCOPY VARCHAR2) IS CURSOR c_currencies IS SELECT 1 FROM fnd_currencies WHERE currency_code = p_currency_code;
l_dummy VARCHAR2(1); BEGIN x_return_status := fnd_api.g_ret_sts_success;
OPEN c_currencies; FETCH c_currencies INTO l_dummy; IF c_currencies%NOTFOUND THEN x_return_status := fnd_api.g_ret_sts_error; HAND_API.set_message( p_app_name => 'CUX', p_msg_name => 'XH_INVALID_CURRENCY_CODE', p_token1 => 'P_CURRENCY_CODE', p_token1_value => p_currency_code); END IF; CLOSE c_currencies; END validate_currency_code; |
带例外处理:
PROCEDURE validate_currency_code1( p_currency_code IN VARCHAR2, x_return_status OUT NOCOPY VARCHAR2) IS CURSOR c_currencies IS SELECT 1 FROM fnd_currencies WHERE currency_code = p_currency_code;
l_api_name CONSTANT VARCHAR2(30) := 'VALIDATE_CURRENCY_CODE1'; l_dummy VARCHAR2(1); BEGIN x_return_status := fnd_api.g_ret_sts_success;
OPEN c_currencies; FETCH c_currencies INTO l_dummy; IF c_currencies%NOTFOUND THEN CLOSE c_currencies; HAND_API.set_message( p_app_name => 'CUX', p_msg_name => 'XH_INVALID_CURRENCY_CODE', p_token1 => 'P_CURRENCY_CODE', p_token1_value => p_currency_code); RAISE fnd_api.g_exc_error; END IF; CLOSE c_currencies; EXCEPTION WHEN fnd_api.g_exc_error THEN x_return_status := fnd_api.g_ret_sts_error; WHEN fnd_api.g_exc_unexp_error THEN x_return_status := fnd_api.g_ret_sts_unexp_error; WHEN OTHERS THEN IF fnd_msg_pub.check_msg_level(fnd_msg_pub.g_msg_lvl_unexp_error) THEN fnd_msg_pub.add_exc_msg( g_pkg_name, l_api_name ); END IF; x_return_status := fnd_api.g_ret_sts_unexp_error; END validate_currency_code1; |
并发程序模板
并发程序的过程第1,2个参数是固定的:
参数名 | 类型 | 数据类型 | 描述 |
|
|
|
|
ERRBUF | OUT | VARCHAR2 | 并发程序返回错误信息 |
RETCODE | OUT | VARCHAR2 | 并发程序返回状态 0 成功 1 警告 2 错误 |
CREATE OR REPLACE PACKAGE BODY XH_CONC_TEMPLATE_PUB AS /*=============================================== Copyright (C) Xieheng Co.,Ltd. AllRights Reserved ================================================ * =============================================== * PROGRAM NAME: * * DESCRIPTION: * * HISTORY: * 1.00 2005-09-26 xxxxxx Creation * * ==============================================*/
G_PKG_NAME CONSTANT VARCHAR2(30):= 'XH_CONC_TEMPLATE_PUB';
PROCEDURE main( errbuf OUT VARCHAR2, retcode OUT VARCHAR2, p_parameter1 IN VARCHAR2, p_parameter2 IN VARCHAR2) IS l_return_status VARCHAR2(30); l_msg_count NUMBER; l_msg_data VARCHAR2(2000); BEGIN retcode := '0'; -- concurrent header log Hand_conc_utl.log_header;
-- convert parameter data type, such as varchar2 to date -- l_date := fnd_conc_date.string_to_date(p_parameter1);
-- conc body -- call process api process_request( p_init_msg_list => fnd_api.g_true, p_commit => fnd_api.g_false, x_return_status => l_return_status, x_msg_count => l_msg_count, x_msg_data => l_msg_data, p_xxx => p_parameter1 p_xxx => p_parameter2); IF l_return_status = fnd_api.g_ret_sts_error THEN RAISE fnd_api.g_exc_error; ELSIF l_return_status = fnd_api.g_ret_sts_unexp_error THEN RAISE fnd_api.g_exc_unexpected_error; END IF; -- conc end body
-- concurrent footer log Hand_conc_utl.log_footer;
EXCEPTION WHEN fnd_api.g_exc_error THEN Hand_conc_utl.log_message_list; retcode := '1'; errbuf := l_msg_data; WHEN fnd_api.g_exc_unexpected_error THEN Hand_conc_utl.log_message_list; retcode := '2'; errbuf := l_msg_data; WHEN OTHERS THEN fnd_msg_pub.add_exc_msg(p_pkg_name => g_pkg_name, p_procedure_name => 'MAIN' ); Hand_conc_utl.log_message_list; retcode := '2'; errbuf := SQLERRM; END main;
END XH_CONC_TEMPLATE_PUB; |
代码调试
使用模板开发的程序包默认具有调试的功能。
开发员可以在任何代码段内调用HAND_DEBUG.LOG进行调试信息的输出。
该调试包可以根据预置文件的设置情况,将信息输出到fnd_log_messages 表、服务器文件、并发程序log文件。
启动调试使用系统标准的日志预置文件进行控制
预置文件名 | 描述 |
|
|
FND:启用调试日志 | 是否启动调试 |
FND:调试日志级别 | 调试须设置为过程 或 对帐单 |
FND:调试日志模块 | 调试客户化程序,设置为% |
启动后,日志默认输出到表fnd_log_message中,若需要产生日志文件需要设置一下两个预置文件
预置文件名 | 描述 |
|
|
CUX: Debug Log mode | FILE 输出到日志文件,但并发输出到并发log文件 FILEONLY 强制输出到日志文件 TABLE 输出到表fnd_log_messages |
CUX: Debug Log File | 设置服务器文件名,如/usr/tmp/hand.log,则产生文件为hand_<USER>.log文件,<USER>为登录用户名。 |