JPA:我如何将 属性 映射到基于其他表中的 COUNT 的派生列(不会产生 n+1 select 问题?)
JPA: how can i map a property to a derived column that is based on a COUNT from other tables (Without incurring in n+1 select problems?)
这些是我的项目中定义的实体:
Post
@Entity
public class Post implements Serializable {
public enum Type {TEXT, IMG}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "section_id")
protected Section section;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "author_id")
protected User author;
@Column(length = 255, nullable = false)
protected String title;
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
protected Type type;
@CreationTimestamp
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
@Column(insertable = false, updatable = false)
protected Integer votes;
/*accessor methods*/
}
评论
@Entity
public class Comment implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "post_id")
protected Post post;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "author_id")
protected User author;
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "parent_comment_id")
protected Comment parentComment;
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@CreationTimestamp
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
@Column(insertable = false, updatable = false)
protected Integer votes;
@Column(insertable = false, updatable = false)
protected String path;
/*accessor methods*/
}
如您所见,仅从 child 的角度跟踪聚合:由于设计选择,没有 @ManyToOne
关系。
但是,我想向 Post
实体添加一个 commentCount
字段。这个字段应该被急切地加载(因为它总是在我的用例中被读取并且 COUNT
语句不是 that 昂贵......我猜)
这些是我目前知道的选项:
使用@Formula
或@PostLoad
注解:会有N+1
select语句
检索帖子,然后调用另一个存储库方法,该方法使用 IN
运算符并将所有检索到的帖子作为参数:
select post, count(comment) from Post post, Comment comment where comment.post in (:posts) group by comment.post
(鉴于我的数据访问层可能一次加载甚至 50 个帖子,50 个参数长 in
是否是最优的?)
创建一个 non-mapped 实体属性,然后在涉及 Post
和 join
的所有存储库方法中手动填充它:我想避免这种情况。
在数据库中存储commentCount
并用触发器维护它:可能是一个选项,但这不是这个问题的重点(我不允许触摸架构)
什么是有效的解决方案?我正在使用 Hibernate,但我更喜欢 implementation-agnostic 解决方案(不过 Implementation-specific 选项不会被忽略)
使用 @Formula
不会导致任何 N+1
select 问题(不同于 @PostLoad
),因为指定的表达式是作为初始 [=25] 的一部分计算的=] 查询.
(事实上,您需要启用 Hibernates 字节码增强才能 lazy-load 计算的 @Formula 属性: Controlling lazy/eager loading of @Formula columns dynamically )
因此,即使它是一个需要原生 SQL 的 Hibernate-specific 功能,@Formula
可能是实现这一点的好而简单的方法。
如果相关子查询的性能缺陷变得很明显,并且在某些情况下不需要计算 属性,您可以另外创建一个仅包含所需属性的 DTO 投影。
这些是我的项目中定义的实体:
Post
@Entity
public class Post implements Serializable {
public enum Type {TEXT, IMG}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "section_id")
protected Section section;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "author_id")
protected User author;
@Column(length = 255, nullable = false)
protected String title;
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
protected Type type;
@CreationTimestamp
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
@Column(insertable = false, updatable = false)
protected Integer votes;
/*accessor methods*/
}
评论
@Entity
public class Comment implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "post_id")
protected Post post;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "author_id")
protected User author;
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "parent_comment_id")
protected Comment parentComment;
@Column(columnDefinition = "TEXT", nullable = false)
protected String content;
@CreationTimestamp
@Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
@Column(insertable = false, updatable = false)
protected Integer votes;
@Column(insertable = false, updatable = false)
protected String path;
/*accessor methods*/
}
如您所见,仅从 child 的角度跟踪聚合:由于设计选择,没有 @ManyToOne
关系。
但是,我想向 Post
实体添加一个 commentCount
字段。这个字段应该被急切地加载(因为它总是在我的用例中被读取并且 COUNT
语句不是 that 昂贵......我猜)
这些是我目前知道的选项:
使用
@Formula
或@PostLoad
注解:会有N+1
select语句检索帖子,然后调用另一个存储库方法,该方法使用
IN
运算符并将所有检索到的帖子作为参数:select post, count(comment) from Post post, Comment comment where comment.post in (:posts) group by comment.post
(鉴于我的数据访问层可能一次加载甚至 50 个帖子,50 个参数长
in
是否是最优的?)创建一个 non-mapped 实体属性,然后在涉及
Post
和join
的所有存储库方法中手动填充它:我想避免这种情况。在数据库中存储
commentCount
并用触发器维护它:可能是一个选项,但这不是这个问题的重点(我不允许触摸架构)
什么是有效的解决方案?我正在使用 Hibernate,但我更喜欢 implementation-agnostic 解决方案(不过 Implementation-specific 选项不会被忽略)
使用 @Formula
不会导致任何 N+1
select 问题(不同于 @PostLoad
),因为指定的表达式是作为初始 [=25] 的一部分计算的=] 查询.
(事实上,您需要启用 Hibernates 字节码增强才能 lazy-load 计算的 @Formula 属性: Controlling lazy/eager loading of @Formula columns dynamically )
因此,即使它是一个需要原生 SQL 的 Hibernate-specific 功能,@Formula
可能是实现这一点的好而简单的方法。
如果相关子查询的性能缺陷变得很明显,并且在某些情况下不需要计算 属性,您可以另外创建一个仅包含所需属性的 DTO 投影。