简体   繁体   English

使用@Transactional和Spring Data自定义方法

[英]Usage of @Transactional with Spring Data custom methods

I am using Spring Data (via Spring Boot 1.3.3). 我正在使用Spring Data(通过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? @Transactional的使用在这里正确吗? Or do I need to add an @Transactional to UserRepositoryImpl as well (possibly with readOnly set or not)? 还是我也需要向UserRepositoryImpl添加一个@Transactional (可能设置了readOnly)?

The reason I am asking is because I get unexplainable ObjectOptimisticLockingFailureException 我问的原因是因为我得到了无法解释的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. 这在JMeter测试期间发生。 Altough the methods that are called do not change the Game entity in any way. 所调用的方法完全不会以任何方式更改Game实体。

I have added this to my Game entity for debugging: 我已将其添加到我的Game实体中进行调试:

@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 ? 因此,以某种方式, findByUsername似乎触发了对不相关实体Game的更新?

FYI: GameServiceImpl#placeShots also has an @Transactional annotation. 仅供参考: GameServiceImpl#placeShots还具有@Transactional注释。 I also tried adding such an annotation on the Controller method, but that did not change anything. 我还尝试在Controller方法上添加这样的注释,但是并没有改变任何东西。

The problem was not in my use of @Transactional . 问题在于我使用@Transactional

I was using a custom Hibernate UserType that stores an object as JSON using the Jackson library. 我正在使用自定义的Hibernate UserType,它使用Jackson库将对象存储为JSON。 The Game object has a field that uses this UserType. Game对象具有使用此UserType的字段。 The class of that field did not implement equals() . 该字段的类未实现equals() As a result, Hibernate assumed the object was changed and issued a save on my Game object. 结果,Hibernate假定该对象已更改,并在我的Game对象上进行了保存。

After properly implementing equals() , the problem went away. 正确实现equals() ,问题消失了。

Don't use @Transactional on interfaces. 不要在接口上使用@Transactional。 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 http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations

Tip 1: 秘诀1:

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces. Spring建议您仅使用@Transactional注释对具体类(以及具体类的方法)进行注释,而不是对接口进行注释。 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. 您当然可以在接口(或接口方法)上放置@Transactional批注,但这仅在您使用基于接口的代理时才可以预期。 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. Java注释不是从接口继承的事实意味着,如果您使用的是基于类的代理(proxy-target-class =“ true”)或基于编织的方面(mode =“ aspectj”),则事务设置为代理和编织基础结构无法识别该对象,并且该对象也不会包装在事务代理中,这肯定是不好的。

Tip 2: 提示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. 这意味着自调用实际上是目标对象中调用目标对象另一个方法的方法,即使调用的方法标记有@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, ie @PostConstruct. 另外,必须完全初始化代理以提供预期的行为,因此您不应在初始化代码(即@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. 在这些情况下,我通常要做的是启用spring的调试级别日志记录(顺便说一句,通过大型应用程序来实现这一点),并启用mysql常规登录。

How to show the last queries executed on MySQL? 如何显示在MySQL上执行的最后一个查询?

Then trying to run queries in spring (debugger preferred) and check the mysql logs. 然后尝试在spring中运行查询(首选调试器)并检查mysql日志。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM