【问题标题】:Spring 3.1 and Oracle Audit Trail: Providing application data to triggersSpring 3.1 和 Oracle Audit Trail:向触发器提供应用程序数据
【发布时间】:2012-07-04 13:33:13
【问题描述】:

问题参数

  1. 春季 3.1
  2. Oracle 11.2.0.3
  3. Glassfish 2.1 应用服务器,提供 JDBC 连接池。

问题描述

我正在改进一组现有管理应用程序中的用户审核,以添加、编辑和删除客户用户。我需要将管理用户的 ID 存储在由与多个表关联的 Oracle 触发器创建的审计记录中。我想通过在数据库操作之前从连接池中检索到的每个连接上设置 Oracle CLIENT_IDENTIFIER 属性来使触发器可以访问管理用户 ID,然后在数据库操作之后清除该属性。我有一些可行的方法,但我不太喜欢它的完成方式。

问题:

有没有办法访问连接,以便可以在数据库操作之前和之后设置 Oracle 上下文属性?也许某种监听器响应事件?

我看过:

  1. 一百万个网页(好吧,也许这有点夸张,但我已经用谷歌搜索了三四个小时)。
  2. 使用 DataSourceUtils 获取连接。这可行,但我真的不想管理连接,我只想在进出池的途中拦截它们以设置 CLIENT_IDENTIFIER 属性值。
  3. 覆盖数据源的 getConnection 方法。由于这在 JdbcTemplate 中的某个地方被调用,因此我无法将应用程序数据获取到该方法。

我希望 Spring 和/或 Oracle 大师会说“嗯,这很明显,答案是……”,而不必破解我的实现,但无论如何都是这样。如果不出意外,如果有人正在寻找想法,这确实有效。

我的实现:

所有数据库操作都是使用 JdbcOperations 对注入到 Dao 中的 JdbcTemplate 对象的引用来完成的。添加、编辑和删除操作使用 JdbcOperations 查询方法,传递 PreparedStatementCreator 或 BatchPreparedStatementSetter。我在这些对象的回调方法(createPreparedStatement 或 setValues)中访问应用程序服务器连接池提供的 java.sql.Connection 对象,以设置 CLIENT_IDENTIFIER 属性。

applicationContext.xml 数据源配置:

<!-- Setup the datasource -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/IpOneDatabasePool"/>
</bean>

<!-- Setup the transaction manager -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!-- Associate the transaction manager with objects that must be managed. -->
<aop:config>
    <aop:pointcut id="userDaoOperation" expression="execution(* com.myCompany.IpOne.dao.UserDaoImpl.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="userDaoOperation"/>
</aop:config>

<!-- Bean providing access to the various prepared statement objects -->
<bean id="daoHelperFactory" class="com.myCompany.IpOne.dao.DaoHelperFactoryImpl" />

<!--  Bean that allows setting of the client identifier for the audit trail -->  
<bean id="databaseContextEditor" class="com.myCompany.IpOne.dao.OracleDatabaseContextEditor" />

<!-- Dao that manages persistence of User objects -->
<bean id="userDao" class="com.myCompany.IpOne.dao.UserDaoImpl" >
    <property name="dataSource" ref="dataSource"/>
    <property name="licenseDao" ref="licenseDao"/>
    <property name="appPropertyManager" ref="appPropertyManager"/>
    <property name="maximumLicensesPerUserKey" value="max_licences_per_user"/>
    <property name="daoHelperFactory" ref="daoHelperFactory"/>
</bean>

这是用户道界面

public interface UserDao {

    void addUser(User newUser,String adminUserId);

}

这是用户道类

public class UserDaoImpl implements UserDao{

    private JdbcOperations jdbcOperations;

    public void setDataSource(DataSource dataSource) {
        this.jdbcOperations = new JdbcTemplate(dataSource);
    }

    public void addUser(User newUser,String adminUserId) {

        PreparedStatementCreator insertUserStatement = 
            this.daoHelperFactory.getInsertUserStatement(newUser,adminUserId);

        KeyHolder keyHolder = this.daoHelperFactory.getKeyHolder();
        this.jdbcOperations.update(insertUserStatement, keyHolder);
        newUser.setUserId(keyHolder.getKey().intValue());

    }

}

此类提供对应用程序上下文的访问。

public class ApplicationContextProvider implements ApplicationContextAware{

