繁体   English   中英

存储库保存的工作方式与 controller 或 spring 启动中的命令行运行程序不同

[英]repository save works differently from controller or commandlinerunner in spring boot

我有两种情况,当从不同的上下文调用时,子实体保存的工作方式不同。 当我更新没有级联父级的子级时,修改后的数据也会更新,但这仅发生在 controller 中,如果我从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();
    }
}

但是如果我从 controller 调用这个服务方法:

@RestController
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping("/")
    public void test() {
        bookService.updateBookAuth();
    }
}

然后BookBookAuthor都更新了,但我不明白为什么Book会更新。

以下是实体:

@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;
    }
}

我也考虑过事务性,如果我在方法上编写事务,那么所有上下文都会打开,并且父子都会更新,但在这种情况下我不使用它,为什么会这样?

当您从存储库中获取图书实体时,您可以检查它是否仍处于托管状态。 为此,我们可以直接在服务中注入实体管理器并添加如下日志语句:

    @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 function 时,它将打印:

----------> 被管理:假

当您从BookController调用updateBookAuth function 时,它将打印:

----------> 被管理:true

这意味着在 rest controller 的情况下,数据库操作在单个持久性 session 中执行,因此更新了书籍和作者实体。

在命令行运行器的情况下,书籍实体查询在单独的持久性 session 中执行,然后分离,作者实体更新在单独的持久性 session 中执行,因为该书籍实体更新丢失。

这是因为 Spring web 框架默认将持久性 session 和请求生命周期联系在一起。 这样做是为了防止视图层中延迟初始化关联的问题。

为了防止这种行为,您可以使用application.properties文件中的以下设置禁用它:

spring.jpa.open-in-view=false

请在本文中阅读有关此功能的更多信息

要真正了解发生了什么,您需要了解 Hibernate 的工作原理,尤其是 Hibernate Session。 这是一篇好文章https://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate 在 BookService class 中,您专门为图书作者设置了新名称,并且由于此事务是 session 的一部分,因此它也会对其进行更新。 Spring 数据 JPA 非常有用,但您必须熟悉 Hibernate 才能完全理解其行为。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM