简体   繁体   English

Hibernate 本机查询也获得 JPA 选择

[英]Hibernate native query also gets JPA select

I have a two database tables, "A" and "B" with @OneToMany(mappedBy = "a") on a List<B> field in entity A, and a @ManyToOne on field Ba I ran into the "N+1" problem when doing default queries on A, so I am trying a native query such as:我有两个数据库表,“A”和“B”,实体 A 中的 List<B> 字段上带有 @OneToMany(mappedBy = "a") 和字段 Ba 上的 @ManyToOne 我遇到了“N+1 " 在 A 上进行默认查询时出现问题,因此我正在尝试本机查询,例如:

@Query(value="select * from A as a left join B as b " +
            "on a.ID = b.b ",
            nativeQuery=true)

This works in the sense that the data is mapped back to the entities as expected.这在数据按预期映射回实体的意义上起作用。

My problem is that I can see that Hibernate is doing a separate select for each B rather than using the results of the join.我的问题是我可以看到 Hibernate 正在为每个 B 做一个单独的选择,而不是使用连接的结果。 That is, I see in the console a sequence of:也就是说,我在控制台中看到了一系列:

  • The select that I specified我指定的选择
  • For each instance of A, another select for B using the ID from A对于 A 的每个实例,使用 A 中的 ID 为 B 选择另一个

In other words, I've still got the "n+1" problem.换句话说,我仍然有“n+1”问题。

I figured that the @OneToMany and @ManyToOne annotations might be causing Hibernate to do these extra selects, but when I take them out, my IDE (IntelliJ) says:我认为 @OneToMany 和 @ManyToOne 注释可能会导致 Hibernate 执行这些额外的选择,但是当我将它们取出时,我的 IDE (IntelliJ) 说:

'Basic' attribute should not be a container

... on the List property in A. ... 在 A 中的 List 属性上。

How can I get it to map the results back in a single select with join?我怎样才能让它通过连接将结果映射回单个选择? Should I just give up on Hibernate and JPA?我应该放弃 Hibernate 和 JPA 吗?

I am using spring-boot-start-data-jpa.2.5.4我正在使用 spring-boot-start-data-jpa.2.5.4

Native @Query doesn't have sufficient mapping power, so it seems that Hibernate native query must be needed. Native @Query没有足够的映射能力,所以似乎必须需要Hibernate native query。

import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.hibernate.Session;
import org.hibernate.transform.BasicTransformerAdapter;
import org.springframework.stereotype.Repository;

// https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repositories.custom-implementations
@Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<A> getAll() {
        // https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#sql-entity-associations-query
        final Session sess = (Session) entityManager.getDelegate();
        final List<A> res = sess
            // If no duplicate column names, original sql can be used, too.
            .createNativeQuery("select {a.*},{b.*} from A as a left join B as b on a.ID = b.a ")
            .addEntity("a", A.class)
            .addJoin("b", "a.bs")
            .setResultTransformer(DistinctResultTransformer.INSTANCE)
            .list();

        return res;
    }

    // https://stackoverflow.com/q/12071014/4506703
    static class DistinctResultTransformer extends BasicTransformerAdapter {
        private static final long serialVersionUID = 1L;

        static final DistinctResultTransformer INSTANCE = new DistinctResultTransformer();

        @Override
        public List transformList(final List collection) {
            final List<Object> res = new ArrayList<>();
            for (final Object[] obj : (List<Object[]>) collection) {
                if (!res.contains(obj[0])) {
                    res.add(obj[0]);
                }
            }
            return res;
        }
    }
}

Above code executes 1 query:上面的代码执行 1 个查询:

select a.id as id1_0_0_, a.name as name2_0_0_,b.a as a3_1_0__, b.id as id1_1_0__, b.id as id1_1_1_, b.a as a3_1_1_, b.name as name2_1_1_
from A as a left join B as b on a.ID = b.a

full sample code 完整的示例代码


You can use some methods avoiding N+1 problem.您可以使用一些方法来避免 N+1 问题。

Using JPQL fetch, instead of native-query:使用 JPQL fetch,而不是 native-query:

    @Query("select distinct a from A a left join fetch a.bs")
    List<A> getAllJpqlFetch();

Above code executes 1 query:上面的代码执行 1 个查询:

select distinct a0_.id as id1_0_0_, bs1_.id as id1_1_1_, a0_.name as name2_0_0_, bs1_.a as a3_1_1_, bs1_.name as name2_1_1_, bs1_.a as a3_1_0__, bs1_.id as id1_1_0__ 
from a a0_ left outer join b bs1_ on a0_.id=bs1_.a

diff 差异


Using JPA Criteria fetch, is equivalent to above JPQL:使用 JPA Criteria fetch,相当于上面的 JPQL:

@Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<A> getAllCriteria() {
        // https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#criteria-from-fetch
        final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<A> criteria = builder.createQuery(A.class);
        final Root<A> root = criteria.from(A.class);
        root.fetch("bs", JoinType.LEFT);
        criteria.select(root).distinct(true);
        return entityManager.createQuery(criteria).getResultList();
    }

Above code executes 1 query:上面的代码执行 1 个查询:

select distinct a0_.id as id1_0_0_, bs1_.id as id1_1_1_, a0_.name as name2_0_0_, bs1_.a as a3_1_1_, bs1_.name as name2_1_1_, bs1_.a as a3_1_0__, bs1_.id as id1_1_0__ 
from a a0_ left outer join b bs1_ on a0_.id=bs1_.a

diff 差异


Using @Fetch(FetchMode.SUBSELECT) :使用@Fetch(FetchMode.SUBSELECT)

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
// ...

@Entity
public class A {

    @OneToMany(mappedBy = "a")
    @Fetch(FetchMode.SUBSELECT)
    private List<B> bs;

    // ...
}
// findAll() method implementation is auto-generated by Spring Data JPA
// https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repositories.core-concepts
repository.findAll();

Above code executes 2 queries(root entities and their relational entities):上面的代码执行 2 个查询(根实体及其关系实体):

select a0_.id as id1_0_, a0_.name as name2_0_ from a a0_

select bs0_.a as a3_1_1_, bs0_.id as id1_1_1_, bs0_.id as id1_1_0_, bs0_.a as a3_1_0_, bs0_.name as name2_1_0_ 
from b bs0_ where bs0_.a in (select a0_.id from a a0_)

diff 差异

I ended up using the following solution, given by DEWA Kazuyuki, above.我最终使用了上面由 DEWA Kazuyuki 提供的以下解决方案。 I'm copying it here because DEWA suggested several answers and I thought it useful to identify the particular one that worked for me.我在这里复制它是因为 DEWA 提出了几个答案,我认为确定对我有用的特定答案很有用。 Thanks, DEWA.谢谢,德瓦。

@Repository
public class CustomizedARepositoryImpl implements CustomizedARepository {

@PersistenceContext
private EntityManager entityManager;

@Override
public List<A> getAllCriteria() {
    // https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#criteria-from-fetch
    final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    final CriteriaQuery<A> criteria = builder.createQuery(A.class);
    final Root<A> root = criteria.from(A.class);
    root.fetch("bs", JoinType.LEFT);
    criteria.select(root).distinct(true);
    return entityManager.createQuery(criteria).getResultList();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM