【问题标题】:How to construct this join query with JPA API?如何使用 JPA API 构造此连接查询?
【发布时间】:2019-09-03 14:04:49
【问题描述】:

两个表:

CREATE TABLE `foo` (
  `foo_id` bigint(20)  not null auto_increment,
  `name` varchar(32) not null,
  `_deleted_` tinyint(1) default '0',
   PRIMARY KEY (`foo_id`)
) ;

CREATE TABLE `bar` (
  `bar_id` bigint(20)  not null auto_increment,
   `foo_id` bigint(20)  not null,
   `key` varchar(32) not null,
   `value` varchar(125) not null,
   `_deleted_` tinyint(1) default '0',
   PRIMARY KEY (`bar_id`)
);

表格内容:

select * from foo;
+--------+-------+-----------+
| foo_id | name  | _deleted_ |
+--------+-------+-----------+
|      1 | cat   |         0 |
|      2 | dog   |         0 |
|      3 | mouse |         0 |
|      4 | rat   |         1 |
+--------+-------+-----------+
3 rows in set (0.00 sec)

select * from bar;
+--------+--------+-------+--------+-----------+
| bar_id | foo_id | key   | value  | _deleted_ |
+--------+--------+-------+--------+-----------+
|      1 |      1 | sound | meow   |         0 |
|      2 |      1 | ears  | pointy |         0 |
|      3 |      2 | sound | ruff   |         0 |
|      4 |      2 | nose  | long   |         0 |
|      5 |      3 | sound | squeak |         0 |
|      6 |      3 | tail  | long   |         0 |
|      7 |      3 | legs  | two    |         1 |
+--------+--------+-------+--------+-----------+
6 rows in set (0.00 sec)

我要创建的查询:

select f.foo_id, f.name, b.key, b.value from foo f, bar b 
  where f.foo_id = b.foo_id and f._deleted_ = 0 and b._deleted_ = 0;

+--------+-------+-------+--------+
| foo_id | name  | key   | value  |
+--------+-------+-------+--------+
|      1 | cat   | sound | meow   |
|      1 | cat   | ears  | pointy |
|      2 | dog   | sound | ruff   |
|      2 | dog   | nose  | long   |
|      3 | mouse | sound | squeak |
|      3 | mouse | tail  | long   |
+--------+-------+-------+--------+
6 rows in set (0.01 sec)

Foo 类:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Entity(name = "foo")
public class Foo {

  @Id
  @Column(name = "foo_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long fooId;
  private String name;
  @Column(name = "_deleted_")
  private Short deleted;

  @OneToMany
  @JoinTable(name="bar",
      joinColumns=@JoinColumn(name="foo_id"))
  private List<Bar> bars;
}

酒吧类:

@Data 
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Entity(name = "bar")
public class Bar {

  @Id
  @Column(name = "bar_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long barId;
  private Long fooId;
  private String key;
  private String value;
  @Column(name = "_deleted_")
  private Short deleted;
}

尝试加入他们:

protected Stream<Foo> getFoosWithBars() {
  return this.jpaApi.withTransaction(entityManager -> {
    final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    final CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
    Root<Foo> fromFoo = criteria.from(Foo.class);
    Join<Foo, Bar> foobars = fromFoo.join("fooId");
    List<Predicate> conditions = new ArrayList();
    conditions.add(builder.notEqual(fromFoo.get("deleted"), 1));
    #  what goes here?
    conditions.add(builder.notEqual(???Bar???.get("deleted"), 1));

    TypedQuery<Foo> typedQuery = entityManager.createQuery(criteria
        .select(fromFoo)
      .where(conditions.toArray(new Predicate[] {})));
    return typedQuery.getResultList().stream();
  });
}

【问题讨论】:

    标签: java jpa join


    【解决方案1】:

    您在此 JPA 查询中缺少表的联接条件,并使用联接对象来匹配 Bar 对象的条件。看看这个查询,如果您有任何进一步的查询,请告诉我。

    protected Stream<Foo> getFoosWithBars() {
      return this.jpaApi.withTransaction(entityManager -> {
        final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
        Root<Foo> fromFoo = criteria.from(Foo.class);
        Join<Foo, Bar> foobars = (Join<Foo, Bar>) fromFoo.fetch("fooId");
    
        List<Predicate> conditions = new ArrayList();
        conditions.add(builder.equal(fromFoo.get("fooId"),foobars.get("fooId"))); // You are missing join Condition
        conditions.add(builder.equal(fromFoo.get("deleted"), 0));
        conditions.add(builder.equal(foobars.get("deleted"), 0));
    
        TypedQuery<Pod> typedQuery = entityManager.createQuery(criteria.select(fromFoo)
          .where(conditions.toArray(new Predicate[] {})));
        return typedQuery.getResultList().stream();
      });
    }
    

    【讨论】:

    • 谢谢你,我真的希望它能够工作,但是使用这一行: Join foobars = (Join) fromFoo.fetch("fooId");我得到一个编译错误:不可转换的类型;无法将 'javax.persistence.criteria.Fetch 转换为 'javax.persistence.criteria.Join'
    • @slashdottir 您使用的是哪个休眠版本?
    • 看起来像它的 5.1.0.Final
    • @slashdottir 为什么不用最新版本?
    • @slashdottir 抱歉,这是 5.3.5 最终版。
    【解决方案2】:

    我坚信问题出在实体的映射上。 如果模型不正确,您将很难生成正确的查询。

    让我们看看 ddl 从初始代码中生成了什么: org.hibernate.DuplicateMappingException: Table [bar] contains physical column name [foo_id] referred to by multiple physical column names: [foo_id], [fooId]

