Oracle 業務表自制審計字段使用

審計字段即記錄數據的建立人、建立時間、修改人、修改時間的字段、體如今每一張數據庫表中。爲了減小代碼量須要設置一套通用的方法。 java

思路:登陸用戶存入session,訪問數據庫的時候攔截器獲取連接先設入數據庫session,隨後進行業務邏輯,每一個表設置trigger,每次更新時獲取session中的用戶名稱,設置入審計字段。 web

一、用戶信息Web到App的傳遞。 sql


思路:攔截分發器,每次分發請求的時候在ServiceRequest中設置用戶信息傳遞到App。 數據庫


首先設置頁面攔截,獲取session中的用戶信息放入用戶線程上下文。 session

web-context.xml app

<bean id="sessionInterceptor" class="com.palic.egis.common.web.util.SessionInterceptor">
        <description>AuthorizationController用來檢查用戶是否login</description>
        <property name="worktableAssociatedFilter">
			<list>
				<value>/index.screen</value>
			</list>
		</property>
    </bean> 
    <bean id="defaultHandlerMapping" class="com.paic.pafa.app.web.servlet.handler.BeanNameUrlHandlerMapping">
        <description>
           	 當一個HTTP請求進來的時候,interceptors先攔截請求,進行預先處理。
        </description>
        <property name="interceptors">
            <list>
                <ref local="sessionInterceptor"/>
              </list>
        </property>
    </bean>

common-context.xml less

<!--=====================================================================-->
	<!-- 線程context的配置 -->
	<!--=====================================================================-->
	<bean id="userThreadContext"
          class="com.paic.pafa.core.service.PafaThreadContext">
        <description>線程的Context</description>
    </bean>

SessionInterceptor.java  攔截器實現 函數

/**
 * 用於檢查Session裏是有用戶登陸信息的攔截器。Interceptor的功能相似於Servlet的Filter。
 * 當一個HTTP請求進來的時候,interceptors先攔截請求,進行預先處理。
 * 
 * @author Leo Liao, 2005-4-14, created
 * @version $Revision$Date$
 * @see com.palic.egis.support.privilege.web.controller.LoginController
 */
public class SessionInterceptor extends HandlerInterceptorAdapter {
	// 用於獲取用戶信息的LoginController
	private Controller	authorizationController;

	// 目前SessionAdmin有待完善,請暫時不要使用
	// private SessionAdmin sessionAdmin;

	/**
	 * 檢查session裏是否是有用戶信息
	 * 
	 * @see com.palic.egis.support.privilege.web.controller.LoginController
	 */
	private boolean checkSession(HttpServletRequest request) {
		HttpSession session = request.getSession();
		if (session != null && session.getAttribute("currentUser") != null) {
			return true;
		}
		return false;
	}

	public final boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler)
			throws PafaWebException {
		try {
			if (!checkSession(request)) {
				authorizationController.handleRequest(request, response);
				if (!checkSession(request)) {
					throw new PafaWebException("appdemo.error.session.invalid");
				}
			}

			// begin 設置當前用戶到線程上下文
			HttpSession session = request.getSession();
			String uid = (String) session.getAttribute("currentUser");

			PafaThreadContext tc = (PafaThreadContext) PafaAppCommonContexton
					.getInstance()
					.getBean(SystemObjectName.USER_THREAD_CONTEXT);

			tc.putTxnID(PafaCoreContexton.getInstance().getIDGenerator()
					.getID());
			tc.putUserID(uid);

			// end

		} catch (PafaWebException ex) {
			// throw new PafaWebException("appdemo.error.session.invalid", ex);
			request.getSession().setAttribute("loginError",
					ex.getInitialCause().getMessage());
		} catch (Exception ex) {
			// throw new PafaWebException("appdemo.error.session.invalid", ex);
			request.getSession().setAttribute("loginError", ex.getMessage());
		}
		return true;

	}

	/**
	 * 清除threadContext中設置的用戶信息
	 */
	public final void afterCompletion(HttpServletRequest httpservletrequest,
			HttpServletResponse httpservletresponse, Object obj,
			Exception exception) throws Exception {
		PafaThreadContext tc = (PafaThreadContext) PafaAppCommonContexton
				.getInstance().getBean(SystemObjectName.USER_THREAD_CONTEXT);
		tc.clear();

		super.afterCompletion(httpservletrequest, httpservletresponse, obj,
				exception);
	}

}

common-context.xml    分發器配置 fetch


