简体   繁体   中英

Are Spring @Controller rsp. @RestController transactional with spring-data-jpa repositories? It looks like it

I am following the Spring-boot tutorial at http://www.baeldung.com/spring-boot-start which uses the spring-boot-starter-web as well as the spring-boot-starter-data-jpa without customization. My spring-boot-starter-parent -version is 1.5.10.RELEASE .

It is a simple REST-Api for a simple Book -entity backed by a spring-data-jpa Repository I am experimenting with different implementations of the delete-method of the @RestController . When calling the sequence

Book book = repo.findOne((Long)1L);
repo.delete(book);

in the main() -method of the SpringBoot-application there will be 2 select -statements generated by JPA/Hibernate as can be seen in the log (abbreviated for clarity):

select book0_.id (...) from book book0_ where book0_.id=?
select book0_.id (...) from book book0_ where book0_.id=?
delete from book where id=?

This is the expected behaviour: Outside of a transaction these 2 calls will trigger a transaction each. Furthermore, since EntityManager.remove() only accepts attached/managed entities, the spring-data delete() -implementation does an EntityManager-find() before calling EntityManager.remove() on the result of the find() . The same sequence in the @RestController -annotated class however will only call select once. Furthermore, a little experiment strongly suggests that this method runs inside a transaction and that the persistence-context of some EntityManager is apparently active here:

@DeleteMapping("{id}")
    void delete(@PathVariable long id) {
        Book book = repo.findOne((Long)id);
        repo.delete(book);
        System.out.println(book);
        book.setTitle("XXX");
        Book book2 = repo.findOne((Long)id);
        System.out.println(book2);
        repo.delete(id);
    }

The log-output when called with a valid id is (again, abbreviated for clarity):

select book0_.id as id1_0_0_(...) from book book0_ where book0_.id=?
Book [id=1, title=Spring Boot, author=Chris]
Book [id=1, title=XXX, author=Chris]
delete from book where id=?

It is my understanding (as well as the result of extensive seach on stackoverflow and the rest of the internet) that @Controller -methods run outside of a transaction. Indeed, there is discussion whether a @Controller should or should not be @Transactional . My @Controller isn't.

So how is this observed behaviour possible? And is there some documentation explaining this?

For completeness' sake, here are class-definitions: The controller:

@RestController
@RequestMapping("/api/books/")
public class BookController {
    @Autowired
    BookRepository repo;

    (...)

 @DeleteMapping("{id}")
        void delete(@PathVariable long id) {
(...) see above
    }

The spring-data-jpa interface:

public interface BookRepository extends CrudRepository<Book, Long>{
    List<Book> findByTitle(String title);
    Optional<Book> findOne(long id);
}

The SpringBoot-application:

@SpringBootApplication(scanBasePackageClasses= {SimpleController.class}) 
@EntityScan(basePackageClasses={Book.class})
@EnableJpaRepositories(basePackageClasses= {BookRepository.class})
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
                              SpringApplication.run(Application.class, args);
    }
}

@M.Deinum Thank you for pointing me to the OpenEntityManagerInViewInterceptor -issue. I now found the apparently infamous OSIV/OEMIV (open session in view/open EntityManager in view)-discussion (ie should the EntityManager be still open in the controller-methods and thus prevent LazyLoading -issues or should, on the contrary, these issues be exposed?

AND: What should the default be? This link https://github.com/spring-projects/spring-boot/issues/7107 has the discussion. Therein, a blog-entry arguing against OSIV/OEMIV is discussed: https://vladmihalcea.com/the-open-session-in-view-anti-pattern/ I was pointed to this by this stackoverflow-question: What is this spring.jpa.open-in-view=true property in Spring Boot? To sum up: The default is OSIV/OEMIV but it can be easily toggled using the application.properties property spring.jpa.open-in-view=false The discussion comes to the conclusion that OSIV/OEMIV should remain the default for SpringBoot. It should however be documented better (its existence is hard to find; only in an appendix of a documentation)

I now have experimented with spring.jpa.open-in-view=false and it does indeed work as advertised.

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