我正在使用Spring Data(通过Spring Boot 1.3.3)。我的所有存储库都有一个自定义方法来获取主键。例如:
@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() );
}
}
这里使用@Transactional
是否正确?或者我是否也需要将@Transactional
添加到UserRepositoryImpl
(可能还是设置了readOnly)?
我问的原因是因为我无法解释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}]
这在JMeter测试期间发生。尽管调用的方法不会以任何方式更改Game
实体。
我已将此添加到我的Game
实体进行调试:
@PreUpdate
public void preUpdate() {
System.out.println("GAME UPDATED!! version = " + version);
Thread.dumpStack();
}
这给出了几次与此类似的堆栈跟踪:
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)
只查看与我的应用相关的内容,您会看到:
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)
所以,不知怎的,findByUsername
似乎触发了对无关实体Game
的更新?
仅供参考:GameServiceImpl#placeShots
还有一个@Transactional
注释。我也尝试在Controller方法上添加这样的注释,但这并没有改变任何东西。
答案 0 :(得分:2)
在使用@Transactional
时问题是不。
我使用的是自定义Hibernate UserType,它使用Jackson库将对象存储为JSON。 Game
对象具有使用此UserType的字段。该字段的类未实现equals()
。因此,Hibernate假设对象已更改并在我的Game
对象上发出了保存。
正确实施equals()
后,问题就消失了。
答案 1 :(得分:1)
不要在接口上使用@Transactional。内部方法调用也要小心。
提示1:
Spring建议您只注释具体的类(和方法) 带有@Transactional注释的具体类),而不是 注释接口。你当然可以放置@Transactional 接口(或接口方法)上的注释,但这是有效的 只有在你使用基于接口的情况下才会如你所愿 代理。 Java注释不是从中继承的事实 接口意味着如果您使用基于类的代理( proxy-target-class =“true”)或基于编织的方面( mode =“aspectj”),然后无法识别事务设置 代理和编织基础设施,而对象不会 包含在事务代理中,这将是非常糟糕的。
提示2:
在代理模式(默认设置)下,只有外部方法调用 通过代理进入是截获的。这意味着 自调用,实际上是目标对象调用中的一个方法 目标对象的另一种方法,不会导致实际的 即使调用的方法被标记,运行时的事务也是如此 @Transactional。此外,代理必须完全初始化以提供 预期的行为,所以你不应该依赖于你的这个功能 初始化代码,即@PostConstruct。
我不确定这会完全解决您的问题,但我认为这是朝着正确方向迈出的良好一步。
在这些情况下我通常做的是启用spring的调试级别日志记录(顺便说一下,通过大型应用程序做好运)并启用一般登录mysql。
How to show the last queries executed on MySQL?
然后尝试在spring中运行查询(首选调试器)并检查mysql日志。