Spring Boot中如何使用JPA的entityManager.getReference?
嘿,这个场景我太熟悉了——明明手里握着关联实体的主键,却还要多做两次查库操作,完全是没必要的开销!下面给你几个Spring Boot里的可行方案,帮你省掉这些冗余查询:
方案1:直接用EntityManager的getReference()
EntityManager的getReference()方法就是专门干这个的——它会返回一个实体的代理对象,不会立刻触发数据库查询,只有当你访问代理的非主键属性时才会真正查库。而我们保存关联关系只需要主键,刚好完美匹配。
步骤很简单:
- 在你的业务类(比如Service)里用
@PersistenceContext注入EntityManager - 用主键直接生成关联实体的代理对象,然后赋值给StudentSubject就行
代码示例:
@Service public class StudentSubjectService { @PersistenceContext private EntityManager entityManager; private final StudentSubjectRepository studentSubjectRepo; // 构造注入(推荐的Spring依赖注入方式) public StudentSubjectService(StudentSubjectRepository studentSubjectRepo) { this.studentSubjectRepo = studentSubjectRepo; } public void saveStudentSubject(String studentId, Long subjectId) { // 生成Student代理对象,不查库 Student studentProxy = entityManager.getReference(Student.class, studentId); // 生成Subject代理对象,不查库 Subject subjectProxy = entityManager.getReference(Subject.class, subjectId); StudentSubject studentSubject = new StudentSubject(); studentSubject.setStudentId(studentId); studentSubject.setStudent(studentProxy); studentSubject.setSubject(subjectProxy); studentSubjectRepo.save(studentSubject); } }
方案2:用Spring Data JPA的getReferenceById()(更优雅)
如果你不想直接操作EntityManager,Spring Data JPA给CrudRepository提供了getReferenceById()方法(旧版本可能是getOne(),不过现在getOne()已经被废弃了,推荐用前者),它本质上就是封装了EntityManager的getReference()。
这个方案更符合Spring Data的风格,只需要注入对应的Repository就行:
@Service public class StudentSubjectService { private final StudentSubjectRepository studentSubjectRepo; private final StudentRepository studentRepo; private final SubjectRepository subjectRepo; public StudentSubjectService(StudentSubjectRepository studentSubjectRepo, StudentRepository studentRepo, SubjectRepository subjectRepo) { this.studentSubjectRepo = studentSubjectRepo; this.studentRepo = studentRepo; this.subjectRepo = subjectRepo; } public void saveStudentSubject(String studentId, Long subjectId) { // 用StudentRepository获取代理对象 Student studentProxy = studentRepo.getReferenceById(studentId); // 用SubjectRepository获取代理对象 Subject subjectProxy = subjectRepo.getReferenceById(subjectId); StudentSubject studentSubject = new StudentSubject(); studentSubject.setStudentId(studentId); studentSubject.setStudent(studentProxy); studentSubject.setSubject(subjectProxy); studentSubjectRepo.save(studentSubject); } }
方案3:直接用自定义JPQL语句插入(适合简单场景)
如果你的StudentSubject没有太多复杂的业务逻辑,也可以直接写一个自定义的插入查询,跳过实体对象的创建,直接用主键关联:
public interface StudentSubjectRepository extends CrudRepository<StudentSubject, Long> { @Modifying @Transactional @Query("INSERT INTO StudentSubject ss (ss.studentId, ss.student.id, ss.subject.id) VALUES (:studentId, :studentId, :subjectId)") void saveByPrimaryKeys(@Param("studentId") String studentId, @Param("subjectId") Long subjectId); }
注意这里需要加上@Modifying和@Transactional注解,而且JPQL语句要和你的实体字段映射完全对应。
关键原理说明
不管用哪种方案,核心都是避免触发不必要的SELECT查询:getReference()返回的代理对象只持有主键信息,Hibernate在保存关联关系时,只需要这个主键就能完成外键的关联,根本不需要去查库获取完整的实体数据。
当然,如果之后你需要访问Student或Subject的其他属性(比如student.getName()),代理对象会自动触发查询去加载完整数据,但如果只是用来保存关联,完全不会有额外的数据库开销。
内容的提问来源于stack exchange,提问作者Registered User

