【问题标题】:Map result of query with filtered join back to entity with relation将过滤连接的查询结果映射回具有关系的实体
【发布时间】:2022-01-28 11:03:41
【问题描述】:

我正在尝试使用联接执行 SQL 查询,其中联接具有附加过滤条件。使用纯 SQL,我的数据建模如下:

create table food_storage (
  name varchar primary key
);
create table food (
  name varchar primary key,
  expired bool,
  stored_in varchar references food_storage(name)
);

insert into food_storage (name) values ('drawer'), ('fridge'), ('cabinet');
insert into food
  (name,        expired, stored_in)
values
  ('teabags',   false,   'drawer'),
  ('beans',     true,    'drawer'),
  ('leftovers', true,    'fridge'),
  ('rice',      false,   'cabinet'),
  ('flour',     false,   'cabinet');

我的查询看起来像这样(为简洁起见,只选择了几个字段):

select food_storage.name, food.name from food_storage
left outer join food on food.stored_in = food_storage.name and not food.expired;
name name
drawer teabags
cabinet rice
cabinet flour
fridge (null)

但是,我正在尝试使用 JPA 执行此查询,并将结果成功映射回 JPA 实体。我这样建模我的 JPA 实体:

@Entity
@Table(name = "food_storage")
@Data
public class FoodStorage {
  @Id
  private String name;

  @OneToMany(mappedBy = "stored_in")
  private List<Food> foodContents;
}

@Entity
@Table(name = "food")
@Data
public class Food {
  @Id
  private String name;

  @Column
  private boolean expired;

  @Column(name = "stored_in")
  private String storedIn;
}

对于上面的原始 SQL 查询结果,我需要将结果映射回如下实体:

[
    FoodStorage(name=drawer, foodContents=[
        Food(name=teabags, expired=false, storedIn=drawer)
    ]),
    FoodStorage(name=fridge, foodContents=[]),
    FoodStorage(name=cabinet, foodContents=[
        Food(name=rice, expired=false, storedIn=cabinet),
        Food(name=flour, expired=false, storedIn=cabinet)
    ])
]

我尝试使用此代码:

String sql = """
select food_storage.*, food.* from food_storage
left outer join food on food.stored_in = food_storage.name and not food.expired;
""";
Query query = entityManager.createNativeQuery(sql, FoodStorage.class);
System.out.println(query.getResultList());

但这是我得到的结果:

[
    FoodStorage(name=drawer, foodContents=[
        Food(name=teabags, expired=false, storedIn=drawer),
        Food(name=beans, expired=true, storedIn=drawer)
    ]),
    FoodStorage(name=fridge, foodContents=[
        Food(name=leftovers, expired=true, storedIn=fridge)
    ]),
    FoodStorage(name=cabinet, foodContents=[
        Food(name=rice, expired=false, storedIn=cabinet),
        Food(name=flour, expired=false, storedIn=cabinet)
    ]),
    FoodStorage(name=cabinet, foodContents=[
        Food(name=rice, expired=false, storedIn=cabinet),
        Food(name=flour, expired=false, storedIn=cabinet)
    ])
]

Hibernate 似乎忽略了我的手动连接,而是只为每个结果行构造一个 FoodStorage-instance,然后根据 FoodStorage 获取所有 Food。我该如何克服这个问题?

我找到了 Hibernate 的 @Where 注释。不幸的是,对于我的实际情况,连接过滤器是动态的,所以我不能使用它。

【问题讨论】:

  • 你必须使用原生查询吗?
  • 谢谢,这回答了我的问题!如果您将其表述为答案,我可以接受并奖励您

标签: java sql hibernate jpa


【解决方案1】:

一种可能的解决方案是在 JQPL 中使用“获取连接”而不是原生 SQL:

var query = entityManager.createQuery(
        """
        select distinct fs from FoodStorage fs
        join fetch fs.foodContents f
        where f.expired = false
        """, FoodStorage.class);
List<FoodStorage> result = query.getResultList();
System.out.println(result);

导致:

[
    FoodStorage(name=cabinet, foodContents=[
        Food(name=rice, expired=false, storedIn=cabinet),
        Food(name=flour, expired=false, storedIn=cabinet)
    ]),
    FoodStorage(name=drawer, foodContents=[
        Food(name=teabags, expired=false, storedIn=drawer)
    ])
]

我认为这是因为 hibernate 知道加入的目的并正确理解如何将结果映射回实体。

但这有一些缺点:

  • 尽管有left outer join,但它无法获取FoodStorage 为零Food。使用left outer join fetch 而不仅仅是join fetch 没有区别。 据我了解,这是因为过滤条件必须是常规过滤器where f.expired = false,因为 JQPL 不支持将其指定为像 left outer join fetch fs.foodContents f on f.expired = false 这样的连接条件。
  • 它需要select distinct,否则hibernate 会为它包含的每个Food 复制FoodStorage 实体。
  • JPQL 的限制自然适用。

特别是最后一点可能会破坏交易,因为我经常发现自己处于 JPQL 无法表达我需要用 SQL 表达的情况。

【讨论】:

    【解决方案2】:
    String sql = "select distinct food_storage.* from food_storage left outer join food on food.stored_in = food_storage.name and not food.expired; ";
    

    food.*没用,可以用spring.jpa.properties.hibernate.show_sql=true打印sql。返回food.* 不会减少查询次数。

    【讨论】:

      猜你喜欢
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-01
      • 1970-01-01
      • 2019-07-25
      • 1970-01-01
      相关资源
      最近更新 更多