    让我们试着纠正它:

    @Column(name = "foo_id")
    private Long fooId;
    

    现在生成以下ddl:

    create table foo (foo_id bigint(20) generated by default as identity,
    _deleted_ smallint,
    name varchar(255),
    primary key (foo_id))
    
    create table bar (bar_id bigint(20) generated by default as identity,
    _deleted_ smallint,
    foo_id bigint,
    key varchar(255),
    value varchar(255),
    bars_bar_id bigint(20) not null,
    primary key (bar_id))
    

    问题

    bars_bar_id 是您的@JoinTable 的结果,会出现问题。

    另一个答案中提出的查询,使用
    Join&lt;Foo, Bar&gt; foobars = (Join&lt;Foo, Bar&gt;) fromFoo.fetch("fooId");

    hibernate.jpa.criteria.BasicPathUsageException: Cannot join to attribute of basic type 失败 见a hint that you need a properly mapped association to make a join

    请注意,仅更改:

    @Column(name = "foo_id")
    private Long fooId;
    

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

    还不够:从 foo 到 bar 的任何 a 都将导致 SQL 中的 2 个连接(如前所述,意外字段 bars_bar_id 上的 FK):

    final CriteriaBuilder builder = em.getCriteriaBuilder();
    final CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
    Root<Foo> fromFoo = criteria.from(Foo.class);
    Join<Foo, Bar> foobars = (Join) fromFoo.fetch("bars");
    
    select
        foo0_.foo_id as foo_id1_2_0_,
        bar2_.bar_id as bar_id1_1_1_,
        foo0_._deleted_ as _deleted2_2_0_,
        foo0_.name as name3_2_0_,
        bar2_._deleted_ as _deleted2_1_1_,
        bar2_.foo_id as foo_id3_1_1_,
        bar2_.key as key4_1_1_,
        bar2_.value as value5_1_1_,
        bars1_.foo_id as foo_id3_1_0__,
        bars1_.bars_bar_id as bars_bar6_1_0__ 
    from
        foo foo0_ 
    inner join
        bar bars1_ 
            on foo0_.foo_id=bars1_.foo_id 
    inner join
        bar bar2_ 
            on bars1_.bars_bar_id=bar2_.bar_id 
    

    正确的映射

    The best way to map a @OneToMany relationship with JPA and Hibernate

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @EqualsAndHashCode(callSuper = false)
    @Entity(name = "foo")
    public class Foo {
    
        @Id
        @Column(name = "foo_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long fooId;
        private String name;
        @Column(name = "_deleted_")
        private Short deleted;
    
        @OneToMany(mappedBy = "foo")
        private List<Bar> bars;
    }
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @EqualsAndHashCode(callSuper = false)
    @Entity(name = "bar")
    public class Bar {
    
        @Id
        @Column(name = "bar_id", nullable = false, unique = true, columnDefinition = "bigint(20)")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long barId;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "foo_id")
        Foo foo;
    
        private String key;
        private String value;
        @Column(name = "_deleted_")
        private Short deleted;
    }
    

    条件查询

    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Foo> criteria = builder.createQuery(Foo.class);
    Root<Foo> fromFoo = criteria.from(Foo.class);
    Join<Foo, Bar> foobars = (Join) fromFoo.fetch("bars");
    
    List<Predicate> conditions = new ArrayList<>();
    conditions.add(builder.equal(fromFoo.get("deleted"), 0));
    conditions.add(builder.equal(foobars.get("deleted"), 0));
    
    TypedQuery<Foo> typedQuery = entityManager.createQuery(
            criteria.select(fromFoo)
                    .where(conditions.toArray(new Predicate[]{})));
    

    生成的 SQL

    select
        foo0_.foo_id as foo_id1_2_0_,
        bars1_.bar_id as bar_id1_1_1_,
        foo0_._deleted_ as _deleted2_2_0_,
        foo0_.name as name3_2_0_,
        bars1_._deleted_ as _deleted2_1_1_,
        bars1_.foo_id as foo_id5_1_1_,
        bars1_.key as key3_1_1_,
        bars1_.value as value4_1_1_,
        bars1_.foo_id as foo_id5_1_0__,
        bars1_.bar_id as bar_id1_1_0__ 
    from
        foo foo0_ 
    inner join
        bar bars1_ 
            on foo0_.foo_id=bars1_.foo_id 
    where
        foo0_._deleted_=0 
        and bars1_._deleted_=0
    

    【讨论】:

    • 谢谢,我试过了,但是当我们到达 List baz = typedQuery.getResultList();结果是一个空数组...
    • 如何查看 ddl?
    • 有趣的是,如果我注释掉条件逻辑,例如我运行: Join foobars = (Join) fromFoo.fetch("bars"); TypedQuery typedQuery = entityManager.createQuery(criteria.select(fromFoo));我得到 7 个结果。两个用于猫(每只猫有两个条),2 个用于狗(每个有两个条),3 个用于鼠标(每个有 3 个条)。但添加条件后,结果为零。
    • 我发现了我的问题。创建表时,deleted 列默认为 NULL,而不是“0”。将条件更改为: conditions.add(builder.isNull(fromFoo.get("deleted"))) 给我 6 个结果。但是,“鼠标”结果显示 3 个子栏,而不是 2 个。
    • 要查看 ddl,我设置了一个测试配置:我使用内存数据库 (h2),然后设置 spring.jpa.hibernate.ddl-auto=create-drop,并强制记录 sql
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-16
    • 2017-09-06
    • 2018-07-14
    • 1970-01-01
    • 2013-05-15
    • 2021-02-15
    相关资源
    最近更新 更多