2016-04-21 2 views
2

Я использую данные 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, но это ничего не изменило.

+0

См. Весеннюю документацию о [@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'? –

+0

Я добавил регистрацию ко всем установщикам 'Game', но ничего не отображается, поэтому я не обновляю его. –

+0

Вы можете попытаться включить свойства ['show_sql'] (http://www.mkyong.com/hibernate/hibernate-display-generated-sql-to-console-show_sql-format_sql-and-use_sql_comments/), чтобы отобразить, какой SQL сбрасываются и увеличивают уровень ведения спящего режима –

ответ

2

Проблема была не в моем использовании @Transactional.

Я использовал пользовательский Hibernate UserType, который хранит объект как JSON, используя библиотеку Джексона. Объект Game имеет поле, которое использует этот UserType. Класс этого поля не реализовал equals(). В результате Hibernate предположил, что объект был изменен и выдается сэкономить на моем объекте Game.

После правильной реализации equals() проблема исчезла.

0

Не используйте @Transactional на интерфейсах. Также будьте осторожны с внутренними вызовами метода.

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

Подсказка 1:

Spring рекомендует только аннотацию конкретные классы (и методы конкретных классов) с аннотацией @Transactional, в отличие от аннотирования интерфейсов. Вы, конечно, можете поместить аннотацию @Transactional на интерфейс (или метод интерфейса), но это работает , как и ожидалось, если вы используете прокси-серверы . Тот факт, что аннотации Java не унаследованы от интерфейсов , означает, что если вы используете прокси-классы на основе классов ( proxy-target-class = "true") или аспект на основе ткачества (mode = "aspectj"), параметры транзакции не распознаются инфраструктурой проксирования и переплетения, и объект не будет , завернутый в транзакционный прокси, что было бы явно плохо.

Подсказка 2:

В режиме прокси (который по умолчанию), только внешний метод вызывает идет через прокси-сервер, перехватываются.Это означает, что самозапуск , в рамках объекта-объекта, вызывающего другого метода целевого объекта, не приведет к фактической транзакции во время выполнения, даже если вызываемый метод помечен @Transactional. Кроме того, прокси-сервер должен быть полностью инициализирован, чтобы обеспечить ожидаемое поведение , поэтому вы не должны полагаться на эту функцию в своем коде инициализации , то есть на @PostConstruct.

Я не уверен, что это полностью решит вашу проблему, но я думаю, что это хороший шаг в правильном направлении.

Что я обычно делаю в этих ситуациях, это включить ведение журнала уровня отладки для весны (кстати, удачи в этом с большим приложением) и включить общий журнал в mysql.

How to show the last queries executed on MySQL?

Затем пытается выполнить запросы весной (отладчик предпочтительнее) и проверьте журналы MySQL.

+0

Малый дополнительный нет. Не используйте стиль com.company.project.service.x, com.company.project.domain.x и т. Д. Он будет расти из-под контроля, и ваш домен будет разбросан и с трудом работать. Используйте com.company.project.game.service, com.company.project.game.domain, com.company.project.user.service, com.company.project.user.domain и т. Д. ... –

Смежные вопросы