0

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?

Mohicane
  • 302
  • 2
  • 15
  • Which database? Show your configuration (properties etc). Also you are using Spring Boot then why would you need a Web config class (spring boot configures that for you already. – M. Deinum Nov 16 '16 at 13:46
  • The original config wasn't from me. I figure that the seperation of configuration is for more readability. I add the configuration ... – Mohicane Nov 16 '16 at 15:44
  • 1
    You don't need that config as spring boot already configures MVC for you. Also is that your actual service method or do you have a try/catch block or something in there? If `@Transactional` isn't working you must be doing something strange regarding the construction of the service. – M. Deinum Nov 16 '16 at 19:23
  • I know springboot makes a lot of thing for us, but we need to add some Interceptors, viewResolvers, ressourceHanlders, etc. So if I understand what you mean, we don't have to use @EnableWebMVC, but if we want to separate GeneralConfig and WebConfig, is it not a good way? There is no try catch block in the method (no try catch that I don't want :p ). I know how Transaction work, and as you could see in my "edit", I manage to do Transaction as I want ... but in the Controller layer. I just want that it's the service layer that is in charge of this – Mohicane Nov 17 '16 at 09:00
  • I remove the @EnableWebMvc, nothing change (even the good working of my app as long as I see) – Mohicane Nov 17 '16 at 15:20
  • Post your actual code, not the pseudo code you have posted here. There must be something in that class that prevents transactions to be applied (`final` keyword somewhere?). Or else you are doing even stranger things to construct a service (`@Bean` method in a non `@Configuration` class?). – M. Deinum Nov 17 '16 at 15:38
  • Done! I'm sorry but, as it is my company code, I can't show you whole. – Mohicane Nov 17 '16 at 16:02
  • Well I think there is something different with your actual service (or how you are calling the method) then you are showing here. That is the problem with pseudo code it almost never reflects the actual situation. – M. Deinum Nov 17 '16 at 16:06
  • Like I told you: My Transaction works well if I put `@Transaction` on the Controller method. Evenmore, when I do that I saw a debug log line which mean that my method is added for transaction. I guess that if `@Transaction` work as type annotation on the Service, every method of it will be added and I will see a debug line for each method (or for the class). – Mohicane Nov 17 '16 at 19:03

1 Answers1

0

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, i.e 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.

Community
  • 1
  • 1
Mohicane
  • 302
  • 2
  • 15