3

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:

@Transactional(readOnly=true)
@Repository
public interface UserRepository extends CrudRepository<User, UserId>, UserRepositoryCustom {
  User findByUsername(String username);
}

public interface UserRepositoryCustom {
  UserId nextId();
}

public class UserRepositoryImpl implements UserRepositoryCustom {
  public UserId nextId() {
    return new UserId( UUID.randomUUID() );
  }
}

Is the use of @Transactional correct here? Or do I need to add an @Transactional to UserRepositoryImpl as well (possibly with readOnly set or not)?

The reason I am asking is because I get unexplainable ObjectOptimisticLockingFailureException

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

This happens during JMeter testing. Altough the methods that are called do not change the Game entity in any way.

I have added this to my Game entity for debugging:

@PreUpdate
public void preUpdate() {
    System.out.println("GAME UPDATED!! version = " + version);
    Thread.dumpStack();
}

This gives a few times a stack trace similar to this:

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)

Looking at only the stuff that is relevant to my app, you see this:

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)

So somehow, findByUsername seems to trigger an update to an unrelated entity Game?

FYI: GameServiceImpl#placeShots also has an @Transactional annotation. I also tried adding such an annotation on the Controller method, but that did not change anything.

Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
  • See the spring documentation about [@Transactional](http://docs.spring.io/autorepo/docs/spring/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations) and especialy `Spring recommends that you only annotate concrete classes....`. `findByUserName` triggers an autoflush of update already done in the current transaction. For some reason Hibernate triggers this auto flush, maybe because there are relations between user and game. Do you modify a `Game` entity (even by calling setter) prior to calling `findByUsername` ? – Nicolas Labrot Apr 21 '16 at 19:45
  • I added logging to all my setters of `Game` but nothing shows, so I am not updating it. – Wim Deblauwe Apr 22 '16 at 06:16
  • You may try to enable [`show_sql`](http://www.mkyong.com/hibernate/hibernate-display-generated-sql-to-console-show_sql-format_sql-and-use_sql_comments/) properties to display which SQL are flushed and increase the hibernate logging level – Nicolas Labrot Apr 22 '16 at 07:04

2 Answers2

2

The problem was not in my use of @Transactional.

I was using a custom Hibernate UserType that stores an object as JSON using the Jackson library. The Game object has a field that uses this UserType. The class of that field did not implement equals(). As a result, Hibernate assumed the object was changed and issued a save on my Game object.

After properly implementing equals(), the problem went away.

Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
1

Don't use @Transactional on interfaces. Also be careful with internal method calls.

http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations

Tip 1:

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class="true") or the weaving-based aspect ( mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.

Tip 2:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct.

I'm not sure that this will entirely solve your problem but I think that it's a good step to the right direction.

What I usually do in these situations is enable debug level logging for spring (good luck doing that with a big app by the way) and enable general log on mysql.

How to show the last queries executed on MySQL?

Then trying to run queries in spring (debugger preferred) and check the mysql logs.

Community
  • 1
  • 1
Lakatos Gyula
  • 3,949
  • 7
  • 35
  • 56
  • Small extra not. Don't use the com.company.project.service.x, com.company.project.domain.x etc packaging style. It will grow out of hand and your domain will be scattered and hard to work with. Use com.company.project.game.service, com.company.project.game.domain, com.company.project.user.service, com.company.project.user.domain etc... – Lakatos Gyula Apr 22 '16 at 08:59