我正在关注 http://www.baeldung.com/spring-boot-start 上的 Spring-boot 教程它使用 spring-boot-starter-web
以及 spring-boot-starter-data-jpa
而无需自定义。我的 spring-boot-starter-parent 版本是 1.5.10.RELEASE。
它是一个简单的 REST-Api,用于由 spring-data-jpa Repository
支持的简单 Book
实体
我正在尝试 @RestController 的删除方法的不同实现。
调用序列时
Book book = repo.findOne((Long)1L);
repo.delete(book);
在 SpringBoot 应用程序的 main()
方法中,将有 2 个由 JPA/Hibernate 生成的 select
语句,如日志中所示(缩写为为了清楚起见):
select book0_.id (...) from book book0_ where book0_.id=?
select book0_.id (...) from book book0_ where book0_.id=?
delete from book where id=?
这是预期的行为:在事务之外,这两个调用将分别触发一个事务。此外,由于 EntityManager.remove()
仅接受附加/托管实体,因此 spring-data delete()
实现会执行 EntityManager-find()
在对 find()
的结果调用 EntityManager.remove()
之前。
然而,@RestController 注释类中的相同序列只会调用 select
一次。此外,一个小实验强烈表明该方法在事务内运行,并且某些 EntityManager 的持久上下文显然在这里处于 Activity 状态:
@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);
}
使用有效的 id
调用时的日志输出为(再次为了清晰起见进行缩写):
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=?
据我了解(以及在 stackoverflow 和互联网其他部分进行广泛搜索的结果),@Controller
方法在事务之外运行。事实上,存在关于 @Controller 是否应该是 @Transactional
的讨论。我的 @Controller
不是。
那么这种观察到的行为是如何可能的呢? 有没有一些文档对此进行解释?
为了完整起见,以下是类定义: Controller :
@RestController
@RequestMapping("/api/books/")
public class BookController {
@Autowired
BookRepository repo;
(...)
@DeleteMapping("{id}")
void delete(@PathVariable long id) {
(...) see above
}
spring-data-jpa 接口(interface):
public interface BookRepository extends CrudRepository<Book, Long>{
List<Book> findByTitle(String title);
Optional<Book> findOne(long id);
}
SpringBoot 应用程序:
@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 感谢您向我指出 OpenEntityManagerInViewInterceptor
-问题。我现在发现了明显臭名昭著的 OSIV/OEMIV(在 View 中打开 session /在 View 中打开 EntityManager)讨论(即 EntityManager
是否应该在 Controller 方法中保持打开状态,从而防止 LazyLoading
-问题或应该在相反,这些问题是否会暴露出来?
并且:默认值应该是什么?此链接https://github.com/spring-projects/spring-boot/issues/7107有讨论。其中讨论了反对 OSIV/OEMIV 的博客文章:
https://vladmihalcea.com/the-open-session-in-view-anti-pattern/
这个 stackoverflow 问题指出了这一点:
What is this spring.jpa.open-in-view=true property in Spring Boot?
总结一下:默认为 OSIV/OEMIV,但可以使用 application.properties 属性 spring.jpa.open-in-view=false
轻松切换。
讨论得出的结论是 OSIV/OEMIV 应该保留为 SpringBoot 的默认值。然而,应该更好地记录它(它的存在很难找到;仅在文档的附录中)
我现在已经尝试过 spring.jpa.open-in-view=false
它确实像宣传的那样有效。
关于java - Spring @Controller rsp. @RestController 与 spring-data-jpa 存储库进行事务处理?看起来像这样,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48728551/