我正在关注http://www.baeldung.com/spring-boot-start的Spring-boot教程,该教程使用 spring-boot-starter-web
以及 spring-boot-starter-data-jpa
而无需自定义 . 我的 spring-boot-starter-parent
-version是 1.5.10.RELEASE
.
这是一个简单的REST-Api,用于由spring-data-jpa支持的简单 Book
实体 Repository
我正在试验 @RestController
的delete-method的不同实现 . 调用序列时
Book book = repo.findOne((Long)1L);
repo.delete(book);
在SpringBoot应用程序的 main()
-method中,JPA / Hibernate将生成2个 select
-statements,如日志中所示(为清晰起见,缩写):
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()
只接受附加/管理实体,因此在 find()
的结果上调用 EntityManager.remove()
之前,spring-data delete()
-mplemple会执行 EntityManager-find()
. 然而, @RestController
注释类中的相同序列只会调用 select
一次 . 此外,一个小实验强烈建议此方法在事务内部运行,并且某些EntityManager的持久性上下文在这里显然是活动的:
@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
-methods在事务之外运行 . 实际上,有人讨论@Controller应该或不应该是 @Transactional
. 我的 @Controller
不是 .
那么这种观察到的行为怎么可能呢?是否有一些文件解释这个?
为了完整起见,这里是类定义:控制器:
@RestController
@RequestMapping("/api/books/")
public class BookController {
@Autowired
BookRepository repo;
(...)
@DeleteMapping("{id}")
void delete(@PathVariable long id) {
(...) see above
}
spring-data-jpa接口:
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);
}
}
1 回答
@ M.Deinum感谢您指点我
OpenEntityManagerInViewInterceptor
-issue . 我现在发现了明显臭名昭着的OSIV / OEMIV(在视图中打开会话/在视图中打开EntityManager)-discussion(即如果EntityManager
仍然在控制器方法中打开,从而防止LazyLoading
-issues或者应该相反,这些问题暴露?AND:默认应该是什么?此链接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
,它确实像宣传的那样工作 .