@Transactional Services

时间:2016-11-16 13:42:32

标签: java spring-boot transactions

我过了一天半寻找答案,但这件事会让我发疯!

我的队友和我正在开发一个基于springboot的项目。我专门负责管理部分,这是一个网络管理。

我的项目主要有三个层:控制器使用使用存储库的服务。 我希望我的项目与@Transactional一起用于服务层(到目前为止我们已经做了一些成功的努力,只使用注释进行配置)。

但是,似乎它不起作用:我的一个服务抛出RuntimeException并且没有完成回滚。我已经阅读了其他兄弟科目中的所有命题。与我的问题相关的唯一一件事,我不确定整齐地做了上下文配置。事件上,我不确定这是我的问题。

我告诉你实际的配置:

@SpringBootApplication
@EnableScheduling
@EnableTransactionManagement
public class Application extends SpringBootServletInitializer {

    @Value("${ajp.port}")
    private int ajpPort;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }

    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {};
        tomcat.addAdditionalTomcatConnectors(createConnector(ajpPort));
        return tomcat;
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return container -> {

                ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/static/401.html");
                ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/static/404.html");

                container.addErrorPages(error401Page, error404Page);
        };
    }

    @Bean
    public EmailValidator emailValidator() {
        return EmailValidator.getInstance();
    }

    private static Connector createConnector(int ajpPort) {
        Connector connector = new Connector("AJP/1.3");
        connector.setPort(ajpPort);
        return connector;
    }
}

网络配置:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private RequestProcessingTimeInterceptor requestProcessingTimeInterceptor;

    @Autowired
    private CertificateInterceptor certificateInterceptor;

    @Autowired
    private ProfilesAuthorizationInterceptor profilesAuthorizationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestProcessingTimeInterceptor);
        registry.addInterceptor(certificateInterceptor);
        registry.addInterceptor(profilesAuthorizationInterceptor);
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setExposeContextBeansAsAttributes(true);
        resolver.setPrefix("/WEB-INF/");
        resolver.setSuffix(".jsp");

        return resolver;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/admin/css/**").addResourceLocations("/WEB-INF/admin/css/").setCachePeriod(CACHE_PERIOD);
        registry.addResourceHandler("/admin/img/**").addResourceLocations("/WEB-INF/admin/img/").setCachePeriod(CACHE_PERIOD);
        registry.addResourceHandler("/admin/js/**").addResourceLocations("/WEB-INF/admin/js/").setCachePeriod(CACHE_PERIOD);
        registry.addResourceHandler("/admin/plugins/**").addResourceLocations("/WEB-INF/admin/plugins/").setCachePeriod(CACHE_PERIOD);
    }

}

类似于Controler:

@RestController
@RequestMapping("/pathA")
public class ControlerA {

    @Autowired
    public ServiceA serviceA;

    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public A getA(@PathVariable long id) {
        return serviceA.getA(id);
    }

}

类似服务(界面+implémentation):

public interface ServiceA {

    A getA(long id);

}

@Service
@Transactional
public class ServiceAImpl implements ServiceA {

    @Autowired
    public RepositoryA repositoryA;

    public A getA(long id) {
        (...)
        A a = repositoryA.findOne(id);
        a.updatesomething(something);
        repositoryA.update(a);
        doOtherThing(a); //throw RuntimeException
        (...)
        return a;
    }

}

和存储库:

@Repository
public interface RepositoryA extends JpaRepository<A, Long> {
    (...)
}

以下是MySQL数据库的配置:

# Configuration de la base de donnée
spring.datasource.url=jdbc:mysql://localhost/name_innodb
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1

我知道默认情况下存储库事务是有效的(我在SQLException发生时看到它)。但是在服务层中,没有任何事情发生(参见抛出异常行);抛出异常时,更新完成而不是回滚。那就意味着我的@Transactional会被忽略。

修改: 我设法获得我想要的事务,在Controller的方法getA(...)上添加@Transactional。它有效,但它不是管理交易的地方。

然后我的问题是:我怎样才能让它发挥作用?

1 个答案:

答案 0 :(得分:0)

好的,经过几天的头脑风暴,我发现了!

唯一可靠的答案是关注您的Configuration类。我的问题只是一个交叉配置问题,导致DispatcherServlet配置导致混乱。

相关主题:For web MVC Spring app should @Transactional go on controller or service?

修改 我添加了一些细节,因为要分离上下文很难找到一些信息。我仍在校准配置,因为没有关于所有弹簧注释的完整和详尽的信息。

您可以像这样创建父和子上下文:

@SpringBootApplication
@ComponentScan({"com.mycompany.service", "com.mycompany.interceptors","com.mycompany.manager"})
@PropertySource("file:config/application.properties")
public class ParentConfig{

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .parent(ParentConfig.class)
                .child(ChildConfig1.class, ChildConfig2.class, ChildConfig3.class, ..., ChildConfigN.class)
                .run(args);
    }

    (...)

}

我仍然想知道为什么我必须添加@PropertySource,以便孩子们知道属性值,为什么“classpath:path”在@PropertySource中不起作用,为什么我必须添加一个静态PropertySourcesPlaceholderConfigurer来使用@Value in我的孩子们(在我这样做之前,即没有这种分层的情境,每个情境都知道这些属性)

@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}

我仍在使用注释来完成每个配置工作。

修改 我终于找到了一些东西,以便能够正确使用spring配置:不同的配置必须尊重包装层次结构。

我停止使用父级和子级配置,让spring工作。我正在尝试使用这样的不同配置类:

MainConfig
|
| __________ my.package.mvc.MVCConfig
|
| __________ my.package.schedulers.SchedulerConfig
|
|
等等..

在我的MainConfig中,我添加:
@ComponentScan({"my.package.mvc", "my.package.services", "my.package.interceptors","my.package.managers", "my.package.schedulers"})

现在一切都很好!大多数情况下,MVCConfig不能与服务产生冲突,因为层次结构不同。