Hibernate一对多关联中过滤同一description_key的最新版本Form
你的场景我完全理解:form_collection和form是一对多关联,每个form有version字段用于追踪版本,你希望在FormCollectionDO的forms集合里,只保留每个description_key下版本最高的记录,而且已经写出了正确的SQL查询,现在想通过Hibernate注解把这个过滤逻辑固化到实体关联里对吧?
首先得明确:你最初设想的@Where(clause = "version = SELECT max(version) FROM form GROUP BY description_key")是行不通的——这个子查询会返回多个max(version)值(每个description_key对应一个),没法直接和当前行的version做匹配,执行时会报SQL语法错误。不过有几种可行的注解方案可以实现你的需求:
方案一:用@Where配合针对当前行的子查询
这是最直接的方案,你可以在@OneToMany注解下添加@Where,但子查询要限定为当前行的description_key对应的最大版本,这样就能精准匹配到最新版本的记录。注意这里要用数据库表的列名(不是实体字段名),示例代码如下:
@Entity @Table(name = "form_collection", schema = "public") public class FormCollectionDO extends BaseDO { @OneToMany(mappedBy = "formCollection") @Where(clause = "version = (SELECT max(f.version) FROM form f WHERE f.description_key = description_key)") private List<FormDO> forms; }
这里的description_key是form表的列名,子查询会针对每一行form,找到同description_key下的最大版本,然后只保留version等于这个最大值的行。如果遇到Hibernate生成SQL时的别名冲突,可以改成:
@Where(clause = "version = (SELECT max(f.version) FROM form f WHERE f.description_key = this_.description_key)")
this_是Hibernate自动给主表(这里是form)生成的别名,能确保子查询关联的是当前行的description_key。
方案二:用@Filter和@FilterDef实现动态过滤
如果需要灵活切换(比如有时候要加载所有版本,有时候只加载最新),可以用Hibernate的过滤器机制:
- 先在
FormDO上定义过滤器:
@Entity @Table(name = "form", schema = "public") @FilterDef(name = "latestVersionFilter", parameters = @ParamDef(name = "descKey", type = "string")) @Filter(name = "latestVersionFilter", condition = "version = (SELECT max(f.version) FROM form f WHERE f.description_key = :descKey)") public class FormDO extends BaseDO { // 原有字段... @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "form_collection_id") private FormCollectionDO formCollection; }
- 然后在
FormCollectionDO的关联上启用过滤器:
@Entity @Table(name = "form_collection", schema = "public") public class FormCollectionDO extends BaseDO { @OneToMany(mappedBy = "formCollection") @Filter(name = "latestVersionFilter", condition = "description_key = :descKey") private List<FormDO> forms; }
不过这个方案需要在Session中手动启用过滤器并设置参数,比如:
session.enableFilter("latestVersionFilter").setParameter("descKey", "desc1");
如果不需要动态切换过滤规则,这个方案就不如方案一便捷。
方案三:映射到自定义视图(最稳定的方式)
如果觉得注解子查询不够直观,你可以先基于已有的SQL创建一个数据库视图:
CREATE VIEW latest_form AS SELECT a.* FROM form AS a INNER JOIN ( SELECT description_key, max(version) AS version FROM form GROUP BY description_key ) AS b ON a.description_key = b.description_key AND a.version = b.version;
然后创建一个LatestFormDO实体映射到这个视图:
@Entity @Table(name = "latest_form", schema = "public") public class LatestFormDO extends BaseDO { // 和FormDO完全一致的字段定义 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "form_collection_id") private FormCollectionDO formCollection; }
最后修改FormCollectionDO的关联:
@Entity @Table(name = "form_collection", schema = "public") public class FormCollectionDO extends BaseDO { @OneToMany(mappedBy = "formCollection") private List<LatestFormDO> latestForms; }
这个方案的优势是逻辑清晰,过滤规则直接在视图中定义,Hibernate只需要映射视图即可,不会有注解子查询的兼容性问题。
需要注意的是:如果使用方案一或二,当你更新form的version字段后,Hibernate的一级缓存可能不会自动刷新,建议在更新操作后手动清空缓存或者重新查询。
内容的提问来源于stack exchange,提问作者simibac

