如何使JPA使用单个联接来获取双方条件的列

发布于 2025-02-12 20:42:45 字数 3407 浏览 3 评论 0原文

我的问题是以下

是有两个实体类,我们将其称为entity1entity2,在之间,即一对多的关系,即一个entity1 包含多个entity2 s,entity2可能只有一个entity1

@Entity
@Table(name = "entity1")
public class Entity1 {

    int x;
    int y;
    ...
    @LazyCollection(LazyCollectionOption.TRUE)
    @OneToMany(mappedBy = "e1", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Entity2> entity2s = new HashSet<>();

}


@Entity
@Table(name = "entity2")
public class Entity2 {

    int a;
    int b;  
    ...
    @ManyToOne
    @JoinColumn(name = "entity1_id")
    @JsonBackReference
    private Entity1 e1;

}

现在我想发出查询以检索entity2 s with whore entity2及其相应的entity1

@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>' AND e2.e1.x = '<val3>' AND e2.e1.y ='<val4>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

因此,这种方法的问题是,它确实得到了需要的entity2 s通过交叉加入entity1entity2具有指定条件的表,但为每个条件提供了e1 s entity2在结果中带有单独的查询。
因此,例如,如果加入的结果为5 entity2 s,则将对entity1表有5个其他查询。

我尝试在entity2中设置@manytoone @manytoone(fetch = fetchType.lazy)>,但这无济于事。我想这是预期的,因为懒惰只会将e1 s的检索推迟,但不会完全消除它。

接下来,我阅读了有关@entityGraph的信息,并将其添加到entity2

@Entity
@Table(name = "entity2")
@NamedEntityGraph(name = "graph.entity2.entity1",
    attributeNodes = { @NamedAttributeNode("e1") })
public class Entity2 {

    int a;
    int b;  
    ...
    @ManyToOne
    @JoinColumn(name = "entity1_id")
    @JsonBackReference
    private Entity1 e1;

}

在存储库中,我添加了它:

@EntityGraph(value = "graph.entity2.entity1")
@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>' AND e2.e1.x = '<val3>' AND e2.e1.y ='<val4>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

在这种情况下,单独的SQL查询消失了,EntityGraph确实可以左JOIN及其结果包含来自entity1Entity2的列,但是因为e2.e1的条件仍在子句,它添加了另外一个不必要的交叉加入,entity1表(e2.e1条件都在该交叉上加入中检查)。

我找不到摆脱额外交叉加入的方法,所以现在我正在使用以下查询:

@EntityGraph(value = "graph.entity2.entity1")
@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

基本上我会得到entity2 s,在应用程序中,我根据条件过滤出来entity1e2.e1.x ='&lt; val3&gt;'和e2.e1.y ='&lt; val4&gt;')。

是否有一种方法可以使其仅适用于两个实体条件,而不仅仅是entity2条件?我现在这样做的方式对我来说似乎并不正确,我觉得只有使用存储库方法就可以做到这一点,而无需涉及应用程序。感谢有关此

UPD的任何帮助。在@qoery> @query注释中,阅读有关nativeQuery option> option(nativeQuery = true),该注释允许指定原始查询,从而绕过基于实体的查询,但是查询仍然使用entity1_id(Entity Graph被禁用了)。我试图启用实体图,但删除了例外,表明实体图不能与本机查询一起使用,这是可以预期的

My problem is the following,

There are two entity classes, let's call them Entity1 and Entity2 with One-to-Many relationship in between, i.e. one Entity1 contains multiple Entity2s, and Entity2 may have only one Entity1:

@Entity
@Table(name = "entity1")
public class Entity1 {

    int x;
    int y;
    ...
    @LazyCollection(LazyCollectionOption.TRUE)
    @OneToMany(mappedBy = "e1", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Entity2> entity2s = new HashSet<>();

}


@Entity
@Table(name = "entity2")
public class Entity2 {

