接前面"oracle 11g streams 应用进程使用示例"
1、LCR中额外特性
--可以额外指定row_id、serial#、session#、thread#、tx_name、username --指定捕获(capture)ROW_ID、USERNAME begin dbms_capture_adm.include_extra_attribute( capture_name=>'DBXA_CAP', attribute_name=>'ROW_ID', include=>TRUE ); dbms_capture_adm.include_extra_attribute( capture_name=>'DBXA_CAP', attribute_name=>'USERNAME', include=>TRUE ); end; / PL/SQL procedure successfully completed. --查看包含LCR的额外特性信息 select attribute_name, include from dba_capture_extra_attributes where capture_name='DBXA_CAP' order by attribute_name; ATTRIBUTE_NAME INCLUDE --------------------------- ------------- ROW_ID YES SERIAL# NO SESSION# NO THREAD# NO TX_NAME NO USERNAME YES rows selected.2 、访问 LCR 内容
--展示提取和修改LCR内容的方法 --创建一个审计表,用来存储LCR相关信息 connstrmadmin/[email protected] Connected. createtablesalary_audit( msg_datedate default sysdate, source_database_name varchar2(30), command_type varchar2(30), object_owner varchar2(30), object_name varchar2(30), is_null_tag varchar2(1), tag varchar2(30), transaction_id varchar2(30), scn number, empno number, column_name varchar2(30), old_value number, new_value number, row_id varchar2(30), username varchar2(30) ); Tablecreated. --创建DML处理存储过程 create or replace procedure salary_audit_proc ( in_any IN SYS.ANYDATA ) is -- Define variable lcr and rc, to access contents of row. -- LCR from sys.lcr$_row_record type and to hold the return -- code respectively. lcr SYS.LCR$_ROW_RECORD; rc PLS_INTEGER; -- Define local variables to hold values for various data -- fields from the LCR to insert into the salary_audit table. l_source_db varchar2(30); l_command_type varchar2(30); l_object_owner varchar2(30); l_object_name varchar2(30); l_is_null_tag varchar2(1); l_tag varchar2(30); l_transaction_id varchar2(30); l_scn number; l_empno number; l_column_name varchar2(30); l_old_value number; l_new_value number; l_row_id varchar2(30); l_username varchar2(30); l_extra_attr ANYDATA; l_get_number ANYDATA; begin -- Access the row LCR from the parameter in_any. -- We use the GETOBJECT function of the SYS.ANYDATA type. rc := in_any.GETOBJECT(lcr); -- The contents of the LCR are now accessible using various -- member functions of the SYS.LCR$_ROW_RECORD type. -- Extract source_database_name using GET_SOURCE_DATABASE_NAME -- function. l_source_db := lcr.GET_SOURCE_DATABASE_NAME(); -- Extract command_type using GET_COMMAND_TYPE function. l_command_type := lcr.GET_COMMAND_TYPE(); -- Extract object_owner using GET_OBJECT_OWNER function. l_object_owner := lcr.GET_OBJECT_OWNER(); -- Extract object_name using GET_OBJECT_NAME function. l_object_name := lcr.GET_OBJECT_NAME(); -- Extract is_null_tag using IS_NULL_TAG function. l_is_null_tag := lcr.IS_NULL_TAG(); -- Extract tag value using GET_TAG function. l_tag := lcr.GET_TAG(); -- Extract transaction_id using GET_TRANSACTION_ID function. l_transaction_id := lcr.GET_TRANSACTION_ID(); -- Extract scn using GET_SCN function. l_scn := lcr.GET_SCN(); -- We will use the GET_VALUE function to extract the value -- of the EMPNO column. This column is configured for -- unconditional supplemental logging and so it will be -- present in the LCR for all changes to the row. -- Since the procedure is invoked for UPDATE operation, -- the EMPNO will be available in the old values in the LCR. -- The GET_VALUE function returns ANYDATA type. -- The ACCESSNUMBER function of ANYDATA will retrieve -- the number for EMPNO. l_get_number := lcr.GET_VALUE('OLD','EMPNO'); l_empno := l_get_number.ACCESSNUMBER(); -- We are tracking employee salary in column called SAL. -- There is no need to extract the column name from the LCR. -- We just set the variable to the column name. l_column_name := 'SAL'; -- If the SAL column was updated then we want to store the -- old and new value of this column in our audit table. -- If it was changed then the LCR will have its old and -- new value. If it was not changed, then the LCR will not -- have new values. -- The parameters for the GET_VALUE function indicate that we -- are looking for the NEW values of SAL column and do not -- want (N) the function to return old values. l_get_number := lcr.GET_VALUE('NEW','SAL'); l_new_value := l_get_number.ACCESSNUMBER(); -- If l_new_value is NOT NULL, then it means the column was -- changed, and we will extract the old value. if l_new_value is not null then l_get_number := lcr.GET_VALUE('OLD','SAL','Y'); l_old_value := l_get_number.ACCESSNUMBER(); end if; -- The rowid and username are the extra attributes that we have -- captured in the LCR. The SYS.ANYDATA provides a member function -- called GET_EXTRA_ATTRIBUTE to access those by their names. -- The function returns the value as ANYDATA. We then need to use -- the ACCESSUROWID and ACCESSVARCHAR2 functions to extract their -- values from the ANYDATA type. l_extra_attr := lcr.GET_EXTRA_ATTRIBUTE('row_id'); l_row_id := l_extra_attr.ACCESSUROWID(); l_extra_attr := lcr.GET_EXTRA_ATTRIBUTE('username'); l_username := l_extra_attr.ACCESSVARCHAR2(); -- Now, we insert the values in our audit table, only if the -- SAL column was updated. if l_new_value is not null then insert into salary_audit values (sysdate, l_source_db, l_command_type, l_object_owner, l_object_name, l_is_null_tag, l_tag, l_transaction_id, l_scn, l_empno, l_column_name, l_old_value, l_new_value, l_row_id, l_username ); end if; -- Do not commit this row. It will be automatically done after -- we execute the LCR. Please note that the LCR was handed to our -- procedure by the apply process when it detected an UPDATE -- operation against the SCOTT.EMP table. So, we must execute -- the LCR irrespective of the change to the SAL column. lcr.execute (true); -- The TRUE option in the above command enables the automatic -- conflict detection by the apply process. -- Setting it to FALSE disables it. end; / Procedure created. show errors No errors. --将存储过程与APPLY进程关联 begin dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'UPDATE', error_handler => FALSE, user_procedure => 'STRMADMIN.SALARY_AUDIT_PROC', apply_name => 'DBXA_APP' ); end; / PL/SQL procedure successfully completed. --当对EMP表的员工编号7566工作从2975改成3000表更改,审计表信息如下: EMPNO SOURCE_DB COMMAND OWNER TABLE_NAME IS_NULL_TAG TAG TXN_ID SCN COLUMN OLD_VALUE NEW_VALUE ROW_ID USERNAME ------ -------------- -------- -------- ------------ ----------- --- ---------- ------- -------- ---------- ---------- ------------------ -------- 7566 DBXA.WORLD UPDATE SCOTT EMP Y 6.0.2049 4188833 SAL 2975 3000 AAAPqZAAEAAAACzAAD SCOTT3 、访问 DDL LCR 内容
--创建DDL LCR审计表 conn strmadmin/[email protected] Connected. create table ddl_audit ( msg_date date, source_database_name varchar2(30), command_type varchar2(30), object_owner varchar2(30), object_name varchar2(30), object_type varchar2(20), ddl_text clob, logon_user varchar2(30), current_schema varchar2(30), base_table_owner varchar2(30), base_table_name varchar2(30), streams_tag raw(10), transaction_id varchar2(30), scn number ) / Table created. --创建处理存储过程 create or replace procedure ddl_audit_proc (in_any IN SYS.ANYDATA ) is lcr SYS.LCR$_DDL_RECORD; rc PLS_INTEGER; l_ddl_text CLOB default empty_clob(); begin -- Access the DDL LCR from the parameter in_any. -- We use the GETOBJECT function of the SYS.ANYDATA type. rc := in_any.GETOBJECT(lcr); -- The contents of the DDL LCR are now accessible using various -- member functions of the SYS.LCR$_DDL_RECORD type. -- These functions are similar in behavior to the ones we saw -- earlier for accessing the row LCR contents. -- To access the DDL Text in the LCR we initialize the -- LOB segment. dbms_lob.createtemporary(l_ddl_text, TRUE); -- Extract the DDL Text from the LCR into the local CLOB. lcr.GET_DDL_TEXT(l_ddl_text); -- Extract information from DDL LCR to insert into the -- DDL Audit table using member functions -- of SYS.LCR$_DDL_RECORD. insert into ddl_audit VALUES (SYSDATE, lcr.GET_SOURCE_DATABASE_NAME(), lcr.GET_COMMAND_TYPE(), lcr.GET_OBJECT_OWNER(), lcr.GET_OBJECT_NAME(), lcr.GET_OBJECT_TYPE(), l_ddl_text, lcr.GET_LOGON_USER(), lcr.GET_CURRENT_SCHEMA(), lcr.GET_BASE_TABLE_OWNER(), lcr.GET_BASE_TABLE_NAME(), lcr.GET_TAG(), lcr.GET_TRANSACTION_ID(), lcr.GET_SCN() ); -- We want to ignore the DDL command that creates indexes -- in the destination database. if lcr.GET_COMMAND_TYPE != 'CREATE INDEX' then lcr.execute(); end if; -- Free the temporary LOB from temp tablespace. DBMS_LOB.FREETEMPORARY(l_ddl_text); END; / Procedure created. show errors No errors. --和APPLY进程关联 begin dbms_apply_adm.alter_apply( apply_name => 'DBXA_APP', ddl_handler => 'DDL_AUDIT_PROC' ); end; / PL/SQL procedure successfully completed. --假如在EMP表中添加了新列,可以在审计表中看到以下内容 COMMAND LOGON_USER OBJECT_OWNER OBJECT_NAME OBJECT_TYPE BASE_TABLE_OWNER BASE_TABLE_NAME TRANSACTION_ID SCN DDL_TEXT ------------ ---------- ------------ ----------- ----------- ---------------- --------------- -------------- ---------- ---------------------------------------- ALTER TABLE STRMADMIN SCOTT EMP TABLE SCOTT EMP 9.9.2022 4202354 alter table scott.emp add (PHONE number)4 、修改 LCR 内容
--修改行LCR --将HIRE_DATE和BIRTH_DATE数据类型改为DATE类型 create or replace procedure convert_timestamp_to_date (in_any in anydata) is lcr SYS.LCR$_ROW_RECORD; rc PLS_INTEGER; l_command_type varchar2(30); l_timestamp timestamp; l_new_values SYS.LCR$_ROW_LIST; l_old_values SYS.LCR$_ROW_LIST; begin -- Access the row LCR. rc := in_any.GETOBJECT(lcr); -- Extract the command_type l_command_type := lcr.GET_COMMAND_TYPE(); -- Check for the command type and perform actions. -- If the command was INSERT, then we will have only -- the new values for the table columns. if l_command_type='INSERT' then -- Extract the list of new values. l_new_values := lcr.GET_VALUES('NEW'); -- Loop through the new values list. for i in 1 .. l_new_values.count loop if l_new_values(i).data is not null then -- Check if interested columns are in the list. if l_new_values(i).column_name in ('HIRE_DATE','BIRTH_DATE') then -- Extract the values of the interested columns. rc := l_new_values(i).data.GETTIMESTAMP(l_timestamp); -- Convert the Timestamp data type to Date. l_new_values(i).data := ANYDATA.CONVERTDATE(to_date(trunc(l_timestamp))); end if; end if; end loop; -- Set the new values list in the LCR. lcr.SET_VALUES('NEW',value_list=>l_new_values); -- -- If the command was UPDATE then we will have -- the old and new values for the table columns. elsif l_command_type='UPDATE' then -- Extract the list of Old values. l_old_values := lcr.GET_VALUES('OLD', 'Y'); for i in 1 .. l_old_values.count -- Loop through the old values list. loop if l_old_values(i).data is not null then -- Check if interested columns are in the list. if l_old_values(i).column_name in ('HIRE_DATE','BIRTH_DATE') then -- Extract the values of the interested columns. rc := l_old_values(i).data.GETTIMESTAMP(l_timestamp); -- Convert the Timestamp data type to Date. l_old_values(i).data := ANYDATA.CONVERTDATE(to_date(trunc(l_timestamp))); end if; end if; end loop; -- Set the old values list in the LCR. lcr.SET_VALUES('OLD',value_list=>l_old_values); -- Now, extract the list of new values. l_new_values := lcr.GET_VALUES('NEW', 'N'); -- Loop through the new values list. for i in 1 .. l_new_values.count loop if l_new_values(i).data is not null then -- Check if interested columns are in the list. if l_new_values(i).column_name in ('HIRE_DATE','BIRTH_DATE') then -- Extract the values of the interested columns. rc := l_new_values(i).data.GETTIMESTAMP(l_timestamp); -- Convert the Timestamp data type to Date. l_new_values(i).data := ANYDATA.CONVERTDATE(to_date(trunc(l_timestamp))); end if; end if; end loop; -- Set the new values list in the LCR. lcr.SET_VALUES('NEW',value_list=>l_new_values); -- -- If the command was DELETE then we will have only -- the old values for the table columns. elsif l_command_type ='DELETE' then l_old_values := lcr.GET_VALUES('OLD', 'Y'); for i in 1 .. l_old_values.count -- Loop through the old values list. loop if l_old_values(i).data is not null then -- Check if interested columns are in the list. if l_old_values(i).column_name in ('HIRE_DATE','BIRTH_DATE') then -- Extract the values of the interested columns. rc := l_old_values(i).data.GETTIMESTAMP(l_timestamp); -- Convert the Timestamp data type to Date. l_old_values(i).data := ANYDATA.CONVERTDATE(to_date(trunc(l_timestamp))); end if; end if; end loop; -- Set the old values list in the LCR. lcr.SET_VALUES('OLD',value_list=>l_old_values); end if; -- Execute the modified LCR. lcr.execute(true); end; / Procedure created. SQL> show errors No errors. --将上面的存储过程与应用程序关联 begin dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'UPDATE', error_handler => FALSE, user_procedure => 'STRMADMIN.CONVERT_TIMESTAMP_TO_DATE', apply_name => 'DBXA_APP' ); dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'INSERT', error_handler => FALSE, user_procedure => 'STRMADMIN.CONVERT_TIMESTAMP_TO_DATE', apply_name => 'DBXA_APP' ); dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'DELETE', error_handler => FALSE, user_procedure => 'STRMADMIN.CONVERT_TIMESTAMP_TO_DATE', apply_name => 'DBXA_APP' ); end; / PL/SQL procedure successfully completed.5 、修改 DDL LCR (替换 schema 和 owner )
connect strmadmin/[email protected] Connected. create or replace procedure ddl_handler ( in_any IN SYS.ANYDATA ) is lcr SYS.LCR$_DDL_RECORD; rc PLS_INTEGER; l_ddl_text CLOB default empty_clob(); l_ddl_text_new CLOB default empty_clob(); l_source_schema1 varchar2(30); l_source_schema2 varchar2(30); l_dest_schema1 varchar2(30); l_dest_schema2 varchar2(30); begin -- Initialize the variables for source and destination schema names. l_source_schema1 := ' '||'SCOTT'; l_dest_schema1 := ' '||'KIRTI'; l_source_schema2 := ' "'||'SCOTT'||'"'; l_dest_schema2 := ' "'||'KIRTI'||'"'; -- Access the DDL LCR from the parameter in_any. -- We use the GETOBJECT function of the SYS.ANYDATA type. rc := in_any.GETOBJECT(lcr); -- The contents of the DDL LCR are now accessible using various -- member functions of the SYS.LCR$_DDL_RECORD type. -- To access the DDL Text in the LCR we initialize the -- LOB segment. dbms_lob.createtemporary(l_ddl_text, TRUE); -- Extract the DDL Text from the LCR into the local variable. lcr.GET_DDL_TEXT(l_ddl_text); -- Change the current schema to match the destination schema name. lcr.SET_CURRENT_SCHEMA('KIRTI'); -- Change the object owner to match the destination object owner. lcr.SET_OBJECT_OWNER('KIRTI'); -- Using the Regular Expression function, replace all occurrences -- of the source schema name to destination schema name in the DDL text. l_ddl_text_new := regexp_replace(l_ddl_text,l_source_schema1||'\.',l_dest_schema1,1,0,'i'); l_ddl_text_new := regexp_replace(l_ddl_text_new,l_source_schema2||'\.',l_dest_schema2,1,0,'i'); -- Set the DDL text in the LCR to the new text. lcr.SET_DDL_TEXT(l_ddl_text_new); -- Free the temp lob for DDL text. dbms_lob.freetemporary(l_ddl_text); -- Execute the modified DDL LCR. lcr.execute(); end; / Procedure created. show errors No errors. --和APPLY进程关联 begin dbms_apply_adm.alter_apply( apply_name => 'DBXA_APP', ddl_handler => 'DDL_HANDLER' ); end; / PL/SQL procedure successfully completed.6 、 LCR 和 LOB 数据类型
--修改目标数据库应用LOB,提高应用效率 --创建对行操作handler create or replace procedure LOB_ASSEMBLER(in_any IN SYS.ANYDATA) is lcr SYS.LCR$_ROW_RECORD; rc PLS_INTEGER; BEGIN -- Access the LCR rc := in_any.GETOBJECT(lcr); -- Apply the row LCR lcr.EXECUTE(TRUE); END; / Procedure created. --为含有LOB类型的DML指定APPLY进程与LOB_ASSEMBLER关联 begin dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'INSERT', error_handler => TRUE, assemble_lobs => TRUE, user_procedure => 'strmadmin.lob_assembler', apply_name => 'DBXA_APP'); dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'UPDATE', error_handler => TRUE, assemble_lobs => TRUE, user_procedure => 'strmadmin.lob_assembler', apply_name => 'DBXA_APP'); dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'DELETE', operation_name => 'INSERT', error_handler => TRUE, assemble_lobs => TRUE, user_procedure => 'strmadmin.lob_assembler', apply_name => 'DBXA_APP'); dbms_apply_adm.set_dml_handler( object_name => 'SCOTT.EMP', object_type => 'TABLE', operation_name => 'LOB_UPDATE', error_handler => TRUE, assemble_lobs => TRUE, user_procedure => 'strmadmin.lob_assembler', apply_name => 'DBXA_APP' ); end; / PL/SQL procedure successfully completed.