We are using Hibernate-envers 3.6.3.Final and I am getting tables are generated properly with @Audited annotations. I am using CustomRevisionEntity to store the user information and CustomRevisionListenner is stored the user information as well. But If I trying to retrieve the "username", It returns the following error.
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:64) ~[spring-orm-3.2.6.RELEASE.jar:3.2.6.RELEASE]
My CustomRevisionEntity class,
@Entity
@Table(name = "revision_info")
@RevisionEntity(CustomEnversListener.class)
public class CustomRevisionEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@RevisionNumber
private int id;
@RevisionTimestamp
private long timestamp;
private String username;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Column(name = "username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
CustomRevisionListener.java
public class CustomEnversListener implements RevisionListener {
public void newRevision(Object revisionEntity) {
CustomRevisionEntity customRevisionEntity = (CustomRevisionEntity) revisionEntity;
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
customRevisionEntity.setUsername(authentication.getName());
}
}
My table as follows,
mysql> select * from revision_info;
+----+---------------+-----------------+
| id | timestamp | username |
+----+---------------+-----------------+
| 1 | 1431693146030 | [email protected] |
| 2 | 1431693150805 | [email protected] |
| 3 | 1431693164895 | [email protected] |
+----+---------------+-----------------+
3 rows in set (0.02 sec)
I am able to retrieve the "rev" using the "timeStamp" and "timeStamp" using the "rev" using the following code,
AuditReader reader = AuditReaderFactory.get(session);
Date timestamp = reader.getRevisionDate(rev);
Number revision = reader.getRevisionNumberForDate(timestamp);
But I am unable to retrieve the entire row with custom field "username" values using the hibernate query.
Criteria criteria = sessionFactory.getCurrentSession()
.createCriteria(CustomRevisionEntity.class)
.add(Restrictions.eq("id", rev));
The above query returns the above error.. How do I solve this?? How to retrieve the values from the revision_info table?
The full stack trace of my error is,
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:64) ~[spring-orm-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:687) ~[hibernate-core-3.6.3.Final.jar:3.6.3.Final]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:701) ~[spring-aop-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) ~[spring-aop-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91) ~[spring-aop-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:634) ~[spring-aop-3.2.6.RELEASE.jar:3.2.6.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_31]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.6.0_31]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.6.0_31]
at java.lang.reflect.Method.invoke(Method.java:622) ~[na:1.6.0_31]
at ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:870) ~[ognl-3.0.6.jar:na]
at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1293) ~[ognl-3.0.6.jar:na]
at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68) ~[ognl-3.0.6.jar:na]
at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:117) [xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:108) [xwork-core-2.3.20.jar:2.3.20]
at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1369) ~[ognl-3.0.6.jar:na]
at ognl.ASTMethod.getValueBody(ASTMethod.java:90) ~[ognl-3.0.6.jar:na]
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212) ~[ognl-3.0.6.jar:na]
at ognl.SimpleNode.getValue(SimpleNode.java:258) ~[ognl-3.0.6.jar:na]
at ognl.Ognl.getValue(Ognl.java:494) ~[ognl-3.0.6.jar:na]
at ognl.Ognl.getValue(Ognl.java:458) ~[ognl-3.0.6.jar:na]
at com.opensymphony.xwork2.ognl.OgnlUtil$2.execute(OgnlUtil.java:309) ~[xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecute(OgnlUtil.java:340) ~[xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.ognl.OgnlUtil.getValue(OgnlUtil.java:307) ~[xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:423) ~[xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:287) ~[xwork-core-2.3.20.jar:2.3.20]
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:250) ~[xwork-core-2.3.20.jar:2.3.20]
at org.apache.struts2.interceptor.DeprecationInterceptor.intercept(DeprecationInterceptor.java:41) ~[struts2-core-2.3.20.jar:2.3.20]
My spring configuration is as follows,
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="xxxSessionFactory"/>
</bean>
<tx:advice id="customRevisionEntityAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="crePointcut"
expression="execution(* bla.bla.CustomRevisionEntity.*(..))"/>
<aop:advisor advice-ref="customRevisionEntityAdvice" pointcut-ref="crePointcut"/>
</aop:config>
my application-content.xml contains the following...
<bean id="auditEventListener" class="org.hibernate.envers.event.AuditEventListener" />
<bean id="xxxSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>bla.bla.domain.Myclass</value>
</list>
</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>
You can query the CustomRevisionEntity
using the AuditReader
AuditReader auditReader = AuditReaderFactory.get(entityManager);
//Here you find the revision number that you want
Number revisionNumber = getRevisionNumber(auditReader);
//then you use the auditReader :-)
CustomRevisionEntity cRevEntity = auditReader.findRevision(
CustomRevisionEntity.class, revisionNumber );
//Then you can just get your Username
String userName = cRevEntity.getUsername();
Here is the method signature
/**
* A helper method; should be used only if a custom revision entity is used. See also {@link RevisionEntity}.
* @param revisionEntityClass Class of the revision entity. Should be annotated with {@link RevisionEntity}.
* @param revision Number of the revision for which to get the data.
* @return Entity containing data for the given revision.
* @throws IllegalArgumentException If revision is less or equal to 0 or if the class of the revision entity
* is invalid.
* @throws RevisionDoesNotExistException If the revision does not exist.
* @throws IllegalStateException If the associated entity manager is closed.
*/
<T> T findRevision(Class<T> revisionEntityClass, Number revision) throws IllegalArgumentException,
RevisionDoesNotExistException, IllegalStateException;
From the Hibernate-envers 3.6.3.Final source, this is implemented at AuditReaderImpl.java line 193:
@SuppressWarnings({"unchecked"})
public <T> T findRevision(Class<T> revisionEntityClass, Number revision) throws IllegalArgumentException,
RevisionDoesNotExistException, IllegalStateException {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
checkSession();
Set<Number> revisions = new HashSet<Number>(1);
revisions.add(revision);
Query query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
try {
T revisionData = (T) query.uniqueResult();
if (revisionData == null) {
throw new RevisionDoesNotExistException(revision);
}
return revisionData;
} catch (NonUniqueResultException e) {
throw new AuditException(e);
}
}
Looking your stacktrace, you're missing some spring transaction configuration. Either use a declarative configuration or use annotation.
You need to declare the usage of transaction within the configuration xml, this is done using a AOP pointcut. Looking at this example, you can see it first sets up the TransactionManager
and it's DataSource
, then declare that every method of x.y.service.FooService
will require a transaction
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
Your provided configuration is missing the AOP configuration. For convenience you can configure every class from a specific package to use transaction.
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
See that is just the expression="execution(* x.y.service.*.*(..))"
that changes.
Spring thankfully provides a easier way to declare the usage of a @Transactional
method, simply by annotating a Class, Interface or Method as @Transactional
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
After that we need to configure spring to scan our code looking for @Transactional, to generate the proper proxy beans instances when needed.
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->
Here is the complete example for @Transactional and my reference for spring configuration about transactions.
Hibernate 3 requires a special configuration to work with Envers, you need to add this at your persistence.xml
. Example
<property name="hibernate.ejb.event.post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-delete" value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" />
In case you don't have a persistence.xml or hibernate.cfg.xml, and you declare the SessionFactory
and it just works, you need to edit your spring configuration something like this
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
....
<property name="eventListeners">
<map>
<entry key="post-insert" value-ref="auditListener"/>
<entry key="post-update" value-ref="auditListener"/>
<entry key="post-delete" value-ref="auditListener"/>
<entry key="pre-collection-update" value-ref="auditListener"/>
<entry key="pre-collection-remove" value-ref="auditListener"/>
<entry key="post-collection-recreate" value-ref="auditListener"/>
</map>
</property>
...
</bean>
<bean id="auditListener" class="org.hibernate.envers.event.AuditEventListener"/>
You are missing pointcuts definitions, as described here.
Pointcuts help you define where you want your advice to be applied.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With