1

问题参数

  1. 春天 3.1
  2. 甲骨文 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. 数据库操作后我无权访问连接,因此我可以清除该属性。

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

任何输入或想法将不胜感激。

4

2 回答 2

3

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

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

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

于 2012-12-10T17:31:33.057 回答
0

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

于 2012-07-05T05:22:18.500 回答