Я добавляю переменные к существующим объектам спящего режима. Все работает нормально до тех пор, пока не выполняется аудит, однако запрос является другой проблемой, поскольку таблицы ревизий arent заполнены существующими данными. Кто-нибудь еще решил эту проблему? Может быть, вы нашли способ заполнить таблицы ревизий существующей таблицей? Просто подумал, что я спрошу, я уверен, что другие сочтут это полезным.
Заполнение таблиц ревизий изменений существующими данными из объектов Hibernate
Ответ 1
Вам не нужно.
AuditQuery позволяет получить RevisionEntity и версию данных:
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity(YourAuditedEntity.class, false, false);
Это построит запрос, который возвращает список Object [3]. Элемент Fisrt - это ваши данные, второй - объект ревизии, а третий - тип ревизии.
Ответ 2
Мы заполнили начальные данные, выполнив ряд необработанных 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 указывает на вставку (в отличие от модификации).
Ответ 3
У вас возникнет проблема в этой категории, если вы используете Envers ValidityAuditStrategy и имеете данные, которые были созданы иначе, чем с включенным Envers.
В нашем случае (Hibernate 4.2.8.Final) базовое обновление объекта выбрасывает "Не удается обновить предыдущую ревизию для объекта и" (зарегистрировано как [org.hibernate.AssertionFailure] HHH000099).
Пришло время найти эту дискуссию/объяснение, поэтому перекрестная публикация:
Ответ 4
Мы решили проблему заполнения журналов аудита существующими данными следующим образом:
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: имейте в виду, что это упрощенный пример. Это не сработает из коробки, но это поможет вам найти рабочее решение.
Ответ 5
Взгляните на http://www.jboss.org/files/envers/docs/index.html#revisionlog
В принципе, вы можете определить свой собственный тип ревизии с помощью аннотации @RevisionEntity, а затем реализовать интерфейс RevisionListener для вставки дополнительных данных аудита, как текущий пользователь и высокий уровень работы. Обычно те извлекаются из контекста ThreadLocal.