@LazyToOne и Criteria Fetch: Hibernate выполняет неожиданный запрос (проблема n + 1) - PullRequest
0 голосов
/ 12 января 2019

У меня есть объект A, который содержит отношение @OneToOne к объекту B и объекту C. Объект B и объект C, в свою очередь, имеют отношение @OneToOne к объекту B1 и объекту C1:

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

Мне нужно выбрать все данные объекта A с некоторыми полями объекта B и объекта C. Поля объекта B1 и C1 следует игнорировать.

Hibernate по умолчанию рассматривает все отношения * ToOne как EADGER, и единственный способ настроить * ToOne с отложенной загрузкой - это использовать аннотацию @LazyToOne (LazyToOneOption.NO_PROXY) в сочетании с инструментарием компиляции hibernate.

Итак, чтобы выполнить только один запрос и загрузить только желаемые отношения:

  1. Я настроил все отношение @OneToOne с @LazyToOne (LazyToOneOption.NO_PROXY)

  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.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=?
...