Hibernate 查询:通过映射键连接

Hibernate query: join by map key

我有以下 classes:

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的一个实例class。

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

org.hibernate.QueryException: could not resolve property: language of: 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: ERROR: invalid input syntax for type integer: "language" Position: 235

和交叉连接!

好吧,去掉任何条件,只有服务+所有元数据属于它,看看它是如何构建的。这是一个序列化为 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"
        }
    }

正如你在这里看到的,metaData 由两个对象组成(实际上是 PersistentMap with key = Language)。

所以,问题是:如何使用 Hibernate 按语言检索特定的映射值?

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

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

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 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

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

@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 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features