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

Spring由于web配置导致的spring配置文件找不到的问题的解决方案

Spring Cloud 入门系列