1

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);
    }
}
chris457
  • 56
  • 5
  • 2
    No the are not transactional there is, by default, however the `OpenEntityManagerInViewInterceptor` which is active by default and hence you will have a single shared `EntityManager`. – M. Deinum Feb 11 '18 at 10:31
  • if you go to /actuator/beans - do you see a PlatformTransactionManager bean? CrudRepository methods are transactional by default (e.g. delete) – hovanessyan Feb 11 '18 at 11:03

1 Answers1

2

@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 (i.e. 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.

chris457
  • 56
  • 5