简体   繁体   中英

ConcurrentModificationException in PostUpdateListener

Recently I've implemented a PostUpdate Hibernate Listener to make some Special Audit about the operation done:

assuming the root entity:

@Entity
@Table(name="plan")
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@DynamicUpdate
@DynamicInsert
public class Plan implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    private String protocol;


    @Lob
    private String description;

    private String title;

    @Type(type="service.dao.hibernate.enumeration.PlanStateUserType")
    @Column(name="plan_state_id")
    @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
    private PlanState planState;

    @OneToMany(mappedBy="plan",fetch=FetchType.LAZY, cascade={CascadeType.ALL},orphanRemoval=true)
    @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
    private Collection<PlanAudit> planAudits;   

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="send_date")
    private Date sendDate;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="approval_date")
    private Date approvalDate;

    @Column(name="extension_days")
    private int extensionDays;

    @Column(name="extension_reporting_days")
    private int extensionReportingDays;


    public Plan() {
        planPurposes = new ArrayList<PlanPurpose>();
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProtocol() {
        return this.protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public Collection<PlanAudit> getPlanAudits() {
        return planAudits;
    }

    public void setPlanAudits(Collection<PlanAudit> planAudits) {
        this.planAudits = planAudits;
    }   

    public void addAudit(User user, PlanState previous, PlanState next, Date operationDate){
        PlanAudit audit = new PlanAudit();

        audit.setPlan(this);
        audit.setOperationDate(operationDate);
        audit.setUser(user);
        audit.setPlanStateFrom(previous);
        audit.setPlanStateTo(next);
        this.planAudits.add(audit);     
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public PlanState getPlanState() {
        return planState;
    }

    public void setPlanState(PlanState planState) {
        this.planState = planState;
    }

    public Date getSendDate() {
        return sendDate;
    }

    public void setSendDate(Date sendDate) {
        this.sendDate = sendDate;
    }

    public Date getApprovalDate() {
        return approvalDate;
    }

    public void setApprovalDate(Date approvalDate) {
        this.approvalDate = approvalDate;
    }

    public int getExtensionDays() {
        return extensionDays;
    }

    public void setExtensionDays(int extensionDays) {
        this.extensionDays = extensionDays;
    }

    public int getExtensionReportingDays() {
        return extensionReportingDays;
    }

    public void setExtensionReportingDays(int extensionReportingDays) {
        this.extensionReportingDays = extensionReportingDays;
    }
}

then the audit entity :

@Entity
@Table(name="plan_audit")
@org.hibernate.annotations.Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@DynamicInsert
@DynamicUpdate
public class PlanAudit implements Serializable { //extends SimpleAuditedEntity 
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="operation_date")
    private Date operationDate;

    //bi-directional many-to-one association to Plan
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="plan_id")
    private Plan plan;

    @Type(type="service.dao.hibernate.enumeration.PlanStateUserType")
    @Column(name="plan_state_from_id")
    @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
    private PlanState planStateFrom;

    @Type(type="service.dao.hibernate.enumeration.PlanStateUserType")
    @Column(name="plan_state_to_id")
    @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
    private PlanState planStateTo;


    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="user_id")
    @org.hibernate.annotations.Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
    private User user;

    public PlanAudit() {
    }


    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Date getOperationDate() {
        return this.operationDate;
    }

    public void setOperationDate(Date operationDate) {
        this.operationDate = operationDate;
    }

    public Plan getPlan() {
        return this.plan;
    }

    public void setPlan(Plan plan) {
        this.plan = plan;
    }

    public PlanState getPlanStateFrom(){
        return this.planStateFrom;
    }

    public void setPlanStateFrom(PlanState planState){
        this.planStateFrom = planState;
    }

    public PlanState getPlanStateTo(){
        return this.planStateTypeTo;
    }

    public void setPlanStateTo(PlanState planState){
        this.planStateTo = planState;
    }

    public User getUser() {
        return this.user;
    }

    public void setUser(User user) {
        this.user = user;
    }

}

Then the listener is :

@Component
public class PlanListener implements PostUpdateEventListener {
    private static final long serialVersionUID = 1L;

    @Autowired
    private UserContext userContext;


    private int getPropertyIndex(String[] array, String name) {

        for (int i = 0; i < array.length; i++) {
            if(array[i] == name)
                return i;
        }
        return -1;
    }

    private void setValue(Object[] currentState, String[] propertyNames, String propertyToSet, Object value, Object entity) {
         int index = ArrayUtils.indexOf(propertyNames, propertyToSet);
         if (index >= 0) {
              currentState[index] = value;
              } else {
                   //Log.error("Field '" + propertyToSet + "' not found on entity '" + entity.getClass().getName() + "'.");
              }
    }


    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        Object entity = event.getEntity();
        if(entity instanceof Plan)
        {
            int stateProperty = getPropertyIndex(
                    event.getPersister().getPropertyNames(), 
                    "planStateType");

            if(stateProperty != -1) {
                PlanState prev = (PlanState)event.getOldState()[stateProperty];
                PlanState next = ((Plan)entity).getPlanState();

                if(prev.getEnumeratedValue() != 
                   next.getEnumeratedValue()) {
                    Plan target = (Plan) event.getEntity();
                    target.addAudit(
                            userContext.getCurrentUser(), 
                            prev), 
                            next), 
                            new Date());
                }
            }
        }       
    }



    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }
}

Now, in the Listener if I get the User object from the UserContext object and then proceed I get a ConcurrentModificationException , if I set a user id instead the User object everything goes. I saw after time & time debugging that just reading the User object from the Session makes the exception happen.

This is the exception stack trace:

- 2016-04-05 17:18:17 ERROR org.hibernate.internal.SessionImpl - HHH000346: Error during managed flush [null]
 java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:558)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:434)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
    at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:581)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
    at service.impl.PlanServiceImpl$$EnhancerBySpringCGLIB$$299741bf.updatePlan(<generated>)
    at controller.presentation.FinancialController.postConfirm(FinancialController.java:643)
    at controller.presentation.FinancialController$$FastClassBySpringCGLIB$$4face017.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)
    at controller.presentation.FinancialController$$EnhancerBySpringCGLIB$$ff2cd793.postConfirm(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:870)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:86)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:130)
    at org.springframework.orm.hibernate5.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:151)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:158)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:120)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:85)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70)
    at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:248)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:77)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:167)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:761)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Any idea? I'm going to be crazy, nothing anywhere, google, SO, forums.. Nothing!

Cheers

I'd use Enver instead of rolling my own audit logging mechanism. You should also add a StackTrace to see where the ConcurrentModificationException originates from because otherwise it is very difficult to spot such an issue.

It may be that it is caused by some bug too, in which case we need a replicating test case for this.

Also, the PostUpdateEventListener is triggered in the doAfterTransactionCompletion phase. Enver applies all changes with a temporary Session, so you'd have to do something like that too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM