Why state of entity returned from JpaRepository in normal thread and async thread are different

Viewed 135

I used to think that any entities returned by a JpaRepository will be in DETACH state because in Spring the default PersistentContextType is TRANSACTION and when we get the returned values at the caller of a JpaRepository, the transaction used to get those entities has been closed.

But recently, I unexpectedly found down that it depend. I have this sample code.

@Service
@RequiredArgsConstructor
public class BookService {
    private final BookRepository bookRepository;
    private final UpdateService updateService;
    @PersistenceContext
    private final EntityManager entityManager;

    public Book updateAuthor(String bookId, String author) {
        Book book = bookRepository.findById(bookId).get();
        entityManager.contains(book); // true
        Book updateBook = updateService.updateAuthorTransactional(book, author);
        return updateBook;
    }

    @Async
    public Book updateAuthorAsync(String bookId, String author) {
        Book book = bookRepository.findById(bookId).get();
        entityManager.contains(book); // false
        Book updatedBook =  updateService.updateAuthorTransactional(book, author);
        return updatedBook;
    }

    public void updateAuthorExecutor(String bookId, String author) {
        Executors.newFixedThreadPool(1).submit(() -> {
            Book book = bookRepository.findById(bookId).get();
            entityManager.contains(book); // false
            Book updateBook = updateService.updateAuthorTransactional(book, author);
            return updateBook;
        });
    }
}
@Service
@RequiredArgsConstructor
public class UpdateService {

    @Transactional
    public Book updateAuthorTransactional(Book book, String author) {
        book.setAuthor(author);
        return book;
    }
}

The BookRepository is a simple empty interface which extends JpaRepositoty. Book is a entity with 3 fields: id, title, author. I use H2 to init 2 records of Book using this

insert into `test`.`book` values ( '101', 'Book101', null );
insert into `test`.`book` values ( '102', 'Book102', null );

When I trigger method BookService.updateAuthor(), the state of returned entity is PERSISTED. I conclude that because EntityManager.contain() return true and after the method UpdateService.updateAuthorTransactional() is executed, the transaction is closed, the persistent context is flushed and the new value of author is updated to database.

But this isn't the case with other two methods in BookService. The EntityManager.contain() return false and the new value of author is not updated to database.

So I wonder

  1. What makes the difference behind the scene here?
  2. Which persistent context stores the book entity in updateAuthor() method? What is about its life-cycle like when it is initialized, closed?
  3. I used to think that when the transaction close, all entities in the persistent context will be transitioned to DETACH state. Is this still correct? Is there something different with Spring Data Jpa?

Update: Look like the web container has created a persistent context somewhere for the main thread, and all repository method call in main thread will share this same persistent context. In case of async thread, there is no such persistent context for the thread. It will be created and closed by proxied Jpa repository method. This result into what i get. Just my guess from the phenomenon, I can not find any document to prove this.

0 Answers
Related