如何使用查询而不是带有 JPA 的 @JoinColumn 映射实体关联?

Posted

技术标签:

【中文标题】如何使用查询而不是带有 JPA 的 @JoinColumn 映射实体关联?【英文标题】:How do I map entity association using a query instead of a @JoinColumn with JPA? 【发布时间】:2015-06-16 11:25:25 【问题描述】:

我正在使用 Hibernate 4.1.3.Final 和 JPA 2.1 和 mysql 5.5.37。我有一个具有以下字段的实体:

@Entity
@Table(name = "category",
       uniqueConstraints =  @UniqueConstraint(columnNames =  "NAME" )
)
public class Category implements Serializable, Comparable<Category> 
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(generator = "uuid-strategy")
    private String id;

    @NotEmpty
    private Set<Subject> subjects;

    ...

没有简单的连接表来链接subjects 字段,而是有一个稍微复杂的 MySQL 查询。这是一个在给定特定类别 id 的情况下找出主题的示例:

SELECT DISTINCT e.subject_id 
FROM category c, resource_category rc, product_resource pr, 
     sb_product p, product_book pe, book e 
WHERE c.id = rc.category_id 
  AND rc.resource_id = pr.resource_id 
  AND pr.product_id = p.id 
  AND p.id = pe.product_id 
  AND pe.ebook_id = e.id 
  AND c.id = ‘ABCEEFGH‘;

在加载类别时使用下面的查询连接上述字段的最简单方法是什么?

这个问题涉及处理 Java 来完成此任务,因此构建视图或做一些其他类型的 MySQL 疯狂不是一种选择,至少作为这个问题的答案。

编辑:

根据建议添加了符号(将 '="ABCDEFG"' 替换为 '=id'),但当我查询与 Category 实体相关的项目时,Hibernate 会生成此无效 SQL。这是 SQL Hibernate 吐出的结果

SELECT categories0_.resource_id AS RESOURCE1_75_0_,
       categories0_.category_id AS CATEGORY2_76_0_,
       category1_.id            AS ID1_29_1_,
       category1_.NAME          AS name2_29_1_,SELECT DISTINCT e.subject_id
FROM            category c,
                resource_category rc,
                product_resource pr,
                product p,
                product_ebook pe,
                book e
WHERE           c.id = rc.category_id
AND             rc.resource_id = pr.resource_id
AND             pr.product_id = p.id
AND             p.id = pe.product_id
AND             pe.ebook_id = e.id
AND             c.id = category1_.id as formula1_1_,
                subject2_.id         AS id1_102_2_,
                subject2_.NAME       AS name2_102_2_
FROM            resource_category categories0_
INNER JOIN      category category1_
ON              categories0_.category_id=category1_.id
LEFT OUTER JOIN subject subject2_
ONSELECT DISTINCT e.subject_id
FROM            category c,
                resource_category rc,
                product_resource pr,
                product p,
                product_ebook pe,
                book e
WHERE           c.id = rc.category_id
AND             rc.resource_id = pr.resource_id
AND             pr.product_id = p.id
AND             p.id = pe.product_id
AND             pe.ebook_id = e.id
AND             c.id = category1_.id=subject2_.id
where           categories0_.resource_id=?

请注意“SELECT DISTINCT e.subject_id 上的左外连接主题 subject2_”和“AND c.id = category1_.id=subject2_.id”。

编辑 2

这是上述查询中涉及的实体

@Entity
@Table(name="resource")
public class Resource implements Serializable, Comparable<Resource>

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(generator = "uuid-strategy")
    private String id;

    …    
    @Column(name = "FILE_NAME")
    private String fileName;

    @Column(name = "URI")
    private String uri;

    …

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "resource_category", joinColumns =  @JoinColumn(name = "RESOURCE_ID") , inverseJoinColumns =  @JoinColumn(name = "CATEGORY_ID") )
    private Set<Category> categories;

这是查询本身……

CriteriaBuilder builder = m_entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(Resource.class);
Root<T> rootCriteria = criteria.from(Resource.class);
criteria.select(rootCriteria).where(builder.equal(rootCriteria.get(“uri”),uri));
Resource ret = null;
try 
        final TypedQuery<T> typedQuery = m_entityManager.createQuery(criteria);
        ret = typedQuery.getSingleResult();
 catch (NoResultException e) 
        LOG.warn(e.getMessage());

return ret; 

【问题讨论】:

【参考方案1】:

你需要使用 Hibernate 特定的JoinColumnOrFormula:

public class Category implements Serializable, Comparable<Category> 
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(generator = "uuid-strategy")
    private String id;

    @NotEmpty
    @ManyToOne
    @JoinColumnsOrFormulas(
        @JoinColumnOrFormula(
            formula = @JoinFormula(
                value = 
                    "SELECT DISTINCT e.subject_id " +
                    "FROM category c, resource_category rc, product_resource pr, " +
                    "     sb_product p, product_book pe, book e " +
                    "WHERE c.id = rc.category_id " +
                    "  AND rc.resource_id = pr.resource_id " +
                    "  AND pr.product_id = p.id " +
                    "  AND p.id = pe.product_id " +
                    "  AND pe.ebook_id = e.id " +
                    "  AND c.id = ‘ABCEEFGH‘", 
                referencedColumnName="id"
            )
        )
    )
    private Set<Subject> subjects;

    ...

或者,您可以将此查询包含在存储过程中:

CREATE FUNCTION join_book(text) RETURNS text
    AS  'SELECT DISTINCT e.subject_id ' +
        'FROM category c, resource_category rc, product_resource pr, ' +
        '     sb_product p, product_book pe, book e ' +
        'WHERE c.id = rc.category_id ' +
        '  AND rc.resource_id = pr.resource_id ' +
        '  AND pr.product_id = p.id ' +
        '  AND p.id = pe.product_id ' +
        '  AND pe.ebook_id = e.id ' +
        '  AND c.id = $1;'  
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT;

然后,您的映射变为:

public class Category implements Serializable, Comparable<Category> 
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(generator = "uuid-strategy")
    private String id;

    @NotEmpty
    @ManyToOne
    @JoinColumnsOrFormulas(
        @JoinColumnOrFormula(
            formula = @JoinFormula(
                value = "join_book(id)", 
                referencedColumnName="id"
            )
        )
    )
    private Set<Subject> subjects;

    ...

【讨论】:

嗨,我添加了你的指令(用 c.id = id 替换 c.id = 'ABCEEFGH')。但是,Hibernate 正在为我对与该实体关联的其他实体执行的查询生成无效的 SQL。我已将该 SQL 包含在我的问题的编辑中。 没有完整的映射和查询,很难分辨。 我已经添加了生成上述查询的实体和 JPA。似乎 Hibernate 确实是在将“@JoinColumnOrFormula”查询剪切并粘贴到引用该字段的位置。 这就是公式的工作原理。您使用该查询请求加入。每次需要关联时,Hibernate 都会使用它。 尝试切换到 LAZY 抓取。【参考方案2】:

SessionFactory 范围的休眠拦截器实现可能会有所帮助。我没有一个可行的例子。

【讨论】:

以上是关于如何使用查询而不是带有 JPA 的 @JoinColumn 映射实体关联?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JPA 存储库中准确搜索数字而不是使用 LIKE?

使用 concat() 时 JPA 本机查询返回字节数组而不是字符串

Spring JPA 查询始终使用序列扫描而不是索引扫描

如何使用连接定义 JPA 存储库查询

JPA CriteriaQuery 多对多谓词

如何在不使用查询缓存的情况下缓存 Spring Data JPA 查询方法的结果?