在 spring 引导中,存储库保存与控制器或命令行运行器的工作方式不同
repository save works differently from controller or commandlinerunner in spring boot
我有两种情况,当从不同的上下文调用时,子实体保存的工作方式不同。
当我更新没有级联父级的子级时,修改后的数据也会更新,但这仅发生在控制器中,如果我从 CommandLineRunner
调用父级更新不会发生。
这是我的服务
@Service
public class BookService {
private final BookAuthorRepository bookAuthorRepository;
private final BookRepository bookRepository;
@Autowired
public BookService(BookAuthorRepository bookAuthorRepository, BookRepository bookRepository) {
this.bookAuthorRepository = bookAuthorRepository;
this.bookRepository = bookRepository;
}
public void updateBookAuth() {
Book book = bookRepository.findById(3).get();
book.setName("should not update");
book.getBookAuthor().setName("new name");
bookAuthorRepository.save(book.getBookAuthor());
}
}
当我从下面的 class 调用它时,只有 BookAuthor
名称被更新,这是正确的行为。
@Component
public class MyRunner implements CommandLineRunner {
@Autowired
private BookService bookService;
@Override
public void run(String... args) throws Exception {
bookService.updateBookAuth();
}
}
但是如果我从控制器调用这个服务方法:
@RestController
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping("/")
public void test() {
bookService.updateBookAuth();
}
}
然后 Book
name 和 BookAuthor
name 都更新了,但是我不明白为什么 Book
name 会更新。
以下是实体:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne
private BookAuthor bookAuthor;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public BookAuthor getBookAuthor() {
return bookAuthor;
}
public void setBookAuthor(BookAuthor bookAuthor) {
this.bookAuthor = bookAuthor;
}
}
@Entity
public class BookAuthor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne(mappedBy = "bookAuthor")
private Book book;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
我也考虑过事务性,如果我在方法上写事务,那么所有上下文都会打开,父子关系都会更新,但在这种情况下我不使用它,为什么会这样?
要真正了解发生了什么,您需要了解 Hibernate 的工作原理,尤其是 Hibernate Session。这是一篇好文章 https://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate 。在 BookService class 中,您专门为图书作者设置了新名称,并且由于此事务是会话的一部分,因此它也会更新它。 Spring Data JPA 非常有用,但您必须熟悉 Hibernate 才能完全理解其行为。
当您从存储库中获取 book 实体时,您可以检查它是否仍在管理中。为此,我们可以直接在服务中注入实体管理器,并添加如下日志语句:
@Service
@RequiredArgsConstructor
public class BookService {
private final BookBookAuthorRepository bookAuthorRepository;
private final BookRepository bookRepository;
private final EntityManager entityManager;
public void updateBookAuth() {
Book book = bookRepository.findById(1L).get();
System.out.println("----------> is managed: " + entityManager.contains(book));
book.setName("should not update");
book.getBookAuthor().setName("new name");
bookAuthorRepository.save(book.getBookAuthor());
}
}
现在,当您从 MyRunner 调用 updateBookAuth 函数时,它将打印:
----------> is managed: false
当您从 BookController 调用 updateBookAuth 函数时,它将打印:
----------> is managed: true
这意味着在其余控制器的情况下,数据库操作是在单个持久性会话中执行的,因此书籍和作者实体都已更新。
并且在命令行运行器的情况下,图书实体查询是在单独的持久性会话中执行的,之后被分离,作者实体更新是在单独的持久性会话中执行的,因为图书实体更新迷路了。
发生这种情况是因为 Spring Web 框架默认将持久性会话和请求生命周期联系在一起。像这样实现是为了防止视图层中延迟初始化关联的问题。
要防止这种行为,您可以在 application.properties 文件中使用以下设置禁用它:
spring.jpa.open-in-view=false
请阅读此 article
中有关此功能的更多信息
我有两种情况,当从不同的上下文调用时,子实体保存的工作方式不同。
当我更新没有级联父级的子级时,修改后的数据也会更新,但这仅发生在控制器中,如果我从 CommandLineRunner
调用父级更新不会发生。
这是我的服务
@Service
public class BookService {
private final BookAuthorRepository bookAuthorRepository;
private final BookRepository bookRepository;
@Autowired
public BookService(BookAuthorRepository bookAuthorRepository, BookRepository bookRepository) {
this.bookAuthorRepository = bookAuthorRepository;
this.bookRepository = bookRepository;
}
public void updateBookAuth() {
Book book = bookRepository.findById(3).get();
book.setName("should not update");
book.getBookAuthor().setName("new name");
bookAuthorRepository.save(book.getBookAuthor());
}
}
当我从下面的 class 调用它时,只有 BookAuthor
名称被更新,这是正确的行为。
@Component
public class MyRunner implements CommandLineRunner {
@Autowired
private BookService bookService;
@Override
public void run(String... args) throws Exception {
bookService.updateBookAuth();
}
}
但是如果我从控制器调用这个服务方法:
@RestController
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping("/")
public void test() {
bookService.updateBookAuth();
}
}
然后 Book
name 和 BookAuthor
name 都更新了,但是我不明白为什么 Book
name 会更新。
以下是实体:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne
private BookAuthor bookAuthor;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public BookAuthor getBookAuthor() {
return bookAuthor;
}
public void setBookAuthor(BookAuthor bookAuthor) {
this.bookAuthor = bookAuthor;
}
}
@Entity
public class BookAuthor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne(mappedBy = "bookAuthor")
private Book book;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
我也考虑过事务性,如果我在方法上写事务,那么所有上下文都会打开,父子关系都会更新,但在这种情况下我不使用它,为什么会这样?
要真正了解发生了什么,您需要了解 Hibernate 的工作原理,尤其是 Hibernate Session。这是一篇好文章 https://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate 。在 BookService class 中,您专门为图书作者设置了新名称,并且由于此事务是会话的一部分,因此它也会更新它。 Spring Data JPA 非常有用,但您必须熟悉 Hibernate 才能完全理解其行为。
当您从存储库中获取 book 实体时,您可以检查它是否仍在管理中。为此,我们可以直接在服务中注入实体管理器,并添加如下日志语句:
@Service
@RequiredArgsConstructor
public class BookService {
private final BookBookAuthorRepository bookAuthorRepository;
private final BookRepository bookRepository;
private final EntityManager entityManager;
public void updateBookAuth() {
Book book = bookRepository.findById(1L).get();
System.out.println("----------> is managed: " + entityManager.contains(book));
book.setName("should not update");
book.getBookAuthor().setName("new name");
bookAuthorRepository.save(book.getBookAuthor());
}
}
现在,当您从 MyRunner 调用 updateBookAuth 函数时,它将打印:
----------> is managed: false
当您从 BookController 调用 updateBookAuth 函数时,它将打印:
----------> is managed: true
这意味着在其余控制器的情况下,数据库操作是在单个持久性会话中执行的,因此书籍和作者实体都已更新。
并且在命令行运行器的情况下,图书实体查询是在单独的持久性会话中执行的,之后被分离,作者实体更新是在单独的持久性会话中执行的,因为图书实体更新迷路了。
发生这种情况是因为 Spring Web 框架默认将持久性会话和请求生命周期联系在一起。像这样实现是为了防止视图层中延迟初始化关联的问题。
要防止这种行为,您可以在 application.properties 文件中使用以下设置禁用它:
spring.jpa.open-in-view=false
请阅读此 article
中有关此功能的更多信息