Pytanie Jak odzyskać nazwę użytkownika z customRevisionEntity


Używamy Hibernate-envers 3.6.3.Final i generuję tabele generowane poprawnie z @ Adnotacjami. Używam CustomRevisionEntity do przechowywania informacji o użytkowniku, a CustomRevisionListenner jest przechowywany również informacje o użytkowniku. Ale jeśli próbuję pobrać "username", zwraca następujący błąd.

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]

Moja klasa CustomRevisionEntity,

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

Mój stół w następujący sposób,

mysql> select * from revision_info;
+----+---------------+-----------------+
| id | timestamp     | username        |
+----+---------------+-----------------+
|  1 | 1431693146030 | sky@test.com    |
|  2 | 1431693150805 | ram@test.com    |
|  3 | 1431693164895 | bobo@test.com   |
+----+---------------+-----------------+
3 rows in set (0.02 sec)

Jestem w stanie odzyskać "rev" używając "timeStamp" i "timeStamp" używając "rev" używając następującego kodu,

AuditReader reader = AuditReaderFactory.get(session);
Date timestamp = reader.getRevisionDate(rev);
Number revision = reader.getRevisionNumberForDate(timestamp);

Nie mogę jednak pobrać całego wiersza z niestandardowymi wartościami "username" pola przy użyciu kwerendy hibernacji.

Criteria criteria = sessionFactory.getCurrentSession()
                .createCriteria(CustomRevisionEntity.class)
                .add(Restrictions.eq("id", rev));

Powyższe zapytanie zwraca powyższy błąd .. Jak rozwiązać ten problem? Jak pobrać wartości z tabeli revision_info?

Śledzenie całego stosu mojego błędu jest następujące:

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]

Moja konfiguracja sprężyny wygląda następująco,

    <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>

moja application-content.xml zawiera następujące ...

<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>

11
2018-05-16 09:20


pochodzenie


Wszelkie wejścia tutaj naprawdę docenione - SST


Odpowiedzi:


Używanie Envers

Możesz zapytać o CustomRevisionEntity używając 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();

Oto podpis metody

 /**
 * 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;

Ze źródła Hibernate-envers 3.6.3.Final jest to zaimplementowane w linii AuditReaderImpl.java 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);
    }
}

Aktualizacja - brak konfiguracji sprężyny

Patrząc na swój stos, tracisz konfigurację transakcji wiosennych. Użyj deklaratywnej konfiguracji lub użyj adnotacji.

Deklaratywna konfiguracja

Musisz zadeklarować użycie transakcji w pliku konfiguracyjnym xml, odbywa się to za pomocą punktu AOP. Patrząc na to przykład, możesz go zobaczyć najpierw konfiguruje TransactionManager i jego DataSource, następnie zadeklaruj, że każda metoda x.y.service.FooService będzie wymagać transakcji

<!-- 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>

Twoja dostarczona konfiguracja jest brakujący konfiguracja AOP. Dla wygody możesz skonfigurować każdą klasę z określonego pakietu, aby użyć transakcji.

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

Zobacz, to jest po prostu expression="execution(* x.y.service.*.*(..))" to się zmienia.

Korzystanie z @Transactional

Wiosna na szczęście zapewnia łatwiejszy sposób deklarowania użycia a @Transactional metoda, po prostu poprzez przypisywanie klasy, interfejsu lub metody jako @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);
}

Następnie musimy skonfigurować sprężynę, aby zeskanować nasz kod szukający @Transactional, aby wygenerować odpowiednie instancje proxy komponentu proxy w razie potrzeby.

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->

Tutaj jest pełny przykład dla @Transactional i moje odniesienie dla wiosennej konfiguracji transakcji.

Envers Configuration On Hibernate 3

Hibernate 3 wymaga specjalnej konfiguracji do pracy z Envers, musisz dodać to do swojej persistence.xml. Przykład 

<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" />

Jeśli nie masz pliku persistence.xml lub hibernate.cfg.xml i deklarujesz SessionFactory i to po prostu działa, musisz edytować swoją konfigurację wiosenną coś w stylu to 

<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"/>

5
2018-05-18 12:34



Dzięki za twoją odpowiedź Andre! Nadal dostaję ten sam błąd ... "org.hibernate.HibernateException: Brak sesji hibernacji związanej z wątkiem, a konfiguracja nie pozwala na utworzenie tutaj nietransakcyjnego" - SST
Cóż, to jest bardziej prawdopodobne, problem z konfiguracją sprężyny. Czy możesz opublikować pełny stacktrace? - André
Dodałem pełny stacktrace na końcu mojego pytania ... proszę spojrzeć na to i podać swoje przemyślenia ... - SST
Dodano pewne wyjaśnienia dotyczące konfiguracji Spring. @ST - André
Dzięki za szybką odpowiedź @Andre !!! teraz otrzymuję następujący błąd .. org.hibernate.envers.exception.AuditException: Należy zainstalować klasę org.hibernate.envers.event.AuditEventListener jako wstawianie wstawiania, aktualizowanie i usuwanie detektora zdarzeń. - SST


Brakuje definicji punktów, zgodnie z opisem tutaj.

Wskaźniki pomogą ci określić, gdzie chcesz zastosować swoją radę.


2
2018-05-19 10:53



Nie Dragan ... Zrobiłem również config .. nadal otrzymuję ten sam błąd .. :( życzę zobaczyć moją ostatnią zmianę w moim pytaniu. - SST
Ok, oczywiście jest więcej niż to, co powoduje problem, ale i tak brakowało. - Dragan Bozanovic


Ten błąd jest najczęściej spowodowany obecnością a TransactionManageri próbuje wykonać metodę zapytania, która nie jest oznaczona jako @Transactional, lub może zbyt szeroka <tx:advice ... definicja fasoli. Czy możesz pokazać udoskonalone części swojej konfiguracji wiosennej?

TL; DR: Spróbuj dodać @Transactional do sprawdzania metody CustomRevisionEntity i zobacz, czy to pomaga. Jeśli tak, podejdź do pracy z rozgraniczeniem transakcji.

Aktualizacja: Twoja konfiguracja wiosna wymaga transakcji na wszystkich metodach. Możesz dodać

<tx:method name="read*" read-only="true"/>

aby dopasować swoje metody odczytu audytu (zmiana read* do czegoś sensownego i upewnij się, że "*" nie łapie wszystkiego). Możesz nawet dodać atrybut propagation="SUPPORTS" do tego tagu, aby umożliwić korzystanie z istniejącej transakcji, jeśli pytasz w takim kontekście.

Ponadto, na pewno masz TransactionInterceptor/ auto-proxy gdzieś?

<bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"></property>
    <property name="transactionAttributeSource" ref="txAttributeSource"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>          
            <value>yourServiceName<value>
            ...
        </list>
    </property>
    <property name="interceptorNames">
        <list>
            <idref local="txInterceptor"/>
        </list>
    </property>
</bean>

Twoje zdrowie,


2
2018-05-19 08:27



Cześć Anders, dodałem już @Transactional w mojej klasie zapytania w metodzie, a także skonfigurowałem <tx: advice ... w moim application-context.xml, ale to nie działało ... - SST
Czy możesz pokazać odpowiednią konfigurację sprężyny? - Anders R. Bystrup
Na końcu mojego pytania opublikowałem moją konfigurację wiosna. Uprzejmie popatrz na to i podaj swoje sugestie - SST