@RestController methods seem to be Transactional by default, Why?
Asked Answered
M

3

25

Using Spring boot 1.3.1

I don't understand why @RestController are Transactionnal by default. I haven't found anything saying so in the docs.

Example which pushes this fact that the method findOne() in the controller below is Transactionnal:

@RestController
@RequestMapping("/books")
public class BookController {

    @RequestMapping("/{id}")
    public Book findOne(@PathVariable Long id) {
        Book book = this.bookDao.findOneBookById(id);
        // following line
        // => triggers a select author0_.id as id1_0_0_ etc... // where author0_.id=?
        System.out.println(book.getAuthor().getFirstname()); 
        return book;
    }
}

The line with the System.out.println(book.getAuthor().getFirstname()); should raise a LazyInitiaizationFailure BUT here it is successful and trigger the select of an an Author. So the method findOne seems to be transactionnal. With the eclipse debugger I can be sure that it is really this line that triggers the complementary select. But Why is that method transactionnal ?

@Configuration
@ComponentScan(basePackageClasses = _Controller.class)
@Import(BusinessConfig.class)
public class WebConfig extends WebMvcConfigurerAdapter {
   // ... here the conf to setup Jackson Hibernate4Module
}

@Configuration
@EnableAutoConfiguration
@EnableTransactionManagement
@EntityScan(basePackageClasses = _Model.class)
@ComponentScan(basePackageClasses = { _Dao.class })
public class BusinessConfig {
}

@SpringBootApplication
public class BookstoreStartForWebEmbedded {

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

}

libs : 
spring-boot-starter 1.3.1.RELEASE
spring-boot-starter-test : 1.3.1.RELEASE
spring-boot-starter-valisation : 1.3.1.RELEASE
spring-boot-starter-web : 1.3.1.RELEASE
spring-boot-starter-data-jpa : 1.3.1.RELEASE
postgresql: 9.4-1206-jdbc41
querydsl-jps:3.7.0
jackson-annotations:2.6.4
jackson-datatype-hibernate4:2.6.4

any idea ?

If it is a feature, i would like to switch it off...

Mervin answered 6/1, 2016 at 19:57 Comment(0)
H
31

In addition to MirMasej answers, there is one more thing: Spring Boot will automatically register an OpenEntityManagerInViewInterceptor when the following conditions are true:

  • you have a web application
  • you use JPA

Both conditions are true in your case. This interceptor holds the entity manager open for the whole duration of a request. The auto configuration occurs in the class JpaBaseConfiguration.

If you don't want that behaviour, you can add the following property to your application.properties file:

spring.jpa.open-in-view=false

Btw. this behaviour is completely independent of transactions, it's only related to the lifecycle of the entity manager. You can still have two separate transactions and no LazyInitializationException, if both transactions have the same underlying entity manager instance.

Halogen answered 6/1, 2016 at 21:29 Comment(4)
ok thanks for your explanation. very helpful. I tried to bookDao.save(_book); and then bookDao.findOneById(_book.getId()); in the same handler method, and the findOne() does not hit the DB but uses the em cache. So it proves your point. the same em is used for the 1rst save tx and then for the findOne. But the 1rst tx (save) is already commitedMervin
BTW do you know how to get the em ? Does @PersistenceContext inject the same em ?Mervin
I would think so. Spring Data JPA also uses this annotation to get the entity manager, and this entity manager is also the one bound to the request. So if it works for Spring Data, it should work for your code.Halogen
"this behaviour is completely independent of transactions" -> I didn't quite catch this part of the answer. Finally got the clarification from a different article at developpaper.com/spring-boot-transaction-rollback. As per this, implicit transaction gets started in service. And, only txns get rolled back for the exceptions thrown up from the method it started the transaction. So, controller won't if it does not explicitly annotate to start the txn there. Thought would be of use to somebody else.Bandylegged
M
1

The answer of @dunni is absolutely brilliant and works. In my case it penalized performance, in the call from a @Controller to a method of an @Service with @Transactional processing read-only JPA operations, on the other hand, calling the same method from a @WebServlet processed it twice as fast. But setting spring.jpa.open-in-view=false from @Controller processed just as fast than from @WebServlet.

But in addition in this sense you can do the following unitarily and works the same annotating the method called from @Controller with @Transactional in this way, example:

@GetMapping("/test")
@Transactional(propagation = Propagation.NEVER, readOnly = true)
public void test()
Maxentia answered 16/12, 2021 at 13:0 Comment(0)
A
0

One-to-one relations are always eagerly fetched. Judging by method names book.getAuthor().getFirstname(), book->author and author->firstName are such relations. LazyInitializationException will only occur for lazy collections.

Alisander answered 6/1, 2016 at 20:2 Comment(1)
Your are right in general, but in my case @ManyToOne(fetch = FetchType.LAZY) which explains the behaviorMervin

© 2022 - 2025 — McMap. All rights reserved.