Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve Username from customRevisionEntity

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>
like image 942
SST Avatar asked May 16 '15 09:05

SST


2 Answers

Using Envers

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);
    }
}

Update - Missing Spring Configuration

Looking your stacktrace, you're missing some spring transaction configuration. Either use a declarative configuration or use annotation.

Declarative Configuration

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.

Using @Transactional

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.

Envers Configuration On Hibernate 3

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"/>
like image 159
André Avatar answered Sep 25 '22 05:09

André


You are missing pointcuts definitions, as described here.

Pointcuts help you define where you want your advice to be applied.

like image 28
Dragan Bozanovic Avatar answered Sep 22 '22 05:09

Dragan Bozanovic