2016-04-21 1 views
2

Ich verwende Spring Data (über Spring Boot 1.3.3). Alle meine Repositorys verfügen über eine benutzerdefinierte Methode zum Abrufen eines Primärschlüssels. Zum Beispiel:Verwendung von @Transactional mit Spring Data benutzerdefinierten Methoden

@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()); 
    } 
} 

Ist der Einsatz von @Transactional hier richtig? Oder muss ich auch @Transactional zu UserRepositoryImpl hinzufügen (möglicherweise mit readOnly gesetzt oder nicht)?

Der Grund ich frage ist, weil ich 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}] 

Dies geschieht während JMeter Test unerklärlich bekommen. Die Methoden, die aufgerufen werden, ändern die Entität Game in keiner Weise jedoch.

Ich habe hinzugefügt, dies zu meiner Game Einheit für die Fehlersuche:

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) 

Betrachtet man nur die Sachen, die relevant ist:

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

dies einige Male einen Stack-Trace ähnlich wie diese gibt Meine App, Sie sehen dies:

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) 

Also irgendwie, findByUsername scheint eine Aktualisierung auf eine nicht verwandte Entität auszulösen Game?

FYI: GameServiceImpl#placeShots hat auch eine @Transactional Annotation. Ich habe auch versucht, eine solche Anmerkung zur Controller-Methode hinzuzufügen, aber daran änderte sich nichts.

+0

Siehe die Frühjahrsdokumentation über [@Transactional] (http://docs.spring.io/autorepo/docs/spring/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations) und besonders "Frühling empfiehlt, dass Sie nur konkrete Klassen kommentieren ...". 'findByUserName' löst einen Autoflush aus, der bereits in der aktuellen Transaktion durchgeführt wurde. Aus irgendeinem Grund löst Hibernate diesen automatischen Flush aus, vielleicht weil es Beziehungen zwischen Benutzer und Spiel gibt. Modifizieren Sie eine 'Game'-Entität (selbst wenn Sie Setter aufrufen), bevor Sie' findByUsername' aufrufen? –

+0

Ich habe alle meine Setter von 'Game' Logging hinzugefügt, aber nichts zeigt, also aktualisiere ich es nicht. –

+0

Sie können versuchen, ['show_sql'] (http://www.mkyong.com/hibernate/hibernate-display-generated-sql-to-console-show_sql-format_sql-and-use_sql_comments/) Eigenschaften zu aktivieren, um anzuzeigen, welche SQL werden geleert und erhöhen den Ruhezustand Logging-Level –

Antwort

2

Das Problem war nicht in meiner Verwendung von @Transactional.

Ich verwendete einen benutzerdefinierten Hibernate UserType, der ein Objekt als JSON unter Verwendung der Jackson-Bibliothek speichert. Das Objekt Game verfügt über ein Feld, das diesen Benutzertyp verwendet. Die Klasse dieses Felds hat equals() nicht implementiert. Als Ergebnis nahm Hibernate an, dass das Objekt geändert wurde, und gab eine Sicherung für mein Objekt Game aus.

Nach ordnungsgemäßer Implementierung equals() ging das Problem weg.

0

Verwenden Sie nicht @Transactional auf Schnittstellen. Seien Sie auch vorsichtig mit internen Methodenaufrufen.

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

Tipp 1:

Frühling empfiehlt, nur konkrete Klassen (und Methoden von konkreten Klassen) mit der @Transactional Anmerkung Anmerkungen versehen, im Gegensatz zu Kommentierung Schnittstellen. Sie können die Annotation @Transactional auf eine Schnittstelle (oder eine Schnittstellenmethode) setzen, aber das funktioniert nur so, wie Sie es erwarten würden, wenn Sie interface-basierte Proxies verwenden. Die Tatsache, dass Java-Annotationen nicht von -Schnittstellen geerbt werden, bedeutet, dass, wenn Sie klassenbasierte Proxys ( proxy-target-class = "true") oder den webbasierten Aspekt ( mode = "aspectj") verwenden, der Transaktionseinstellungen werden von der Proxy- und Webinfrastruktur nicht erkannt, und das Objekt wird nicht in einen Transaktions-Proxy eingebettet, was ausgesprochen schlecht wäre.

Tip 2:

im Proxy-Modus (das ist die Standardeinstellung), nur externe Methode aufruft kommen in über den Proxy abgefangen.Dies bedeutet, dass Selbstaufruf, in Wirklichkeit eine Methode innerhalb des Zielobjekts Aufruf eine andere Methode des Zielobjekts, führt zu einer tatsächlichen -Transaktion zur Laufzeit, auch wenn die aufgerufene Methode mit @Transactional markiert ist. Außerdem muss der Proxy vollständig initialisiert werden, um das erwartete Verhalten bereitzustellen, so dass Sie sich nicht auf dieses Feature in Ihrem Initialisierungscode verlassen sollten, d. H. @PostConstruct.

Ich bin nicht sicher, dass dies Ihr Problem vollständig lösen wird, aber ich denke, dass es ein guter Schritt in die richtige Richtung ist.

Was ich normalerweise in diesen Situationen mache, ist Debug-Level-Logging für den Frühling aktivieren (viel Glück dabei mit einer großen App) und aktivieren Sie allgemeine Anmeldung mysql.

How to show the last queries executed on MySQL?

Dann versuchen Abfragen im Frühjahr (Debugger bevorzugt) und überprüfen Sie die MySQL-Protokolle zu laufen.

+0

Kleines Extra nicht. Verwenden Sie nicht den Packstil com.company.project.service.x, com.company.project.domain.x usw. Es wird außer Kontrolle geraten und deine Domain wird zerstreut sein und es ist schwer mit ihr zu arbeiten. Verwenden Sie com.company.project.game.service, com.company.project.game.domain, com.company.project.user.service, com.company.project.user.domain usw. –