JPA实体生命周期回调中JpaRepository使用及事务创建问题
问题描述
我现在遇到三个JPA相关的问题:
- 如何在JPA实体生命周期回调方法中使用JpaRepository?
- 如何在@PostUpdate JPA实体生命周期方法中创建新事务?
- 无法在生命周期方法中通过@Autowire注入AuthorRepository,它始终返回null。
相关代码如下:
实体类 Author.java
package com.bookstore.entity; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Author implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String genre; private int age; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGenre() { return genre; } public void setGenre(String genre) { this.genre = genre; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Author{" + "id=" + id + ", name=" + name + ", genre=" + genre + ", age=" + age + '}'; } }
AuthorRepository.java
@Repository public interface AuthorRepository extends JpaRepository<Author, Long> { @Transactional(readOnly=true) Author findByName(String name); }
BookListener.java(生命周期监听器)
@Component public class BookListener { @Autowired private AuthorRepository authorRepository; @PrePersist void onPrePersist(Book book) { System.out.println("BookListener.onPrePersist(): " + book); } @PostPersist void onPostPersist(Book book) { System.out.println("BookListener.onPostPersist(): " + book); } @PostLoad void onPostLoad(Book book) { System.out.println("BookListener.onPostLoad(): " + book); } @PreUpdate void onPreUpdate(Book book) { System.out.println("BookListener.onPreUpdate(): " + book); } @PostUpdate void onPostUpdate(Book book) { System.out.println("BookListener.onPostUpdate(): " + book); // authorRepository.findAll() // 此处调用会触发NullPointerException } @PreRemove void onPreRemove(Book book) { System.out.println("BookListener.onPreRemove(): " + book); System.out.println(authorRepository.count()); // 同样会触发NullPointerException } @PostRemove void onPostRemove(Book book) { System.out.println("BookListener.onPostRemove(): " + book); } }
BookstoreService.java
import com.bookstore.repository.AuthorRepository; import com.bookstore.entity.Author; import com.bookstore.entity.Paperback; import com.bookstore.entity.Ebook; import com.bookstore.repository.EbookRepository; import java.util.Optional; import org.springframework.stereotype.Service; import com.bookstore.repository.PaperbackRepository; import org.springframework.transaction.annotation.Transactional; @Service public class BookstoreService { private final AuthorRepository authorRepository; private final PaperbackRepository paperbackRepository; private final EbookRepository ebookRepository; public BookstoreService(AuthorRepository authorRepository, PaperbackRepository paperbackRepository, EbookRepository ebookRepository) { this.authorRepository = authorRepository; this.paperbackRepository = paperbackRepository; this.ebookRepository = ebookRepository; } @Transactional public void persistAuthorWithBooks() { Author author = new Author(); author.setName("Alicia Tom"); author.setAge(38); author.setGenre("Anthology"); Paperback paperback = new Paperback(); paperback.setIsbn("002-AT"); paperback.setTitle("The beatles anthology"); paperback.setSizeIn("7.5 x 1.3 x 9.2"); paperback.setWeightLbs("2.7"); paperback.setAuthor(author); Ebook ebook = new Ebook(); ebook.setIsbn("003-AT"); ebook.setTitle("Anthology myths"); ebook.setFormat("kindle"); ebook.setAuthor(author); authorRepository.save(author); paperbackRepository.save(paperback); ebookRepository.save(ebook); } @Transactional public void update() { Optional<Author> optional = authorRepository.findById(1L); if(optional.isPresent()) { Author author = optional.get(); author.setName("Johnn Doe"); authorRepository.save(author); } } @Transactional public void fetchAndRemovePaperback() { Paperback paperback = paperbackRepository .findByTitle("The beatles anthology"); paperbackRepository.delete(paperback); } @Transactional public void fetchAndRemoveEbook() { Ebook ebook = ebookRepository .findByTitle("Anthology myths"); ebookRepository.delete(ebook); } }
解决方案
1. 解决@Autowired注入为null的问题
你的BookListener加了@Component但无法注入,核心原因是JPA原生实体监听器由JPA提供者(如Hibernate)实例化,不受Spring容器管理,因此Spring的依赖注入机制无法生效。
有两种主流解决方案:
方案A:改用Spring事务事件监听(推荐)
Spring提供的@TransactionalEventListener可以监听实体生命周期的事务事件,这类监听器由Spring容器管理,天然支持依赖注入。
修改后的监听器示例:
@Component public class BookEventListener { @Autowired private AuthorRepository authorRepository; @Autowired private BookTransactionService transactionService; // 替代@PostUpdate,监听Book更新后的事务提交事件 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onBookUpdated(Book book) { System.out.println("Book更新完成,事务已提交: " + book); // 此处可正常调用Repository authorRepository.findAll(); // 调用新事务逻辑 transactionService.executeInNewTransaction(); } // 其他生命周期事件示例 @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void onBookBeforeUpdate(Book book) { System.out.println("Book即将更新,事务提交前: " + book); } }
方案B:通过Spring上下文手动获取Bean(兼容原生JPA监听器)
如果必须使用JPA原生监听器,可以创建一个Spring上下文工具类,手动获取Repository实例:
@Component public class SpringBeanHelper implements ApplicationContextAware { private static ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } public static <T> T getBean(Class<T> clazz) { return ctx.getBean(clazz); } }
然后修改你的BookListener:
// 注意:此处不需要@Component,因为JPA会实例化它 public class BookListener { private final AuthorRepository authorRepository; public BookListener() { // 通过工具类获取Spring管理的Repository this.authorRepository = SpringBeanHelper.getBean(AuthorRepository.class); } @PostUpdate void onPostUpdate(Book book) { System.out.println("BookListener.onPostUpdate(): " + book); authorRepository.findAll(); // 现在可正常使用 } }
记得在Book实体上添加@EntityListeners(BookListener.class)注解,让JPA识别这个监听器。
2. 在生命周期回调中使用JpaRepository
解决注入问题后,直接调用JpaRepository的方法即可,但需要注意事务上下文:
- JPA原生回调默认在当前事务中执行,比如
@PostUpdate会在更新事务的提交阶段触发,此时Repository操作会参与当前事务。 - 使用Spring的
@TransactionalEventListener时,可以通过phase参数控制事件触发时机(如BEFORE_COMMIT/AFTER_COMMIT/AFTER_ROLLBACK)。
3. 在@PostUpdate中创建新事务
JPA原生回调方法无法直接通过@Transactional开启新事务,因为这些方法不是Spring调用的。需要将新事务逻辑抽离到Spring管理的Service方法中,并给该方法添加@Transactional(propagation = Propagation.REQUIRES_NEW)注解。
示例Service:
@Service public class BookTransactionService { @Autowired private AuthorRepository authorRepository; @Transactional(propagation = Propagation.REQUIRES_NEW) public void executeInNewTransaction() { // 此方法会在独立的新事务中执行 long count = authorRepository.count(); System.out.println("新事务中查询作者数量: " + count); } }
然后在监听器中调用该方法即可,不管是Spring事件监听器还是原生JPA监听器,只要能获取到这个Service实例就行。
内容的提问来源于stack exchange,提问作者PAA
相关产品推荐
相关产品推荐

