Popolare Envers tabelle di revisione con i dati esistenti provenienti da Enti Hibernate
-
23-08-2019 - |
Domanda
Sto aggiungendo envers a un'entità Hibernate esistenti. Tutto funziona senza problemi per quanto riguarda la revisione, tuttavia interrogazione è una questione diversa, perché le tabelle di revisione non sono occupati con i dati esistenti. Qualcun altro ha già risolto questo problema? Forse hai trovato un modo per popolare le tabelle di revisione con la tabella esistente? Ho pensato di chiedere, sono sicuro che gli altri lo troverebbero utile.
Soluzione
Non è necessario.
AuditQuery consente di ottenere sia RevisionEntity e la revisione dei dati da:
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity(YourAuditedEntity.class, false, false);
In questo modo creare una query che restituisce un elenco di Object [3]. Elemento fisrt è i dati, il secondo è l'entità di revisione e il terzo è il tipo di revisione.
Altri suggerimenti
Abbiamo popolato i dati iniziali eseguendo una serie di query SQL prime per simulare "inserendo" tutte le entità esistenti come se fossero stati appena creato allo stesso tempo. Ad esempio:
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
Si noti che il RevType valore 0 per indicare un inserto (in contrapposizione ad una modifica).
Avrete un problema in questa categoria se si utilizza Envers ValidityAuditStrategy e hanno dati che sono stati creati diversi da quelli con Envers abilitati.
Nel nostro caso (Hibernate 4.2.8.Final) un aggiornamento di oggetto base getta "Impossibile aggiornare revisione precedente per l'entità e" (registrata come [org.hibernate.AssertionFailure] HHH000099).
Mi ci volle un po 'per trovare questa discussione / spiegazione in modo cross-posting:
Abbiamo risolto il problema di popolare i registri di controllo con i dati esistenti nel seguente modo:
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 );
}
La configurazione delle mie fonti di dati (via Primavera):
<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>
L'intercettore:
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: tenere a mente che questo è un esempio semplificato. Non funzionerà fuori dalla scatola, ma vi guiderà verso una soluzione di lavoro.
Date un'occhiata a http://www.jboss.org /files/envers/docs/index.html#revisionlog
In pratica si può definire il proprio 'Tipo di revisione' utilizzando @RevisionEntity annotazione, e quindi implementare un'interfaccia RevisionListener inserire i dati di controllo aggiuntivi, come utente corrente e il funzionamento di alto livello. Di solito questi sono tirati dal contesto ThreadLocal.