我正在向现有的hibernate实体添加envers。到目前为止,审计工作一切顺利,但查询是一个不同的问题,因为修订表没有填充现有数据。还有其他人已经解决了这个问题吗?也许您已经找到了一些方法来使用现有表填充修订表?我想我会问,我相信其他人会发现它很有用。
答案 0 :(得分:13)
我们通过运行一系列原始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 表示插入(与修改相对)。
答案 1 :(得分:6)
如果您使用的是Envers ValidityAuditStrategy并且已创建的数据不是启用了Envers,则此类别中存在问题。
在我们的案例中(Hibernate 4.2.8.Final),基本对象更新抛出“无法更新实体的先前版本和”(记录为[org.hibernate.AssertionFailure] HHH000099)。
我花了一些时间才发现这个讨论/解释,所以交叉发布:
答案 2 :(得分:5)
你不需要。
AuditQuery允许您通过以下方式获取RevisionEntity和数据修订:
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity(YourAuditedEntity.class, false, false);
这将构造一个返回Object [3]列表的查询。 Fisrt元素是您的数据,第二个是修订实体,第三个是修订类型。
答案 3 :(得分:2)
我们已经解决了使用现有数据填充审核日志的问题,如下所示:
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:请记住,这是一个简化的例子。它不会开箱即用,但它会引导您找到可行的解决方案。
答案 4 :(得分:1)
查看http://www.jboss.org/files/envers/docs/index.html#revisionlog
基本上你可以使用@RevisionEntity注释定义自己的'修订类型', 然后实现RevisionListener接口以插入您的其他审计数据, 喜欢当前用户和高级操作。通常这些是从ThreadLocal上下文中提取的。
答案 5 :(得分:0)
您可以使用 find 方法的后备选项扩展 AuditReaderImpl
,例如:
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);
}
}