java - JPA 与共享主键的单向@OneToOne 关系始终触发辅助查询,即使 fetchType 为 EAGER
问题描述
我正在构建一个博客系统,并希望为博客提供 upvote/downvote 功能。由于博客的投票数应该被持久化,所以我选择使用 MySQL 作为数据存储。我使用 Spring JPA(Hibernate) 来完成 ORM 工作。这是我的数据对象:
class Blog{
// ...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(optional = false, fetch = FetchType.EAGER)
@PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
}
和柜台类:
@Entity
public class BlogVoteCounter extends ManuallyAssignIdEntitySuperClass<Long> {
@Id
private Long id;
private Integer value;
}
BlogVoteCounter
我将from分开的原因Blog
是我认为与voteCount
博客的其他字段相比,该字段将被完全不同的频率修改,因为我想使用缓存来缓存Blog
,按照本指南,我选择将它们分开。
但是,由于在VoteCount
将 Blog 对象返回到前端时可能总是需要该字段,并且为了避免 n+1 问题,我BlogVoteCounter
在 Blog 类中声明了 EAGER 获取类型的字段。
我已经看过这篇文章了。因此根据我个人的理解,我使用单向关系并且只OneToOne
在Blog
侧面声明。
但是,当我检查查询时,发现 jpa 仍然会触发辅助查询以从数据库中检索,而无需在使用方法 onBlogVoteCounter
时简单地使用连接。findAll
BlogRepository
select
blogvoteco0_.id as id1_2_0_,
blogvoteco0_.value as value2_2_0_
from
blog_vote_counter blogvoteco0_
where
blogvoteco0_.id=?
那么我应该如何配置,总是让BlogVoteCounter
字段Blog
被热切地获取。
的用法ManuallyAssignIdEntitySuperClass
遵循 Spring JPA doc,因为我手动为BlogVoteCounter
类分配 id。
@MappedSuperclass
public abstract class ManuallyAssignIdEntitySuperClass<ID> implements Persistable<ID> {
@Transient
private boolean isNew = true;
@Override
public boolean isNew() {
return isNew;
}
@PrePersist
@PostLoad
void markNotNew(){
this.isNew = false;
}
}
并且BlogRepository
源自JpaRepository
public interface BlogRepository extends JpaRepository<Blog, Long>{
// ...
}
我通过 using 方法触发查询findAll
,但 usingfindById
或其他条件查询似乎没有区别。
解决方案
When to fetch
vsHow to fetch
:fetchType
定义何时获取关联(instantly
vslater when someone access
)关联,但不定义如何获取关联(即第二个选择与连接查询)。因此,从 JPA 规范的角度来看,EAGER 意味着不要等到有人访问该字段来填充它,但 JPA 提供者可以自由使用 JOIN 或第二次选择,只要他们立即执行。
尽管他们可以自由地使用 join vs second select,但我仍然认为他们应该在 EAGER 的情况下为 join 进行优化。非常有兴趣找出不使用连接的逻辑推理
1. 生成的查询repository.findById(blogId);
select
blog0_.id as id1_0_0_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.id as id1_1_1_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
where
blog0_.id=?
2.更新映射
public class Blog {
@Id
private Long id;
@ManyToOne(optional = false, cascade = ALL, fetch = FetchType.EAGER)
@PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
public Blog() {
}
public Blog(Long id, BlogVoteCounter voteCounter) {
this.id = id;
this.voteCounter = voteCounter;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BlogVoteCounter getVoteCounter() {
return voteCounter;
}
public void setVoteCounter(BlogVoteCounter voteCounter) {
this.voteCounter = voteCounter;
}
}
3. 当前映射的问题
- 根据您的映射,无法创建
blog
,votecounter
因为它会导致chicken and egg
问题。IE - blog 和 votecounter 需要共享同一个主键
- 博客的主键是由数据库生成的。
- 所以为了获取博客的主键并将其分配给投票计数器,您需要先存储博客
- 但是@OneToOne 关系不是可选的,所以你不能先单独存储博客
4.变化
- 要么需要使关系可选,因此可以先存储博客,获取 id,分配给 BlogVoteCounter 并保存计数器
- 或者不要自动生成 ID 并手动分配 ID,以便可以同时保存博客和投票计数器。(我已经选择了这个选项,但你可以做第一个选项)
5.注意事项
- 默认
repository.findAll
是生成 2 个查询,所以我重写了该方法以生成一个连接查询
public interface BlogRepository extends JpaRepository<Blog, Long> {
@Override
@Query("SELECT b from Blog b join fetch b.voteCounter ")
List<Blog> findAll();
}
select
blog0_.id as id1_0_0_,
blogvoteco1_.id as id1_1_1_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
推荐阅读
- macos - gdb:没有可用的瓶子-gdb install
- python - 如何将不同长度的列表字典写入csv?
- latex - 如何通过 Latex 在 R 中将文本居中
- html - 使用媒体查询按屏幕大小调整文本大小
- html - 图像未相对于其父级调整大小
- qt - 如何使从 qml 组件初始化的 QWidget 的背景透明?
- python - 如何在没有 Context.invoke 或 Context.forward 的情况下“自调用”python-click CLI 命令?
- python - python中的Numba用于更快的哈希
- java - 如何部分重绘画布的非矩形部分
- java - 保护 Java 应用程序免受 SQL 注入