問題のパラメータ:
- 春3.1
- Oracle 11.2.0.3
- Glassfish 2.1アプリケーションサーバー、JDBC接続プールを提供します。
問題の説明:
顧客ユーザーを追加、編集、削除するための既存の管理アプリケーションのセットでユーザー監査を改良しています。多数のテーブルに関連付けられたOracleトリガーによって作成された監査レコードに管理ユーザーのIDを保存する必要があります。データベース操作の前に接続プールから取得した各接続にOracleCLIENT_IDENTIFIER属性を設定し、データベース操作の後で属性をクリアすることにより、管理ユーザーIDをトリガーにアクセスできるようにします。私はうまくいくものを持っていますが、それが行われる方法は本当に好きではありません。
質問:
データベース操作の前後にOracleコンテキスト属性を設定できるように接続にアクセスする方法はありますか?たぶん、イベントに応答するある種のリスナー?
私は見ました:
- 百万のウェブページ(多分それは誇張ですが、私は3、4時間グーグルで検索しました)。
- DataSourceUtilsを使用して接続を取得します。これは機能しますが、実際には接続を管理したくありません。プールに出入りする途中で接続をインターセプトして、CLIENT_IDENTIFIER属性値を設定したいだけです。
- データソースのgetConnectionメソッドをオーバーライドします。これはJdbcTemplate内のどこかで呼び出されるため、アプリケーションデータをメソッドに取得できません。
SpringやOracleGurusが、私の実装をハックすることなく、「それは明らかであり、答えは...」と言うことを望んでいますが、とにかくここにあります。他に何もないとしても、誰かがアイデアを探している場合に備えて、これは機能します。
私の実装:
すべてのデータベース操作は、Daoに注入されたJdbcTemplateオブジェクトへのJdbcOperations参照を使用して実行されます。追加、編集、および削除操作は、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>
これはユーザーDaoインターフェースです
public interface UserDao {
void addUser(User newUser,String adminUserId);
}
これはユーザーのDaoクラスです
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 ORINSERTORUPDATE」トリガーがあります。各テーブルには、対応する監査テーブルがあります。コンテキストから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;
私が言っているように、これはうまくいきますが、次の理由で私はそれが好きではありません。
- プリペアドステートメントを設定するためのメソッドで接続を変更する必要があります。
- このコードを、監査するすべてのPreparedStatementCreatorまたはBatchPreparedStatementSetterオブジェクトに追加する必要があります。
- データベース操作後に接続にアクセスできないため、属性をクリアできます。
私が本当に望んでいるのは、接続の前後に属性を設定できる単一のポイントです。
任意の入力やアイデアをいただければ幸いです。