title: Usage of @Transactional with Spring Data custom methods
body: <p>I am using Spring Data (via Spring Boot 1.3.3). All my repositories have a custom method to get a primary key. For example:</p>
  
  <pre><code>@Transactional(readOnly=true)
  @Repository
  public interface UserRepository extends CrudRepository&lt;User, UserId&gt;, UserRepositoryCustom {
    User findByUsername(String username);
  }
  
  public interface UserRepositoryCustom {
    UserId nextId();
  }
  
  public class UserRepositoryImpl implements UserRepositoryCustom {
    public UserId nextId() {
      return new UserId( UUID.randomUUID() );
    }
  }
  </code></pre>
  
  <p>Is the use of <code>@Transactional</code> correct here? Or do I need to add an <code>@Transactional</code> to <code>UserRepositoryImpl</code> as well (possibly with readOnly set or not)?</p>
  
  <p>The reason I am asking is because I get unexplainable <code>ObjectOptimisticLockingFailureException</code></p>
  
  <pre><code>org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class 
  [com.company.project.domain.Game] with identifier [GameId{id=7968c30b-838f-424c-bfef-838de7028def}]: 
  optimistic locking failed; nested exception is 
  org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 
  (or unsaved-value mapping was incorrect) : [com.company.project.domain.Game#GameId{id=7968c30b-838f-424c-bfef-838de7028def}]
  </code></pre>
  
  <p>This happens during JMeter testing. Altough the methods that are called do not change the <code>Game</code> entity in any way.</p>
  
  <p>I have added this to my <code>Game</code> entity for debugging:</p>
  
  <pre><code>@PreUpdate
  public void preUpdate() {
      System.out.println("GAME UPDATED!! version = " + version);
      Thread.dumpStack();
  }
  </code></pre>
  
  <p>This gives a few times a stack trace similar to this:</p>
  
  <pre><code>java.lang.Exception: Stack trace
      at java.lang.Thread.dumpStack(Thread.java:1329)
      at com.company.project.domain.Game.preUpdate(Game.java:85)
      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:497)
      at org.hibernate.jpa.event.internal.jpa.EntityCallback.performCallback(EntityCallback.java:47)
      at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.callback(CallbackRegistryImpl.java:112)
      at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.preUpdate(CallbackRegistryImpl.java:76)
      at org.hibernate.jpa.event.internal.core.JpaFlushEntityEventListener.invokeInterceptor(JpaFlushEntityEventListener.java:68)
      at org.hibernate.event.internal.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:342)
      at org.hibernate.event.internal.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:293)
      at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:160)
      at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:231)
      at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:102)
      at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:61)
      at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1227)
      at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1293)
      at org.hibernate.internal.QueryImpl.list(QueryImpl.java:103)
      at org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:573)
      at org.hibernate.jpa.internal.QueryImpl.getSingleResult(QueryImpl.java:495)
      at org.hibernate.jpa.criteria.compile.CriteriaQueryTypeQueryAdapter.getSingleResult(CriteriaQueryTypeQueryAdapter.java:71)
      at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:206)
      at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:78)
      at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:100)
      at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:91)
      at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:462)
      at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:440)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
      at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:131)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
      at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
      at com.sun.proxy.$Proxy141.findByUsername(Unknown Source)
      at com.company.project.service.UserServiceImpl.findByUsername(UserServiceImpl.java:117)
      at com.company.project.service.UserServiceImpl.subtractCredits(UserServiceImpl.java:143)
      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:497)
      at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
      at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
      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.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
      at com.sun.proxy.$Proxy154.subtractCredits(Unknown Source)
      at com.company.project.service.GameServiceImpl.subtractCreditsForPlacedShotsAndSave(GameServiceImpl.java:703)
      at com.company.project.service.GameServiceImpl.placeShotsOnGameWhenGameIsOpen(GameServiceImpl.java:641)
      at com.company.project.service.GameServiceImpl.placeShotsOnGame(GameServiceImpl.java:629)
      at com.company.project.service.GameServiceImpl.placeShots(GameServiceImpl.java:281)
      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:497)
      at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
      at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
      at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
      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.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
      at com.sun.proxy.$Proxy164.placeShots(Unknown Source)
      at com.company.project.controller.front.FrontGameController.placeShots(FrontGameController.java:180)
  </code></pre>
  
  <p>Looking at only the stuff that is relevant to my app, you see this:</p>
  
  <pre><code>java.lang.Exception: Stack trace
      at java.lang.Thread.dumpStack(Thread.java:1329)
      at com.company.project.domain.Game.preUpdate(Game.java:85)
      at com.company.project.service.UserServiceImpl.findByUsername(UserServiceImpl.java:117)
      at com.company.project.service.UserServiceImpl.subtractCredits(UserServiceImpl.java:143)
      at com.company.project.service.GameServiceImpl.subtractCreditsForPlacedShotsAndSave(GameServiceImpl.java:703)
      at com.company.project.service.GameServiceImpl.placeShotsOnGameWhenGameIsOpen(GameServiceImpl.java:641)
      at com.company.project.service.GameServiceImpl.placeShotsOnGame(GameServiceImpl.java:629)
      at com.company.project.service.GameServiceImpl.placeShots(GameServiceImpl.java:281)
      at com.company.project.controller.front.FrontGameController.placeShots(FrontGameController.java:180)
  </code></pre>
  
  <p>So somehow, <code>findByUsername</code> seems to trigger an update to an unrelated entity <code>Game</code>?</p>
  
  <p>FYI: <code>GameServiceImpl#placeShots</code> also has an <code>@Transactional</code> annotation. I also tried adding such an annotation on the Controller method, but that did not change anything.</p>