<!-- Real pafaAC -->
	<bean id="pafaACTarget"
		class="com.paic.pafa.app.lwc.service.remoting.access.ejb.SmartRemoteStatelessSessionProxyFactoryBean">
		<property name="jndiName">
			<value>ejb/egis/PafaAC</value>
		</property>
		<property name="businessInterface">
			<value>
				com.paic.pafa.app.biz.ac.ApplicationController
			</value>
		</property>
		<property name="jndiTemplate">
			<ref local="pafaACJndi" />
		</property>
	</bean>
	<!-- pafaAC Proxy -->
	<bean id="pafaAC"
        class="com.paic.pafa.app.lwc.core.aop.framework.ProxyFactoryBean">
    	<property name="target">
            <ref local="pafaACTarget"/>
        </property>
    	
    	<property name="proxyInterfaces">
            <value>com.paic.pafa.app.biz.ac.ApplicationController</value>
        </property>
    	<property name="interceptorNames">
    		<list>
    		    <value>dispatchServiceAdvisor</value>
    		</list>
    	</property>
    </bean>
    <bean id="dispatchServiceAdvisor"
        class="com.paic.pafa.app.lwc.core.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="mappedName">
            <value>handleRequest</value>
        </property>
        <property name="advice">
            <ref bean="dispatchServiceInterceptor"/>
        </property>    
    </bean>
    <bean id="dispatchServiceInterceptor"
        class="com.palic.egis.common.web.util.ThreadContextInterceptor">
        <property name="threadContext">
        	<ref bean="userThreadContext"/>
        </property>
    </bean>

ThreadContextInterceptor.java ui

public class ThreadContextInterceptor implements MethodInterceptor {

	private PafaThreadContext threadContext = null;
	
	public PafaThreadContext getThreadContext() {
		return threadContext;
	}

	public void setThreadContext(PafaThreadContext threadContext) {
		this.threadContext = threadContext;
	}

	public Object invoke(MethodInvocation method) throws Throwable {
		
		Object[] args = method.getArguments();
		
		
		ServiceRequest sr = (ServiceRequest) args[0];
		if (sr != null) {

			SessionDTO dto = sr.getSessionDTO();
			
			if(dto.getUserId() == null){
				dto.setUserId(threadContext.getUserID());				 
			}
			
			if (dto.getTxnId() == null) {
				dto.setTxnId(threadContext.getTxnID());
			}

		}

		return method.proceed();
	}



二、APP層傳入數據庫

思路:攔截數據源,先設置數據庫session在執行業務邏輯

biz-context.xml

<bean id="dsFactory"
        class="com.paic.pafa.app.lwc.service.persistence.datasource.DataSourceFactoryBean" >
        <property name="defaultDSKey">
            <description>缺省的數據源,必須指定爲下面map中的entry key之一</description>
            <value>GBSDS</value>
        </property>
        <property name="dataSources">
            <description>能夠在map屬性裏面添加多個數據源</description>
            <map>
                <entry key="GBSDS">
                    <ref local="defaultDS"/>
                </entry>
                <entry key="GBSDS_XA">
                    <ref local="gbsDS_XA"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="defaultDS"
		class="com.paic.pafa.app.lwc.core.aop.framework.ProxyFactoryBean">
		<property name="target">
			<ref local="defaultDSTarget" />
		</property>
		<property name="proxyInterfaces">
			<value>javax.sql.DataSource</value>
		</property>
		<property name="interceptorNames">
			<list>
				<value>dbConnectionAdvisor</value>
			</list>
		</property>
	</bean>     
    <bean id="dbConnectionAdvisor"
        class="com.paic.pafa.app.lwc.core.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="mappedName">
            <value>getConnection</value>
        </property>
        <property name="advice">
            <ref bean="dbConnectionInterceptor"/>
        </property>    
    </bean>
    
    <bean id="dbConnectionInterceptor"
        class="com.palic.egis.common.util.SetLcuInterceptor">
    </bean>    
    
   <bean id="defaultDSTarget"
        class="com.paic.pafa.app.lwc.core.naming.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>${defaultDS}</value>
        </property>
        <property name="jndiTemplate">
            <ref local="jndiTemplate"/>
        </property>
    </bean>    
    
    <bean id="gbsDS_XA"
        class="com.paic.pafa.app.lwc.core.naming.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>${GBSDS_XA}</value>
        </property>
        <property name="jndiTemplate">
            <ref local="jndiTemplate"/>
        </property>
    </bean>



SetLcuInterceptor.java
public class SetLcuInterceptor implements MethodInterceptor {

	public Object invoke(MethodInvocation mi) throws Throwable {
		
		String userId = PafaCoreContexton.getInstance().getThreadContext().getUserID();

		Connection conn = (Connection) mi.proceed();
		
		if (userId != null && !userId.startsWith("V_") && !userId.equalsIgnoreCase("GMONIUSER")) {		
			
			try {
				PreparedStatement stat = null;
				
					// 設置LCU
					try {
						stat = conn.prepareStatement("{call pub_sys_package.set_attributes(?)}");
	
						stat.setString(1, userId);					
						stat.execute();
	
					} catch (Throwable e) {
						throw e;
					} finally {
						if (stat != null) {
							stat.close();
						}
					}	
			} catch (Throwable e) {
				DevLog.error("SetLcuInterceptor Error:" + e.getMessage());	
			}
		}
		return conn;
	}


}



pub_sys_package
CREATE OR REPLACE PACKAGE BODY pub_sys_package IS

--get user的兩種訪問1/pro
  PROCEDURE get_user_p(p_user OUT VARCHAR2) IS  
  BEGIN
    p_user := get_user;
  END get_user_p;
  
--get user的兩種訪問2/fun
  FUNCTION get_user RETURN VARCHAR2 IS
    v_user VARCHAR2(100);
  
    CURSOR cur_empno IS
      SELECT user_empno
        FROM gbs_user
        --根據登陸名稱查詢
       WHERE user_name = USER;
  
  BEGIN
  
    SELECT sys_context('user_policy_context', 'uid')
      INTO v_user
      FROM dual;
  
    IF v_user IS NULL THEN
      OPEN cur_empno;
      FETCH cur_empno
        INTO v_user;
      CLOSE cur_empno;
    END IF;
  
    IF v_user IS NULL THEN
      v_user := USER;    
    END IF;
  
    RETURN v_user;
  END get_user;

  --***********************************************
  -- 功能說明:
  --   在得到Connection時調用,用於將用戶信息設置到數據庫的context中
  -- 參數說明:
  --   uid     用戶標誌
  -- 調用函數:
  --   無
  --***********************************************
  PROCEDURE set_attributes(uid VARCHAR2) IS
    regionlist VARCHAR2(100) := NULL;
  
  BEGIN
    dbms_session.set_context('user_policy_context', 'logon', 'true');
    dbms_session.set_context('user_policy_context', 'uid', uid);
  END set_attributes;

  --***********************************************
  -- 功能說明:
  --   在得到Connection時調用,用於將用戶信息設置到數據庫的context中。egis-pos專用
  --   在lcu長度不夠的時代,保全利用該過程傳入full_uid,記錄在另外的字段中
  -- 參數說明:
  --   uid     用戶標誌
  -- 調用函數:
  --   無
  --***********************************************
  PROCEDURE set_attributes(uid VARCHAR2, full_uid VARCHAR2) IS
    regionlist VARCHAR2(100) := NULL;
  BEGIN
    dbms_session.set_context('user_policy_context', 'logon', 'true');
    dbms_session.set_context('user_policy_context', 'uid', uid);
    dbms_session.set_context('user_policy_context', 'full_uid', full_uid);
  END set_attributes;

  --***********************************************
  -- 功能說明:
  --   數據庫集中:將從UM中獲取的region信息set到context中
  -- 參數說明:
  --   uid            用戶名
  --   access_region  可訪問的region列表,以,分隔
  -- 調用函數:
  --   無
  --***********************************************
  PROCEDURE set_access_attributes(uid VARCHAR2, access_region VARCHAR2) IS
  BEGIN
    dbms_session.set_context('user_policy_context', 'logon', 'true');
    dbms_session.set_context('user_policy_context', 'uid', uid);
    dbms_session.set_context('user_policy_context',
                             'access_region',
                             access_region);
  END set_access_attributes;



  FUNCTION get_fcu(infcu IN VARCHAR2) RETURN VARCHAR2 IS
    v_fcu VARCHAR2(100);  
    v_user_empno VARCHAR2(100);
  
  BEGIN
  
    v_user_empno := pub_sys_package.get_user;
  
    IF v_user_empno = 'SOLIX' THEN
      --若是是歸檔用戶,則使用原有記錄的數據 
      v_fcu := infcu;     
    ELSE    
      v_fcu := v_user_empno;    
    END IF;
  
    RETURN v_fcu;
  END;

  FUNCTION get_fcd(infcd IN VARCHAR2) RETURN DATE IS
    v_fcd DATE;    
    v_user_empno VARCHAR2(100);  
  
  BEGIN
  
    v_user_empno := pub_sys_package.get_user;  
    IF v_user_empno = 'SOLIX' THEN
    --若是是歸檔用戶,則使用原有記錄的數據
      v_fcd := infcd;     
    ELSE    
      v_fcd := SYSDATE;    
    END IF;
  
    RETURN v_fcd;
  END;

  FUNCTION get_lcu(inlcu IN VARCHAR2) RETURN VARCHAR2 IS
    v_lcu VARCHAR2(100);
  BEGIN
    
    v_lcu := pub_sys_package.get_user;
    IF v_lcu IS NULL THEN
      v_lcu := inlcu;
    end IF;
    
    RETURN v_lcu;
  END;

  FUNCTION get_lcd(inlcd IN VARCHAR2) RETURN DATE IS
    v_lcd DATE;
  BEGIN    
    v_lcd := SYSDATE;
    RETURN v_lcd;
  END;

END pub_sys_package;



數據庫表中trigger實例:

插入

create or replace trigger TR_I_table_name
before insert on table_name
for each row
declare
    --通用變量定義
    v_trigger_user varchar2(100);
    v_trigger_date date;
    v_sqlcode varchar2(6);
    v_sqlerrm varchar2(200);
    v_error_comment varchar2(300);

    --針對審計字段更新功能定義的遊標和變量
    cursor c_switch(cp_switch gbs_tr_switch.switch_for%type) is
        select status from gbs_tr_switch
        where trigger_name='TR_I_table_name' and switch_for =cp_switch;
    v_status gbs_tr_switch.status%type;

begin
    v_error_comment:='before get user';
    v_trigger_user :=pub_sys_package.get_user;
    v_trigger_date :=sysdate;

    --需求來源:表中記錄的審計字段信息更新
    --功能描述:用於保證審計信息的完整性
    v_error_comment:='before GBS_insert_4_audit_column';
    open c_switch('table_name_in');
    fetch c_switch into v_status;
    if c_switch%found and v_status ='1' then
         :new.created_by:=v_trigger_user;
         :new.created_date:=v_trigger_date;
         :new.updated_by:=v_trigger_user;
         :new.updated_date:=v_trigger_date;
    end if;
    close c_switch;

    --需球來源:XXXX
    --功能描述:XXXX

    --錯誤處理
    --觸發器執行有誤,將出錯信息插入到gbs_tr_error_log表
    exception
    when others then
    v_sqlcode :=sqlcode;
    v_sqlerrm :=substr(sqlerrm,1,200);
    insert into  gbs_tr_error_log
   (
        error_no ,         --系統錯誤代碼
        error_message ,    --系統錯誤信息
        trigger_name ,     --出錯的trigger
        trigger_user ,     --出錯的用戶
        trigger_date ,     --出錯的時間
        error_comment      --出錯詳細信息
    )
    values
   (
        v_sqlcode,
        v_sqlerrm,
        'TR_I_table_name',
        v_trigger_user,
        v_trigger_date,
        v_error_comment
   );
end;



更新

CREATE OR REPLACE TRIGGER tr_u_table_name
  BEFORE UPDATE ON table_name
  FOR EACH ROW
DECLARE
  --通用變量定義
  v_trigger_user  VARCHAR2(100);
  v_trigger_date  DATE;
  v_sqlcode       VARCHAR2(6);
  v_sqlerrm       VARCHAR2(200);
  v_error_comment VARCHAR2(300);

  --針對審計字段更新功能定義的遊標和變量
  CURSOR c_switch(cp_switch gbs_tr_switch.switch_for%TYPE) IS
    SELECT status
      FROM gbs_tr_switch
     WHERE trigger_name = 'tr_u_table_name'
       AND switch_for = cp_switch;
  v_status gbs_tr_switch.status%TYPE;

BEGIN
  v_error_comment := 'before get user';
  v_trigger_user  := pub_sys_package.get_user;
  v_trigger_date  := SYSDATE;

  --需求來源:表中記錄的審計字段信息更新
  --功能描述:用於保證審計信息的完整性
  v_error_comment := 'before GBS_update_2_audit_column';
  OPEN c_switch('table_name_up');
  FETCH c_switch
    INTO v_status;
  IF c_switch%FOUND
     AND v_status = '1' THEN
    :new.updated_by   := v_trigger_user;
    :new.updated_date := v_trigger_date;
  END IF;
  CLOSE c_switch;

EXCEPTION
  WHEN OTHERS THEN
    v_sqlcode := SQLCODE;
    v_sqlerrm := substr(SQLERRM, 1, 200);
    INSERT INTO tr_error_log
      (error_no, --系統錯誤代碼
       error_message, --系統錯誤信息
       trigger_name, --出錯的trigger
       trigger_user, --出錯的用戶
       trigger_date, --出錯的時間
       error_comment --出錯詳細信息
       )
    VALUES
      (v_sqlcode,
       v_sqlerrm,
       'tr_u_table_name',
       v_trigger_user,
       v_trigger_date,
       v_error_comment);
END;
相關文章
相關標籤/搜索