    private static ApplicationContext ctx = null;

    public static ApplicationContext getApplicationContext() {
       return ctx;
    }

    public void setApplicationContext(ApplicationContext ctx) throws BeansException {

        this.ctx = ctx;

    }

}

提供 Dao 使用的各种对象的类的接口。

public interface DaoHelperFactory {

    PreparedStatementCreator getInsertUserStatement(User user,String adminUserId);   
    KeyHolder getKeyHolder();

}

这个类只是 PreparedStatementCreator 和 BatchPreparedStatementSetter 对象以及 Dao 使用的其他对象的工厂。我已经对其进行了更改,以提供将数据库上下文属性实际设置为返回的各种对象的对象。

public class DaoHelperFactoryImpl implements DaoHelperFactory{

    private DatabaseContextEditor getDatabaseContextEditor(){

        ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
        DatabaseContextEditor databaseContextEditor = (DatabaseContextEditor) appContext.getBean("databaseContextEditor");

        return databaseContextEditor;

     }

    public KeyHolder getKeyHolder(){

        return new GeneratedKeyHolder();

    }

    public PreparedStatementCreator getInsertUserStatement(User user,String adminUserId){

        InsertUser insertUser = new InsertUser(user,adminUserId);
        insertUser.setDatabaseContextEditor(getDatabaseContextEditor());

        return insertUser;

    }

}

这是设置数据库上下文的类的接口

public interface DatabaseContextEditor {

    public DatabaseContextEditor getInstance();
    public  void setClientIdentifier(Connection connection,String clientIdentifier)        throws SQLException;

}

这是为 Oracle 做的类

public class OracleDatabaseContextEditor implements DatabaseContextEditor{


    public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException{

        OracleJdbc4NativeJdbcExtractor extractor = new OracleJdbc4NativeJdbcExtractor();

        oracle.jdbc.OracleConnection oracleConnection = null;

        if(!(connection instanceof oracle.jdbc.OracleConnection))
            oracleConnection = (oracle.jdbc.OracleConnection) extractor.getNativeConnection(connection);
        else
            oracleConnection = (oracle.jdbc.OracleConnection)connection;

        String[] metrics = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
        metrics[OracleConnection.END_TO_END_CLIENTID_INDEX]=clientIdentifier;
        oracleConnection.setEndToEndMetrics(metrics,(short)0);

}

    public DatabaseContextEditor getInstance(){

        return new OracleDatabaseContextEditor();

    }
}

这个类是用于添加用户的 PreparedStatementCreator

public class InsertUser implements PreparedStatementCreator {

     User insertUser;

    /** This is the admin user Id I need to store */
    String adminUserId;

    private final String SQL = "INSERT INTO SC_USR (" +
    "USR_ID, USR_SSO_NAME, USR_PH_NO, USR_SIP_NAME," +
    "USR_SIP_PSWD, USR_SIP_DISP_NAME, USR_SIP_DOMAIN, USR_SIP_PROXY," +
    " USR_CREATED_BY, USR_CREATED_DATETIME) " +
    "VALUES (SEQ_SC_USR_ID.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, SYSTIMESTAMP)";

    private final String GENERATED_COLUMNS[] = {"USR_ID"};

    /** Object that provides functionality for setting values in the database context */
    private DatabaseContextEditor databaseContextEditor;


    public InsertUser(User user,String adminUserId){

        this.insertUser = user;
        this.adminUserId = adminUserId;

    }

    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {

        this.databaseContextEditor.setClientIdentifier(connection, adminUserId);

        PreparedStatement preparedStatement =      connection.prepareStatement(SQL,GENERATED_COLUMNS);
        int i=1;
        preparedStatement.setString(i++,this.insertUser.getSsoName());
        preparedStatement.setString(i++,this.insertUser.getPhoneNumber());
        preparedStatement.setString(i++,this.insertUser.getSipName());
        preparedStatement.setString(i++,this.insertUser.getSipPassword());
        preparedStatement.setString(i++,this.insertUser.getSipDisplayName());
        preparedStatement.setString(i++,this.insertUser.getSipDomain());
        preparedStatement.setString(i++,this.insertUser.getSipProxy());
        preparedStatement.setString(i++,this.insertUser.getCreatedBy().name());


        return preparedStatement;

    }

