Spring Transactions 导致 spring-mvc 集成测试失败
Posted
技术标签:
【中文标题】Spring Transactions 导致 spring-mvc 集成测试失败【英文标题】:Spring Transactions cause spring-mvc integration Tests to fail 【发布时间】:2016-06-13 19:57:48 【问题描述】:我正在使用 spring boot 1.3 开发一个 spring 应用程序
我有一个如下所示的 MVC 请求处理程序:
@RequestMapping(method = PUT, path = "/categoryId")
public String update(@Valid @ModelAttribute("category") Category category, BindingResult result, @GetAttribute("currentStore") Store store, Model model, RedirectAttributes ra)
if (result.hasErrors())
model.addAttribute("categories", categoryService.activeCategories(store));
return EDIT_VIEW_NAME;
else
categoryService.update(category);
ra.addFlashAttribute("info", "Category updated successfully!");
return redirectTo(categoryUrls.indexPath());
这是它的集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(EstoreApplication.class)
@WebIntegrationTest
@Transactional
public class CategoriesControllerIntegrationTests
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Test
@WithFactoryUser(roles = "admin")
public void testUpdateCategoryActionWithInvalidData() throws Exception
//GIVEN
String categoryId = "category_001";
Store store = storeBuilder.getTestStore();
categoryBuilder.createListWithStore(5,store);
Category category = categoryBuilder.createWithId(categoryId);
//WHEN
this.mockMvc.perform(
put("/admin/categories/categoryId", categoryId)
.param("name", category.getName())
.param("title", category.getTitle())
.param("status", " ")
.param("store", store.getId())
.with(csrf())
)
//THEN
.andExpect(status().isOk())
.andExpect(view().name(CategoriesController.EDIT_VIEW_NAME))
.andExpect(model().attributeHasErrors("category"))
.andExpect(model().attributeHasFieldErrors("category", "status"))
;
为空间删除了一些位,但请注意顶部的 @Transactional 注释
它在没有线的情况下运行并通过:
model.addAttribute("categories", categoryService.activeCategories(store));
但是当添加它时,它给出了这个讨厌的异常,这是一个没有任何数据插入操作的bean验证异常(spring mvc已经处理了验证错误并且结果在BindingResult中)
List of constraint violations:[
ConstraintViolationImplinterpolatedMessage='com.estore.constraints.inset', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='com.estore.constraints.inset'
ConstraintViolationImplinterpolatedMessage='may not be empty', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='org.hibernate.validator.constraints.NotBlank.message'
], mergedContextConfiguration = [WebMergedContextConfiguration@ba2f4ec testClass = CategoriesControllerIntegrationTests, locations = '', classes = 'class org.commerceforge.estore.EstoreApplication', contextInitializerClasses = '[]', activeProfiles = '', propertySourceLocations = '', propertySourceProperties = 'org.springframework.boot.test.IntegrationTest=true', resourceBasePath = '', contextLoader = 'org.springframework.boot.test.SpringApplicationContextLoader', parent = [null]]].
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: Validation failed for classes [org.commerceforge.estore.app.model.Category] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImplinterpolatedMessage='com.estore.constraints.inset', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='com.estore.constraints.inset'
ConstraintViolationImplinterpolatedMessage='may not be empty', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='org.hibernate.validator.constraints.NotBlank.message'
]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:981)
at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:651)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:120)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155)
at org.commerceforge.estore.tests.mvc.web.integration.CategoriesControllerIntegrationTests.testUpdateCategoryActionWithInvalidData(CategoriesControllerIntegrationTests.java:296)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [org.commerceforge.estore.app.model.Category] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImplinterpolatedMessage='com.estore.constraints.inset', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='com.estore.constraints.inset'
ConstraintViolationImplinterpolatedMessage='may not be empty', propertyPath=status, rootBeanClass=class org.commerceforge.estore.app.model.Category, messageTemplate='org.hibernate.validator.constraints.NotBlank.message'
]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:160)
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:95)
at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:218)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:97)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:67)
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.getResultList(QueryImpl.java:449)
at org.hibernate.jpa.criteria.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:67)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:114)
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.$Proxy163.findAllByStoreAndStatus(Unknown Source)
at org.commerceforge.estore.app.service.impl.CategoryServiceImpl.activeCategories(CategoryServiceImpl.java:115)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
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.$Proxy200.activeCategories(Unknown Source)
at org.commerceforge.estore.app.web.mvc.CategoriesController.update(CategoriesController.java:81)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
... 84 more
当我删除类级别的@Transactional 时,一切运行顺利,所以我认为这是事务中的问题
行:
model.addAttribute("categories", categoryService.activeCategories(store));
是查找器查询的包装器:
@Transactional(readOnly = true)
public List<Category> activeCategories(Store store)
return categoryRepository.findAllByStoreAndStatus(store, Category.STATUS_ENABLED);
它只是一个 select quert,它永远不会导致 bean 验证错误
发生的情况是,我传递了一组无效数据来测试它不会通过,spring mvc 在 BindingResult 中捕获错误,但似乎在测试方法结束时,某种事务回滚(我不确定,只是猜测)并尝试插入数据
如果我删除 @Transactional 注释,则测试通过,但这对我来说并不实用,因为我的整个测试套件(950 多个测试)依赖于空数据库,并且我使用 @Transactional 实现它的方式来回滚任何更改数据
【问题讨论】:
您传递了无效数据,因此测试必须失败,但您不希望它失败?为什么你的测试课上有@Transactional
?因为“Spring TestContext Framework 中集成测试的回滚语义默认为true”。 @Transactional
不是这样。
当传递无效数据时,测试应该通过,因为spring框架捕获验证异常,将结果放入BindingResult对象中。 @Transactional 与 rollback=true 导致删除测试期间插入的所有数据,导致在测试结束时清理空数据库
@Transactional
没有 rollback
属性。再说一遍:所有测试都应该在默认回滚的事务中运行。没有必要明确地做任何事情。
【参考方案1】:
我认为问题在于只读查询的刷新模式。如果您使用的是休眠,您可以通过您的方法上的 org.springframework.data.jpa.repository.QueryHints-Annotation 将刷新模式设置为 COMMIT (https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/jpa/QueryHints.html#HINT_FLUSH_MODE)。
我现在只能猜测......等待您对我的问题的回答。但我认为你有一个 @ModelAttribute-annotated 方法来查找 Category-Object(类似于 http://www.javabeat.net/modelattribute-spring-mvc/)
这就是为什么 Category-object 被加载然后被请求中的数据修改,删除 status-value => Category-Object 包含在 entityManager 中的无效数据,在提交查询时刷新。
我猜也可以通过调用 entityManager.clear() 或 entityManager.detach(category) 来缓解问题。
我猜你有一个 OpenEntityManagerInViewFilter。您应该知道,即使您的控制器没有显式调用持久/更新,如果从非只读存储库方法中获取实体,您所做的每一个修改都将被写入数据库,除非有事务回滚(通过引发异常或手动告诉事务回滚http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/transaction/TransactionStatus.html#setRollbackOnly%28%29)
...编辑
啊。这就是为什么在没有@Transactional-rollback 的情况下进行测试的原因,因为在调用@ModelAttribute-annotated 方法时没有事务=> entityManager 立即关闭,因此在提交查询时不会刷新对 Category 的更改。
【讨论】:
我在finder方法的顶部添加了这一行:@QueryHints(@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE, value="COMMIT")) 并且它可以工作:): )拜托拜托,你能提供一些解释,或者一个参考文档的指针。这将不胜感激。谢谢 我不知道为什么会这样。似乎实体管理器在发出 jpa 查询时包含一个具有无效数据的 Category 对象,这会导致 entityMangers 状态的刷新(以确保查询的数据是最新的)......我会尝试找出为什么会发生这种情况。同时,你能把你的categoryBuilder的相关代码贴出来吗? 能否请您发布您的@ModelAttribute 注释控制器方法的代码?以上是关于Spring Transactions 导致 spring-mvc 集成测试失败的主要内容,如果未能解决你的问题,请参考以下文章
由于 IP 和 SP 之间的时区差异导致的 Spring Saml 安全身份验证问题
Spring Transactions 和通用 DAO 和服务的最佳实践
Distributed transactions in Spring, with and without XA
Spring JTA multiple resource transactions in Tomcat with Atomikos example