Я использую данные Spring (через Spring Boot 1.3.3). У всех моих репозиториев есть собственный метод получения первичного ключа. Например:Использование @Transactional с пользовательскими методами Spring Data
@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
?
FYI: GameServiceImpl#placeShots
также имеет аннотацию @Transactional
. Я также попытался добавить такую аннотацию к методу Controller, но это ничего не изменило.
См. Весеннюю документацию о [@Transactional] (http://docs.spring.io/autorepo/docs/spring/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations) и особенно 'Весна рекомендует, чтобы вы только комментировали конкретные классы ....'. 'findByUserName' запускает автозапуск обновления, уже выполненный в текущей транзакции. По какой-то причине Hibernate запускает этот автопоток, может быть, потому что существуют отношения между пользователем и игрой. Вы модифицируете объект 'Game' (даже при вызове setter) до вызова' findByUsername'? –
Я добавил регистрацию ко всем установщикам 'Game', но ничего не отображается, поэтому я не обновляю его. –
Вы можете попытаться включить свойства ['show_sql'] (http://www.mkyong.com/hibernate/hibernate-display-generated-sql-to-console-show_sql-format_sql-and-use_sql_comments/), чтобы отобразить, какой SQL сбрасываются и увеличивают уровень ведения спящего режима –