@LazyToOne和条件获取:休眠执行意外查询(n + 1个问题)

时间:2019-01-12 12:31:41

标签: hibernate spring-boot jpa spring-data-jpa one-to-one

我有一个实体A,其中包含与实体B和实体C的@OneToOne关系。实体B和实体C又与实体B1和实体C1具有@OneToOne关系:

A
| 
|--B  @OneToOne
   |
   |--B1   @OneToOne
|--C
   |
   |--C1   @OneToOne

我需要选择实体A的所有数据以及实体B和实体C的某些字段。实体B1和C1的字段必须忽略。

默认情况下,Hibernate将所有* ToOne关系视为EADGER,并且配置带有延迟加载的* ToOne的唯一方法是将注释@LazyToOne(LazyToOneOption.NO_PROXY)与休眠编译工具结合使用。

因此,仅执行单个查询并仅加载希望的关系:

  1. 我使用@LazyToOne(LazyToOneOption.NO_PROXY)配置了所有@OneToOne关系

  2. 我使用Criteria API检索A实体,获取B和C关系(希望的关系)。

// The 'A' Entity (that I want to load with criteria)
@Entity
@Table(name = "users", schema = "test_db")   
public class UserEntity
{
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer userId;

    @Column(name = "NAME")
    private String name;

    @Column(name = "SURNAME")
    private String surname;

    @Column(name = "EMAIL")
    private String email;

    // The B Entity Relation (wished)
    @OneToOne
    @JoinColumn(name = "BADGE_ID")
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private BadgeEntity badge;

    // The C Entity Relation (wished)
    @OneToOne
    @JoinColumn(name = "ADDRESS_ID")
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private AddressEntity address;

    // Getters and Setters
}

// The B Entity (relation that I eventually want to load fetching LEFT on father) 
@Entity
@Table(name = "badges", schema = "test_db")
public class BadgeEntity
{    

   @Id
   @GeneratedValue(strategy= GenerationType.IDENTITY)
   @Column(name = "BADGE_ID", nullable = false)
   private Integer badgeId;

   @Column(name = "CODE", nullable = false, length = 10)
   private String code;

   @Column(name = "DESCRIPTION", nullable = false, length = 255)
   private String description;

   // The B1 Entity Relation (not wished)
   @OneToOne
   @JoinColumn(name = "BADGE_TYPE_ID")
   @LazyToOne(LazyToOneOption.NO_PROXY)
   private BadgeTypeEntity badgeType;

   // Getters and Setters 
}

// The C Entity (relation that I eventually want to load fetching LEFT on father)   
@Entity
@Table(name = "addresses", schema = "test_db")
public class AddressEntity
{

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "ADDRESS_ID")
    private Integer addressId;

    @Column(name = "DESCRIPTION")
    private String description;

    @Column(name = "CITY")
    private String city;

    @Column(name = "STATE")
    private String state;

    // The C1 Entity Relation (not wished)
    @OneToOne
    @JoinColumn(name = "ADDRESS_TYPE_ID")
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private AddressTypeEntity addressType;

     // Getters and Setters 
 }

// The B1 Entity (relation that I don't want to load) 
@Entity
@Table(name = "badge_types", schema = "test_db")
public class BadgeTypeEntity
{

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "BADGE_TYPE_ID")
    private Integer badgeTypeId;

    @Column(name = "CODE")
    private String code; 

    // Getters and Setters 
}    

// The C1 Entity (relation that I don't want to load) 
@Entity
@Table(name = "address_types", schema = "test_db")
public class AddressTypeEntity {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "ADDRESS_TYPE_ID")
    private Integer addressTypeId;

    @Column(name = "CODE")
    private String code;

    // Getters and Setters 
}    

/**
*
*   Load all UserEntities executing only single query,
*   fetching RIGHT on BadgeEntity and AddressEntity (@OneToOne relations), 
*   avoiding load of nested BadgeTypeEntity and AddressTypeEntity
*
**/
@Override
public List<UserEntity> getUserByFilter()
{
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<UserEntity> cq = cb.createQuery(UserEntity.class);
    Root<UserEntity> user = cq.from(UserEntity.class);

    // Fetch User --> Address (wished relation, without nested @OneToOne relations)
    Fetch<UserEntity, AddressEntity> addressFetch = user.fetch(UserEntity_.address, JoinType.LEFT);
    // Fetch User --> Badge (wished relation, without nested @OneToOne relations) 
    Fetch<UserEntity, BadgeEntity> badgeFetch = user.fetch(UserEntity_.badge, JoinType.LEFT);

    List<Predicate> predicates = new ArrayList<Predicate>();

    cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()]))).distinct(true);
    TypedQuery<UserEntity> query = em.createQuery(cq);
    List<UserEntity> result = query.getResultList();

    return result;
}


@Test
@Transactional
public void getUserByFilter_Test()
{
    try
    {
        UserEntity eUser = userCustomRepository.getUserByFilter().get(0);
        // At this point, one single query is executed
        System.out.println("\n********************************");
        System.out.println("Name    : " + eUser.getName());
        System.out.println("Surname : " + eUser.getSurname());
        System.out.println("Email   : " + eUser.getEmail());
        System.out.println("********************************\n");
        // Calling get on alrready fetched relation, cause execution of an extra query!
        BadgeEntity eBadge = eUser.getBadge();
        System.out.println("Badge Code : " + eBadge.getCode());
        Assert.assertNotNull(eUser);
    }
    catch (Exception ex)
    {
        ex.printStackTrace();
        Assert.fail(ex.getMessage());
    }
}    

执行查询方法时,我希望在User表上执行单个查询,并在Badge和Address表上直接获取。

此查询实际上已执行,但是,如果我尝试访问UserEntity的关系(例如徽章),则会执行一个奇怪的额外查询:

Hibernate: 
    select
        distinct userentity0_.user_id as user_id1_6_0_,
    addressent1_.address_id as address_1_1_1_,
    badgeentit2_.badge_id as badge_id1_3_2_,
    userentity0_.email as email2_6_0_,
    userentity0_.name as name3_6_0_,
    userentity0_.surname as surname4_6_0_,
    addressent1_.city as city2_1_1_,
    addressent1_.description as descript3_1_1_,
    addressent1_.state as state4_1_1_,
    badgeentit2_.code as code2_3_2_,
    badgeentit2_.description as descript3_3_2_ 
from
    users userentity0_ 
left outer join
    addresses addressent1_ 
        on userentity0_.address_id=addressent1_.address_id 
left outer join
    badges badgeentit2_ 
        on userentity0_.badge_id=badgeentit2_.badge_id 
where
    1=1

名称:Jhon 姓:母鹿 电子邮件:jhon.doe@gmail.com


-- STRANGE EXTRA QUERY I DON'T KNOW WHY EXECUTED  
Hibernate: 
    select
        userentity_.address_id as address_5_6_,
        userentity_.badge_id as badge_id6_6_ 
    from
        users userentity_ 
    where
        userentity_.user_id=?

0 个答案:

没有答案