【问题标题】:JPA/HIBERNATE: How can Query return Non- Entities Objects or List of Objects with inner non-Entities objects?JPA/HIBERNATE:查询如何返回非实体对象或具有内部非实体对象的对象列表?
【发布时间】:2017-02-13 15:40:17
【问题描述】:

有没有办法将自定义对象作为另一个自定义对象的内部对象包含在内? 例如,我有两个实体:

@Entity
class Foo {
    @Id
    private int id;

    @Column
    private String a;

    @Column
    private String b;

    @OneToMany(...)
    private Set<Bar> bars;

    ...
}

@Entity
class Bar {
    @Id
    private int id;

    @Column
    private String x;

    @Column
    private String y;

    @Column
    private String z;

    ...
}

我想编写一个查询来选择一个 FooQueryResult 对象,例如:

class FooQueryResult {
    private String a;
    private Set<BarQueryResult> bars;

    ...
}

class BarQueryResult {
    private String id;
    private String x;

    ...
}

我正在尝试类似下面的东西

String query =
        "SELECT " +
                "s.a, " +
                "new package.BarQueryResult(f.bars.id, f.bars.x) " +
        "FROM Foo as f " +
        "WHERE f.id = ?1";

FooQueryResult site = factory.createEntityManager()
        .createQuery(query, FooQueryResult.class)
        .setParameter(1, fooId)
        .getSingleResult();

但这以错误结束:

QuerySyntaxException: unexpected token: , near line 1, column 14 [SELECT s.a, new package.BarQueryResult(f.bars.id, f.bars.x) FROM package.Foo as f WHERE f.id = ?1]

是否可以做到这一点以及如何做到这一点?

【问题讨论】:

  • 您是否尝试只选择new package.BarQueryResult(..) 而没有额外的s.a
  • 它引发了另一个异常:QueryException: 非法尝试使用元素属性引用 [id] 取消引用集合 [foo0_.id.bars]

标签: java hibernate jpa


【解决方案1】:

开箱即用是不可能的。你应该使用 select new 构造函数表达式,但是有一个限制——不支持嵌套构造函数调用,你不能写select new FooQueryResult(..., new BarQueryResult)
或者您可以使用您的自定义 ResultSetTransformer。但我建议创建 Transformer 类并手动完成。

【讨论】:

  • 顺便说一句,你可以将mapstruct.org用于transformers,它有助于减少样板代码的数量
【解决方案2】:

尝试删除new package.BarQueryResult(f.bars.id, f.bars.x) 之后的逗号?

【讨论】:

  • 不是这个,我正在解决一个问题并删除逗号
【解决方案3】:

请注意,存在用于操作数据库对象(实体)的 JPA/Hibernate API 结构。如果您想操作非实体,那么您最好在 JPA 之外创建自己的自定义集合。

也就是说,您的 Foo 是您的 Bar 对象(“多”侧)的父对象(“单”侧),即子对象。您可以通过执行以下四件事来获取 FooQueryResult(即 Foo 'a' 和 Bars):

1) 在您的 Bar 对象 (foo_id) 中添加一个父字段。基本上,孩子使用父母的主键 (@Id) 加入父母。

2) 在 Bar 对象上创建与父 Foo 的多对一关系,即

@ManyToOne
  @JoinColumn(name="foo_id",
    referencedColumnName = "foo_id")
  private Foo foo;

3) 将 FooQueryResult 创建为返回查询结果的对象,例如

public class FooQueryResult {
  private String a;
  private String b;
  private String x;
  private String y;
  private String z; ...

4)创建查询(这里直接在子对象中使用JPA的命名查询方法):

@Entity
@Table(name="Bar")
  @NamedQuery(name="Bar.FooQueryResult",
    query="SELECT NEW FooQueryResult("
    + "e.foo.a, e.foo.b, e.x, e.y, e.z)"
    + " FROM Bar e"
    + " WHERE e.foo.id = :foo_id") ...

最后,在您的 Session bean 中,您可以创建一个方法来返回一个结合了您的 Foo 和 Bar 对象的结果列表:

public List<FooQueryResult> myResults(int foo_id) {
    return em.createNamedQuery("Bar.FooQueryResult")
      .setParameter("foo_id", foo_id)
      .getResultList();
  }

这是另一种方法,您可以通过 5 个步骤实现您想要的:

1) 创建 Foo 和聚合 Bar 对象的类。在其中,包括两个构造函数: a) 一个用于构造 Foo 对象; b) 另一个用于构造所有对象,包括 Bars:

public class FooQueryResult {
private a;
private b;
private List<BarResult> bars;

public FooQueryResult(a, b) ...
public FooQueryResult(a, b, List<Bar> bars) ...
...

2) 创建另一个只返回 Bars 的结果对象:

public class BarResult {
    private x
    private y
    private z
    ...

3) 在 Foo 实体中,使用仅构建 Foos 的构造函数创建返回所需 Foo 对象的查询:

@Entity
@Table(name="Foo")
  @NamedQuery(name="Bar.FooQueryResult",
    query="SELECT NEW FooQueryResult("
    + "e.a, e.b)"
    + " FROM Foo e"
    ...

4) 在 Bar 实体中,创建一个返回 Foo 的所有 Bars 的查询:

@Entity
@Table(name="Bar")
  @NamedQuery(name="Bar.BarResult",
    query="SELECT NEW BarResult("
    + "e.x, e.y, e.z)"
    + " FROM Bar e"
    + " WHERE e.foo.id = :foo_id") ...

5) 最后,创建一个返回聚合的方法:

public List<FooQueryResult> myResults() {
    List<FooQueryResult> aggregate;

        for (FooQueryResult foo : em.createNamedQuery("Foo.FooQueryResult")
          .getResultList()) {

          // create a new result
          FooQueryResult query = new FooQueryResult();
// add Foos
          query.setA(foo.getA());
          query.setB(foo.getB());

          // .. then add Bars
          query.setBars(em.createNamedQuery("Bar.BarResult")
          .setParameter("foo_id", foo.id)
          .getResultList());

            // add query to aggregate
            aggregate.add(query);
          }
    return aggregate;
}

【讨论】:

  • 我知道我可以这样做。但是,我想避免编写从一种结构到另一种结构的自定义转换。这就是我想编写返回聚合嵌套对象而不是平面列表的查询的原因。你认为休眠是不可能的吗?
  • 一般来说,JPA 不支持嵌套集合关系。见这篇文章:en.wikibooks.org/wiki/Java_Persistence/…。但是,请参阅我根据使用两个查询提供结果的解决方法编辑的答案。
猜你喜欢
  • 2013-05-01
  • 2020-10-06
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多