Results 1 to 10 of 11

Thread: Spring Data JPA - Infinite loop when updating, but not saving an auditable object

Hybrid View

  1. #1
    Join Date
    Dec 2008
    Location
    New York City
    Posts
    134

    Default Spring Data JPA - Infinite loop when updating, but not saving an auditable object

    I'm using hades 2.0.3 and hibernate 3.6.6. I've got a uni directional relationship from an entity that extends AbstractAuditable to my user entity.

    The behavior I'm seeing is that on @PrePersist everything works fine. But on @PreUpdate, the AuditingEntityListener gets stuck in an infinite loop until I get a StackOverFlow. Specifically, the act of retrieving the user from the userDAO triggers the @PreUpdate. The userDAO is just an interface extending GenericDAO

    Below is my auditaware implementation:

    Code:
    public class AuditorAwareBean implements AuditorAware<User> {
    
        //~ Instance fields ----------------------------------------------------------------------------
    
        @Resource private UserDAO UserDAO;
    
        //~ Methods ------------------------------------------------------------------------------------
    
        /** @see  org.synyx.hades.domain.auditing.AuditorAware#getCurrentAuditor() */
        @Override public User getCurrentAuditor() {
    
            String id = SecurityContextHolder.getContext().getAuthentication()
                    .getName()
                    ;
    
    // This sets off an infinte loop during update only
            return userDAO.findByUserId(id);
    
        }
    }

  2. #2
    Join Date
    May 2011
    Posts
    4

    Default I have the same problem but..

    Hi,

    I got the sampe problem yesterday in my project when I use a declared query:

    Code:
            public SystemUser getCurrentAuditor() {
    		SecurityContext secureContext = SecurityContextHolder.getContext();
    		Authentication authentication = secureContext.getAuthentication();
    		Object principal = authentication.getPrincipal();  
      
    		UserDetails userDetails = (UserDetails) principal;  
    		
    		SystemUser systemUser = systemUserDAO.findByUsername(userDetails.getUsername()); 
    		
    		return systemUser;
    	}

    But when I changed it to findOne from the interface JPARepository I no longer got the StackOverflow

    Code:
      public SystemUser getCurrentAuditor() {
    		SecurityContext secureContext = SecurityContextHolder.getContext();
    		Authentication authentication = secureContext.getAuthentication();
    		Object principal = authentication.getPrincipal();  
      
    		UserDetails userDetails = (UserDetails) principal;  
    		
    		SystemUser systemUser = systemUserDAO.findOne(1); 
    		
    		return systemUser;
    	}
    Hope this helps in some way, I've tried both 1.1.0.M1 and 1.0.1.RELEASE

    //Evo 1

  3. #3
    Join Date
    Dec 2008
    Location
    New York City
    Posts
    134

    Default

    Thanks for the tip. Unfortunately I'm still on hades so don't have access to that method. I'll dig through the spring-data-jpa source to see if anything jumps out at me as different between those two methods.

  4. #4
    Join Date
    Apr 2006
    Location
    Dresden, Germany
    Posts
    483

    Default

    The Hades' equivalent for findOne(…) is readByPrimaryKey(…) in case you might want to try that. I suspect a query being triggered to recursively trigger flushing and thus the invocation of the postUpdate callback. We have customers using finders in their AuditorAware implementation, so what OR mapper are you using? Might be an implementation detail of the one chosen.

  5. #5
    Join Date
    Dec 2008
    Location
    New York City
    Posts
    134

    Default

    Hello Oliver,

    I'm using hibernate 3.6.6 and get the same behavior Evo1 described. When I change the method to readByPrimaryKey - everything works. I don't like that solution because I would thus have to store that value as the Principal rather then a userId.

  6. #6
    Join Date
    May 2011
    Posts
    4

    Post Here is some more info

    Thx for the reply,

    I'm using Hibernate. I slimmed it down to a smaller project that get the same error and post the relevant details for it.

    This is my dao-config:
    Code:
      
        <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
            <property name="driverClassName" value="${database.driverClassName}"/>
            <property name="url" value="${database.url}"/>
            <property name="username" value="${database.username}"/>
            <property name="password" value="${database.password}"/>
        </bean>
    
        <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
       
        <tx:annotation-driven transaction-manager="transactionManager" />
    
        <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
            <property name="dataSource" ref="dataSource"/>
            <property name="jpaVendorAdapter">
    			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    				<property name="showSql" value="true" />
    				<property name="generateDdl" value="true" />
    			</bean>
    		</property>
    		<property name="jpaProperties">
    			<props>
    				<prop key="hibernate.show_sql">true</prop>
    				<prop key="hibernate.format_sql">true</prop>
    				<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
    			</props>
    		</property>
        </bean>
    This is my repository-config
    Code:
            <repositories base-package="se.testproject" />
      
    	<beans:bean id="auditorAware" class="se.testproject.AuditorAwareImpl" >
    		<beans:constructor-arg ref="userAccountDAO">
    		</beans:constructor-arg>
    	</beans:bean>
    	
    	<auditing auditor-aware-ref="auditorAware"  />
    persistence.xml and orm.xml
    Code:
    <persistence ..>
    
        <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
        </persistence-unit>
    </persistence>
    
    
    <entity-mappings ..>
      <persistence-unit-metadata>
         <persistence-unit-defaults>
             <entity-listeners>
                  <entity-listener class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
             </entity-listeners>
          </persistence-unit-defaults>
       </persistence-unit-metadata>
    </entity-mappings>
    The entities
    Code:
    package se.testproject.useraccount;
    
    @Entity
    public class UserAccount {
    	private int userAccountId;
            private String name;
       
    	@Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            public Integer getUserAccountId() {
    		return userAccountId;
    	}
    	public void setUserAccountId(int userAccountId) {
    		this.userAccountId = userAccountId;
    	}
    
    	@NotNull
            @Size(min = 1)
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    package se.testproject.country;
    
    @Entity
    public class Country implements Auditable<UserAccount, Integer>{
    	private Integer countryId;
            private String name;
    
    	private UserAccount createdBy;
    	private DateTime createdDate;
    	private UserAccount lastModifiedBy;
    	private DateTime lastModifiedDate;
    	
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            public Integer getCountryId() {
        	     return countryId;
            }
    
            public void setCountryId(Integer countryId) {
        	    	this.countryId = countryId;
        	}
    
        	@NotNull
        	@Size(min = 1)
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	@OneToOne
    	@JoinColumn(updatable=false)
    	public UserAccount getCreatedBy() {
    		return createdBy;
    	}
    
    	@Override
    	public void setCreatedBy(UserAccount createdBy) {
    		this.createdBy = createdBy;
    
    	}
    
    	@Override
    	@Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
    	@Column(updatable=false)
    	public DateTime getCreatedDate() {
    		return null == createdDate ? null : new DateTime(createdDate);
    	}
    
    	@Override
    	public void setCreatedDate(DateTime createdDate) {
    		this.createdDate = null == createdDate ? null : new DateTime(createdDate);
    	}
    
    	@Override
    	@OneToOne
    	public UserAccount getLastModifiedBy() {
    		return lastModifiedBy;
    	}
    
    	@Override
    	public void setLastModifiedBy(UserAccount lastModifiedBy) {
    		this.lastModifiedBy = lastModifiedBy;
    	}
    
    	@Override
    	@Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
    	public DateTime getLastModifiedDate() {
    		return null == lastModifiedDate ? null : new DateTime(lastModifiedDate);
    	}
    
    	@Override
    	public void setLastModifiedDate(DateTime lastModifiedDate) {
    		this.lastModifiedDate = null == lastModifiedDate ? null : new DateTime(lastModifiedDate);
    	}
    	
    	@Override
    	@Transient
    	public boolean isNew() {
    		return countryId == null ? true : false;
    	}
    	
    	@Override
    	@Transient
    	public Integer getId() {
    		return getCountryId();
    	}
    }
    The dao
    Code:
    package se.testproject.useraccount;
    
    public interface UserAccountDAO extends JpaRepository<UserAccount, Integer> {
    	public UserAccount findByName(String name);
    }
    
    package se.testproject.country;
    
    public interface CountryDAO extends JpaRepository<Country, Integer> {
    }
    The auditor aware implementation
    Code:
    package se.testproject;
    
    public class AuditorAwareImpl implements AuditorAware<UserAccount> {
    	private UserAccountDAO userAccountDAO;
    	
    	public AuditorAwareImpl(UserAccountDAO userAccountDAO) {
    		this.userAccountDAO = userAccountDAO;
    	}
    	public UserAccount getCurrentAuditor() {
    		return userAccountDAO.findByName("test");
    	}
    
    }
    Stacktrace

    Code:
    	org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
    	org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
    	org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
    	org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185)
    	org.hibernate.impl.SessionImpl.list(SessionImpl.java:1261)
    	org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
    	org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:274)
    	org.hibernate.ejb.criteria.CriteriaQueryCompiler$3.getSingleResult(CriteriaQueryCompiler.java:264)
    	org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:116)
    	org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:54)
    	org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:94)
    	org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:84)
    	org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:301)
    	org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    	org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    	org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    	$Proxy33.findByName(Unknown Source)
    	se.senai.AuditorAwareImpl.getCurrentAuditor(AuditorAwareImpl.java:18)
    	se.senai.AuditorAwareImpl.getCurrentAuditor(AuditorAwareImpl.java:1)
    	sun.reflect.GeneratedMethodAccessor57.invoke(Unknown Source)
    	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	java.lang.reflect.Method.invoke(Method.java:601)
    	org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    	org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:196)
    	$Proxy28.getCurrentAuditor(Unknown Source)
    	org.springframework.data.jpa.domain.support.AuditingEntityListener.touchAuditor(AuditingEntityListener.java:150)
    	org.springframework.data.jpa.domain.support.AuditingEntityListener.touch(AuditingEntityListener.java:129)
    	org.springframework.data.jpa.domain.support.AuditingEntityListener.touchForUpdate(AuditingEntityListener.java:117)
    	sun.reflect.GeneratedMethodAccessor59.invoke(Unknown Source)
    	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	java.lang.reflect.Method.invoke(Method.java:601)
    	org.hibernate.ejb.event.ListenerCallback.invoke(ListenerCallback.java:45)
    	org.hibernate.ejb.event.EntityCallbackHandler.callback(EntityCallbackHandler.java:94)
    	org.hibernate.ejb.event.EntityCallbackHandler.preUpdate(EntityCallbackHandler.java:79)
    	org.hibernate.ejb.event.EJB3FlushEntityEventListener.invokeInterceptor(EJB3FlushEntityEventListener.java:61)
    	org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:349)
    	org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:287)
    	org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:155)

  7. #7
    Join Date
    Jan 2013
    Posts
    1

    Default

    My solution is do not query User object from persistence layer instead of retrieve from spring-security directly:

    Code:
    	@Override
    	public User getCurrentAuditor() {
    		User auditor;
    
    		Authentication authentication = SecurityContextHolder.getContext()
    				.getAuthentication();
    		if (authentication != null) {
    			Object principal = authentication.getPrincipal();
    
    			if (principal instanceof User) {
    				auditor = (User) principal;
    			} else {
    				auditor = null;
    				log.warn("The principal is not a user.");
    			}
    		} else {
    			auditor = null;
    		}
    
    		return auditor;
    	}

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •