既存の休止状態のエンティティにエンバーを追加しています。監査に関してはすべてがスムーズに機能していますが、リビジョンテーブルに既存のデータが入力されていないため、クエリは別の問題です。他の誰かがすでにこの問題を解決しましたか?リビジョンテーブルに既存のテーブルを追加する方法を見つけたのではないでしょうか。私が尋ねると思っただけで、他の人がそれが役に立つと思うと確信しています。
6 に答える
一連の生のSQLクエリを実行して初期データを入力し、既存のすべてのエンティティを同時に作成されたかのように「挿入」することをシミュレートしました。例えば:
insert into REVINFO(REV,REVTSTMP) values (1,1322687394907);
-- this is the initial revision, with an arbitrary timestamp
insert into item_AUD(REV,REVTYPE,id,col1,col1) select 1,0,id,col1,col2 from item;
-- this copies the relevant row data from the entity table to the audit table
REVTYPE値は(変更ではなく)挿入を示すために0であることに注意してください。
Envers ValidityAuditStrategyを使用していて、Enversが有効になっていない場合以外に作成されたデータがある場合、このカテゴリで問題が発生します。
私たちの場合(Hibernate 4.2.8.Final)、基本的なオブジェクトの更新は「エンティティの以前のリビジョンを更新できません」([org.hibernate.AssertionFailure] HHH000099としてログに記録されます)をスローします。
このディスカッション/説明を見つけるのに少し時間がかかりましたので、クロスポストします:
あなたはする必要はありません。
AuditQueryを使用すると、RevisionEntityとデータリビジョンの両方を次の方法で取得できます。
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity(YourAuditedEntity.class, false, false);
これにより、オブジェクト[3]のリストを返すクエリが作成されます。最初の要素はデータ、2番目はリビジョンエンティティ、3番目はリビジョンのタイプです。
次のように、監査ログに既存のデータを入力する問題を解決しました。
SessionFactory defaultSessionFactory;
// special configured sessionfactory with envers audit listener + an interceptor
// which flags all properties as dirty, even if they are not.
SessionFactory replicationSessionFactory;
// Entities must be retrieved with a different session factory, otherwise the
// auditing tables are not updated. ( this might be because I did something
// wrong, I don't know, but I know it works if you do it as described above. Feel
// free to improve )
FooDao fooDao = new FooDao();
fooDao.setSessionFactory( defaultSessionFactory );
List<Foo> all = fooDao.findAll();
// cleanup and close connection for fooDao here.
..
// Obtain a session from the replicationSessionFactory here eg.
Session session = replicationSessionFactory.getCurrentSession();
// replicate all data, overwrite data if en entry for that id already exists
// the trick is to let both session factories point to the SAME database.
// By updating the data in the existing db, the audit listener gets triggered,
// and inserts your "initial" data in the audit tables.
for( Foo foo: all ) {
session.replicate( foo, ReplicationMode.OVERWRITE );
}
私のデータソースの構成(Spring経由):
<bean id="replicationDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value=".."/>
<property name="username" value=".."/>
<property name="password" value=".."/>
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
<bean id="auditEventListener"
class="org.hibernate.envers.event.AuditEventListener"/>
<bean id="replicationSessionFactory"
class="o.s.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="entityInterceptor">
<bean class="com.foo.DirtyCheckByPassInterceptor"/>
</property>
<property name="dataSource" ref="replicationDataSource"/>
<property name="packagesToScan">
<list>
<value>com.foo.**</value>
</list>
</property>
<property name="hibernateProperties">
<props>
..
<prop key="org.hibernate.envers.audit_table_prefix">AUDIT_</prop>
<prop key="org.hibernate.envers.audit_table_suffix"></prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="post-insert" value-ref="auditEventListener"/>
<entry key="post-update" value-ref="auditEventListener"/>
<entry key="post-delete" value-ref="auditEventListener"/>
<entry key="pre-collection-update" value-ref="auditEventListener"/>
<entry key="pre-collection-remove" value-ref="auditEventListener"/>
<entry key="post-collection-recreate" value-ref="auditEventListener"/>
</map>
</property>
</bean>
インターセプター:
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
..
public class DirtyCheckByPassInterceptor extends EmptyInterceptor {
public DirtyCheckByPassInterceptor() {
super();
}
/**
* Flags ALL properties as dirty, even if nothing has changed.
*/
@Override
public int[] findDirty( Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types ) {
int[] result = new int[ propertyNames.length ];
for ( int i = 0; i < propertyNames.length; i++ ) {
result[ i ] = i;
}
return result;
}
}
ps:これは単純化された例であることに注意してください。そのままでは機能しませんが、実用的なソリューションに向けてガイドします。
http://www.jboss.org/files/envers/docs/index.html#revisionlogをご覧ください
基本的に、@ RevisionEntityアノテーションを使用して独自の「リビジョンタイプ」を定義し、RevisionListenerインターフェイスを実装して、現在のユーザーや高レベルの操作などの追加の監査データを挿入できます。通常、それらはThreadLocalコンテキストから取得されます。
AuditReaderImpl
次のように、findメソッドのフォールバックオプションを使用してを拡張できます。
public class AuditReaderWithFallback extends AuditReaderImpl {
public AuditReaderWithFallback(
EnversService enversService,
Session session,
SessionImplementor sessionImplementor) {
super(enversService, session, sessionImplementor);
}
@Override
@SuppressWarnings({"unchecked"})
public <T> T find(
Class<T> cls,
String entityName,
Object primaryKey,
Number revision,
boolean includeDeletions) throws IllegalArgumentException, NotAuditedException, IllegalStateException {
T result = super.find(cls, entityName, primaryKey, revision, includeDeletions);
if (result == null)
result = (T) super.getSession().get(entityName, (Serializable) primaryKey);
return result;
}
}
場合によっては、返品に関してさらにいくつかのチェックを追加できますnull
。独自のファクトリも使用することをお勧めします。
public class AuditReaderFactoryWithFallback {
/**
* Create an audit reader associated with an open session.
*
* @param session An open session.
* @return An audit reader associated with the given sesison. It shouldn't be used
* after the session is closed.
* @throws AuditException When the given required listeners aren't installed.
*/
public static AuditReader get(Session session) throws AuditException {
SessionImplementor sessionImpl;
if (!(session instanceof SessionImplementor)) {
sessionImpl = (SessionImplementor) session.getSessionFactory().getCurrentSession();
} else {
sessionImpl = (SessionImplementor) session;
}
final ServiceRegistry serviceRegistry = sessionImpl.getFactory().getServiceRegistry();
final EnversService enversService = serviceRegistry.getService(EnversService.class);
return new AuditReaderWithFallback(enversService, session, sessionImpl);
}
}