首页 > 解决方案 > 休眠查询:通过映射键加入

问题描述

我有以下课程:

public abstract class Entity<T> implements Serializable {
    protected long id;
    protected Map<Language,EntityMetaData> metaData;
...
public class Service extends Entity<Long> implements Serializable {
    protected String name;

XML 映射:

<class name="Service" table="SERVICES" schema="EDRIVE" dynamic-update="false" dynamic-insert="true" batch-size="30">
    <meta attribute="class-description">
        This class contains definitions of relations between entities
    </meta>
    <id name="id" type="long" column="id">
        <generator class="identity" />
    </id>
    <map name="metaData" table="ServiceMetaData">
        <key column="serviceid"/>
        <map-key-many-to-many column="languageId" class="com.core.domain.Language"/>
        <one-to-many entity-name="serviceMetaData"/>
    </map>
</class>

<class name="EntityMetaData" entity-name="serviceMetaData" table="SERVICESMETADATA" lazy="false">
    <id name="id" type="long" column="id" access="field">
        <generator class="identity" />
    </id>
    <property name="name" column="name" type="string" length="256" />
    <property name="description" column="description" type="string" length="512"/>
</class>

我只想为特定语言加载 serviceMetadata,即 ru、en、uk 等:

Service serviceEntity = (Service) databaseUtilities
                .getSession()
                .createQuery("FROM Service s"
                        + " JOIN FETCH s.metaData m"
                        + " WHERE s.id = :id AND m.language = :language")
                .setParameter("id", serviceId)
                .setParameter("language", language)
                .uniqueResult();

其中 serviceId 很长,language 是 Language 类的一个实例。

当我尝试上述方法时,出现以下异常:

org.hibernate.QueryException:无法解析属性:语言:serviceMetaData [FROM com.core.domain.service.Service s JOIN FETCH s.metaData m WHERE s.id = :id AND m.language = :language]

在其他情况下:

Service serviceEntity = (Service) databaseUtilities
                .getSession()
                .createQuery("FROM Service s"
                        + " WHERE s.id = :id AND s.metaData['language'] = :language")
                .setParameter("id", serviceId)
                .setParameter("language", language)
                .uniqueResult();

例外是

org.postgresql.util.PSQLException:错误:整数类型的输入语法无效:“语言”位置:235

并交叉加入!

好吧,去掉任何条件,只有service+所有元数据属于它,看它是如何构造的。这是一个序列化为 JSON 结果集:

"service": {
        "id": 5,
        "entityType": null,
        "entityKey": null,
        "metaData": {
            "com.core.domain.Language@57d41bfc": {
                "id": 2145,
                "entityType": null,
                "entityKey": null,
                "metaData": null,
                "miscMetaData": null,
                "name": "Заправка топливом",
                "description": null
            },
            "com.core.domain.Language@99ca0b20": {
                "id": 2144,
                "entityType": null,
                "entityKey": null,
                "metaData": null,
                "miscMetaData": null,
                "name": "Fueling",
                "description": null
            }
        },
        "miscMetaData": null,
        "name": "FUELING",
        "serviceType": {
            "id": 23,
            "entityType": null,
            "entityKey": null,
            "metaData": null,
            "miscMetaData": null,
            "value": "RECURRENT"
        }
    }

正如您在此处看到的,元数据由两个对象组成(实际上是带有 key = Language 的 PersistentMap)。

所以,问题是:如何使用 Hibernate 通过语言检索特定的地图值?

标签: hibernatedictionaryjoinkey

解决方案


JPA 要求托管实体和数据库的状态必须在事务结束时保持同步,因此仅获取实体的部分集合实际上会删除未获取的元素,这就是 JPA 不允许这样做的原因。但是,您可以使用 Hibernate 执行此操作,但我不推荐它,因为它可能导致删除。

所以要得到你想要的,你需要编写一个 HQL 查询并将数据提取到 DTO 中。在查询中,您可以选择您需要的所有字段,并且可以根据需要定义连接条件。类似于以下内容:

SELECT s.id, s.name, m.id, m.name
FROM Service s
LEFT JOIN s.metadata m ON KEY(m) = 'en'

我认为这是Blaze-Persistence Entity Views的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(Service.class)
public interface ServiceDto {
    @IdMapping
    Long getId();
    String getName();
    @Mapping("metadata['en']")
    LanguageDto getLanguage();

    @EntityView(EntityMetaData.class)
    interface LanguageDto {
        @IdMapping
        Long getId();
        String getName();
    }

    // Other mappings
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

ServiceDto a = entityViewManager.find(entityManager, ServiceDto.class, id);

Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features


推荐阅读