    int a;
    int b;  
    ...
    @ManyToOne
    @JoinColumn(name = "entity1_id")
    @JsonBackReference
    private Entity1 e1;

}

Now I would like to issue a query for retrieving Entity2s with WHERE conditions for both Entity2 and its corresponding Entity1:

@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>' AND e2.e1.x = '<val3>' AND e2.e1.y ='<val4>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

So the problem with this approach is that, it indeed gets desired Entity2s by cross joining entity1 and entity2 tables with specified WHERE conditions BUT it fetches e1s for each of those Entity2s in the result with a separate query.
So for example if the result of join is 5 Entity2s, there will be 5 additional queries to entity1 table.

I tried to set @ManyToOne in Entity2 as @ManyToOne(fetch = FetchType.LAZY) but it didn't help. I guess that's expected because LAZY would simply postpone the retrieval of e1s but wouldn't eliminate it completely.

Next, I read about @EntityGraph, and added it to Entity2:

@Entity
@Table(name = "entity2")
@NamedEntityGraph(name = "graph.entity2.entity1",
    attributeNodes = { @NamedAttributeNode("e1") })
public class Entity2 {

    int a;
    int b;  
    ...
    @ManyToOne
    @JoinColumn(name = "entity1_id")
    @JsonBackReference
    private Entity1 e1;

}

and in the repository, I added it as:

@EntityGraph(value = "graph.entity2.entity1")
@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>' AND e2.e1.x = '<val3>' AND e2.e1.y ='<val4>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

In this case, the separate SQL queries disappear, EntityGraph does left join and its result contains columns from both entity1 and entity2, BUT because the conditions for e2.e1 are still in WHERE clause, it adds ONE MORE unnecessary cross join with entity1 table (e2.e1 conditions are checked in that cross join).

I couldn't find a way to get rid of that extra cross join, so now I'm using the following query:

@EntityGraph(value = "graph.entity2.entity1")
@Query("SELECT " +
       " e2 " +
       "FROM " +
       " Entity2 e2 " +
       "WHERE " +
       " e2.a = '<val1>' AND e2.b = '<val2>'")
List<Entity2> findMyEntity2s(
        @Param...,
        @Param...,
);

So basically I get Entity2s and in the application I filter out based on conditions of Entity1 (e2.e1.x = '<val3>' AND e2.e1.y ='<val4>').

Is there a way to make it work with a single join only, for both entity's conditions, not only Entity2 conditions? The way I'm doing it now, does not seem correct and efficient to me, and I feel there's a way to do that using repository method only, without involving the app. Would appreciate any help on this

UPD. Read about nativeQuery option (nativeQuery = true) for @Query annotation, which allows specifying a raw query and thus bypassing entity-based query, but the query still fetches many-to-one e1 field, using entity1_id (entity graph was disabled). I tried to enable entity graph but it dropped exception stating that entity graph cannot be used with native query, which is expected

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

未蓝澄海的烟 2025-02-19 20:42:45

这是经典的N + 1查询问题。
您可以在此处阅读详细信息: 1-问题 - 问题/

在您的查询中,附加:

LEFT JOIN FETCH e2.e1 e2e1

这将在第一个和单个查询中用E2获取E1。

不要忘记;始终使用fetchtype.lazy并使用加入Fetch获取实体。否则,在项目扩大的范围时,您将陷入较大的质量。

此外,为什么在实体课程中使用Jaxson注释?使用实体仅访问DAO,然后将其映射到另一个DTO,以便在其他地方使用。

This is the classic n + 1 query problem.
You can read the detail here: https://vladmihalcea.com/n-plus-1-query-problem/

In your query, append:

LEFT JOIN FETCH e2.e1 e2e1

This will fetch e1 with e2 in the first and single query.

Don't forget; always use FetchType.LAZY and fetch your entities with JOIN FETCH. Otherwise, you will get into a big mass while the scope of the project enlarges.

In addition, why do you use Jaxson annotations in your Entity classes? Use entities for only DAO access and map them to another DTOs to use elsewhere.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文