    public void setDatabaseContextEditor(DatabaseContextEditor databaseContextEditor) {
        this.databaseContextEditor = databaseContextEditor;
    }
}

在我要审核的每个表上都有“AFTER DELETE OR INSERT OR UPDATE”触发器。每个表都有一个对应的审计表。他们从上下文中提取 CLIENT_IDENTIFIER 并在适当的审计表中插入一行。这是一个示例。

CREATE OR REPLACE TRIGGER IPONE_DEV_USER.SC_USR$AUDTRG
AFTER DELETE OR INSERT OR UPDATE
ON IPONE_DEV_USER.SC_USR 
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE 
v_operation VARCHAR2(10) := NULL;
v_admin_user_id VARCHAR2(30);

BEGIN 

    v_admin_user_id := SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER');
    IF INSERTING THEN 
       v_operation := 'INS'; 
    ELSIF UPDATING THEN 
       v_operation := 'UPD'; 
    ELSE 
       v_operation := 'DEL'; 
    END IF; 

IF INSERTING OR UPDATING THEN

   INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SOME_VALUE1,
USR_SOME_VALUE2,
USR_SOME_VALUE3,
USR_SOME_VALUE4,
USR_CREATED_BY,
USR_SOME_VALUE5,
USR_SOME_VALUE6,
aud_action,aud_timestamp,aud_user) VALUES (
:new.USR_ID,
:new.USR_SSO_NAME,
:new.USR_PH_NO,
:new.USR_SOME_VALUE1,
:new.USR_SOME_VALUE2,
:new.USR_SOME_VALUE3,
:new.USR_CREATED_DATETIME,
:new.USR_CREATED_BY,
:new.USR_SOME_VALUE4,
:new.USR_SOME_VALUE5,
v_operation,SYSDATE,v_admin_user_id);

ELSE 

   INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SIP_NAME,
USR_SIP_PSWD,
USR_SIP_DISP_NAME,
USR_CREATED_DATETIME,
USR_CREATED_BY,
USR_SIP_DOMAIN,
USR_SIP_PROXY,
aud_action,aud_timestamp,aud_user) VALUES (
:old.USR_ID,
:old.USR_SSO_NAME,
:old.USR_PH_NO,
:old.USR_SIP_NAME,
:old.USR_SIP_PSWD,
:old.USR_SIP_DISP_NAME,
:old.USR_CREATED_DATETIME,
:old.USR_CREATED_BY,
:old.USR_SIP_DOMAIN,
:old.USR_SIP_PROXY,
v_operation,SYSDATE,v_admin_user_id);

   END IF;
END;

正如我所说的那样有效,但我不喜欢它,原因如下。

  1. 我必须修改用于设置准备好的语句的方法中的连接。
  2. 我必须将此代码添加到我要审核的每个 PreparedStatementCreator 或 BatchPreparedStatementSetter 对象中。
  3. 数据库操作后我无权访问连接,所以我可以清除属性。

我真正想要的是一个可以在连接之前和之后设置属性的点。

任何意见或想法将不胜感激。

【问题讨论】:

    标签: spring oracle11g spring-jdbc


    【解决方案1】:

    Spring 有一种优雅的方式来做到这一点。该示例几乎是您想要的: Spring Data docs

    当从数据源获得连接时,使用 AOP 设置 CLIENT_IDENTIFIER。

    可以在连接关闭时使用另一个切入点。但不是问题是您的应用单独使用了连接池。

    【讨论】:

    【解决方案2】:

    更好的方法是使用连接标签。看看 oracle.ucp.jdbc.LabelableConnection 接口。

    【讨论】:

    • 感谢史蒂夫的回复。我查看了link 的 Oracle 文档“在 UCP 中标记连接”。我仍然需要使用数据源 getConnection(label) 方法从池中借用连接,不是吗?我不清楚这与使用 Spring 实用程序类 DataSourceUtils getConnection(dataSource) 方法获取连接有何不同。
    • 它会调用回调并且在回调中你可以确保客户端标识符总是被设置。
    • 如果我可以在每个管理用户访问站点时注册一个新的标签回调实例,并且我仍然可以让 Spring 管理连接,它可以工作。谢谢史蒂夫,我会试一试并发布更新。
    猜你喜欢
    • 2022-07-14
    • 1970-01-01
    • 2011-01-31
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多