简体   繁体   中英

@Transactional Services

I passed one day and a half looking for answer, but this thing is going to put me crazy!

My teammates and I are working on an project, based on springboot. I work specificaly on the administration part, which is a web administration.

There are mainly three layers on my project: Controllers which use Services which use Repositories. I want my project work with @Transactional for the Service layer (we made some successful efforts until now to use only annotations for configuration).

But, it seems that it doesn't work: One of my service throws a RuntimeException and no rollback is done. I allready read all the proposition in the others sibling subjects. The only thing, related to my problem, that i'm not sure to do neatly is the contexts configuration. Eventhow, i'm not sure that it's really my problem.

I show you the actual configuration:

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

The web config:

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

}

A Controler-like:

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

}

A Service-like (interface + 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;
    }

}

And the Repository:

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

Here is the configuration of the MySQL database:

# 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

I know that repository transaction works by default (I saw it, when a SQLException happen). But in the service layer, nothing happen (cf. the throwing exception line) ; when the exception is thrown, the update is done and not rollback. Then it mean that my @Transactional is ignored.

Edit : I manage to get a transaction like I want, adding @Transactional on the method getA(...) of the Controller. It works, but it's not the place to manage Transaction.

Then my question is: How can I make it work?

Ok, after some days of brainstorming, I found!

The only reasonnable answer is to take care about your Configuration class. My problem was only a crossover configuration problem which leaded to a DispatcherServlet configuration who caused the mess.

Related Subject: For web MVC Spring app should @Transactional go on controller or service?

Edit: I add some details because it'll be hard to find some information in order to separate context. And I'm still calibrating the configuration because there's no complete and exhaustive information about all the spring's annotations.

You could create parent and child context like this:

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

    (...)

}

I'm still wondering why I must add the @PropertySource in order children are aware of property values, why "classpath:path" have not work in @PropertySource, why I have to add a static PropertySourcesPlaceholderConfigurer for using @Value in my children (before I do that, ie without this hierarchical contexts, every context was aware of the properties)

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

and I'm still playing with annotations in order every configuration work.

Edit: I finally have found something in order to work correctly with spring configuration: Different configurations must respect packaging hierarchy.

I stop working with parent and child configuration and let spring work. I ordonate my different config class like this:

MainConfig
|
|__________my.package.mvc.MVCConfig
|
|__________my.package.schedulers.SchedulerConfig
|
|
and so on..

And in my MainConfig I add:
@ComponentScan({"my.package.mvc", "my.package.services", "my.package.interceptors","my.package.managers", "my.package.schedulers"})

And everything is good now! Mostly, MVCConfig can not create conflict with services, because of the different hierarchy.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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