Избегайте вторичных выборок или объединений с помощью критериев гибернации или запроса HQL

У меня возникли проблемы с оптимизацией запросов Hibernate, чтобы избежать объединения или вторичного выбора.

Когда выполняется запрос Hibernate (критерии или hql), например:

return getSession().createQuery(("from GiftCard as card where card.recipientNotificationRequested=1").list();

... и предложение where исследует свойства, которые не требуют каких-либо соединений с другими таблицами ... но Hibernate по-прежнему выполняет полное соединение с другими таблицами (или вторичный выбор в зависимости от того, как я установил fetchMode).

У рассматриваемого объекта (GiftCard) есть пара ассоциаций ManyToOne, которые я предпочел бы в этом случае загружать лениво (но не обязательно во всех случаях). Мне нужно решение, в котором я могу контролировать то, что лениво загружается при выполнении запроса.

Вот как выглядит объект GiftCard:

@Entity
@Table(name = "giftCards")
public class GiftCard implements Serializable
{
 private static final long serialVersionUID = 1L;

 private String id_;
 private User buyer_;
 private boolean isRecipientNotificationRequested_;


 @Id
 public String getId()
 {
  return this.id_;
 }

 public void setId(String id)
 {
  this.id_ = id;
 }

 @ManyToOne
 @JoinColumn(name = "buyerUserId")
 @NotFound(action = NotFoundAction.IGNORE)
 public User getBuyer()
 {
  return this.buyer_;
 }
 public void setBuyer(User buyer)
 {
  this.buyer_ = buyer;
 }

 @Column(name="isRecipientNotificationRequested", nullable=false, columnDefinition="tinyint")
 public boolean isRecipientNotificationRequested()
 {
  return this.isRecipientNotificationRequested_;
 }

 public void setRecipientNotificationRequested(boolean isRecipientNotificationRequested)
 {
  this.isRecipientNotificationRequested_ = isRecipientNotificationRequested;
 }
}

person Ben Benson    schedule 29.05.2010    source источник


Ответы (2)


Как сказано

Мне нужно решение, которое я могу контролировать ленивую загрузку при выполнении запроса

Если у вас есть такое отображение

@Entity
public class GiftCard implements Serializable {

    private User buyer;

    @ManyToOne
    @JoinColumn(name="buyerUserId")
    public User getBuyer() {
        return this.buyer;
    }
}

Любое отношение * ToOne, такое как @OneToOne и @ManyToOne, по умолчанию является FetchType.EAGER, что означает, что оно будет всегда извлекаться. Но это может быть не то, что вы хотите. То, что вы говорите как Я могу контролировать то, что загружается лениво, можно перевести как Стратегия загрузки. Книга POJO в действии поддерживает такой шаблон (подпись метода уведомления)

public class GiftCardRepositoryImpl implements GiftCardRepository {

     public List<GiftCard> findGiftCardWithBuyer() {
         return sessionFactory.getCurrentSession().createQuery("from GiftCard c inner join fetch c.buyer where c.recipientNotificationRequested = 1").list();
     }

}

Таким образом, в зависимости от вашего варианта использования вы можете создать свой собственный метод find ... With ... And .... Он позаботится о том, чтобы получить именно то, что вы хотите.

Но у него есть проблема: он не поддерживает сигнатуру универсального метода. Для каждого репозитория @Entity вы должны определить свой собственный метод find ... With ... And. По этой причине я покажу вам, как я определяю общий репозиторий.

public interface Repository<INSTANCE_CLASS, UPDATABLE_INSTANCE_CLASS, PRIMARY_KEY_CLASS> {

    void add(INSTANCE_CLASS instance);
    void remove(PRIMARY_KEY_CLASS id);
    void update(PRIMARY_KEY_CLASS id, UPDATABLE_INSTANCE_CLASS updatableInstance);
    INSTANCE_CLASS findById(PRIMARY_KEY_CLASS id);
    INSTANCE_CLASS findById(PRIMARY_KEY_CLASS id, FetchingStrategy fetchingStrategy);
    List<INSTANCE_CLASS> findAll();
    List<INSTANCE_CLASS> findAll(FetchingStrategy fetchingStrategy);
    List<INSTANCE_CLASS> findAll(int pageNumber, int pageSize);
    List<INSTANCE_CLASS> findAll(int pageNumber, int pageSize, FetchingStrategy fetchingStrategy);
    List<INSTANCE_CLASS> findAllByCriteria(Criteria criteria);
    List<INSTANCE_CLASS> findAllByCriteria(Criteria criteria, FetchingStrategy fetchingStrategy);
    List<INSTANCE_CLASS> findAllByCriteria(int pageNumber, int pageSize, Criteria criteria);
    List<INSTANCE_CLASS> findAllByCriteria(int pageNumber, int pageSize, Criteria criteria, FetchingStrategy fetchingStrategy);

}

Но, иногда вам не нужно, чтобы все методы определялись универсальным интерфейсом репозитория. Решение: создайте класс AbstractRepository, который будет реализовывать фиктивный репозиторий. Например, Spring Framework активно использует этот тип шаблона. Интерфейс >> AbstractInterface

public abstract class AbstractRepository<INSTANCE_CLASS, UPDATABLE_INSTANCE_CLASS, PRIMARY_KEY_CLASS> implements Repository<INSTANCE_CLASS, UPDATABLE_INSTANCE_CLASS, PRIMARY_KEY_CLASS> {

    public void add(INSTANCE_CLASS instance) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void remove(PRIMARY_KEY_CLASS id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void update(PRIMARY_KEY_CLASS id, UPDATABLE_INSTANCE_CLASS updatableInstance) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public INSTANCE_CLASS findById(PRIMARY_KEY_CLASS id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public INSTANCE_CLASS findById(PRIMARY_KEY_CLASS id, FetchingStrategy fetchingStrategy) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAll() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAll(FetchingStrategy fetchingStrategy) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAll(int pageNumber, int pageSize) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAll(int pageNumber, int pageSize, FetchingStrategy fetchingStrategy) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAllByCriteria(Criteria criteria) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAllByCriteria(Criteria criteria, FetchingStrategy fetchingStrategy) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAllByCriteria(int pageNumber, int pageSize, Criteria criteria) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<INSTANCE_CLASS> findAllByCriteria(int pageNumber, int pageSize, Criteria criteria, FetchingStrategy fetchingStrategy) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}

Таким образом, ваш GiftCardRepository может быть переписан как (см. Расширяет, а не реализует) и просто переопределяет то, что вы действительно хотите

public class GiftCardRepository extends AbstractRepository<GiftCard, GiftCard, String> {

    public static final GIFT_CARDS_WITH_BUYER GIFT_CARDS_WITH_BUYER = new GIFT_CARDS_WITH_WITH_BUYER();
    public static final GIFT_CARDS_WITHOUT_NO_RELATIONSHIP GIFT_CARDS_WITHOUT_NO_RELATIONSHIP = new GIFT_CARDS_WITHOUT_NO_RELATIONSHIP();

    public List<GiftCard> findAll(FetchingStrategy fetchingStrategy) {
        sessionFactory.getCurrentSession().getNamedQuery(fetchingStrategy.toString()).list();
    }


    /**
      * FetchingStrategy is just a marker interface
      * public interface FetchingStrategy {}
      *
      * And AbstractFetchingStrategy allows you to retrieve the name of the Fetching Strategy you want, by overriding toString method
      * public class AbstractFetchingStrategy implements FetchingStrategy {
      *
      *     @Override
      *     public String toString() {
      *         return getClass().getSimpleName();
      *     } 
      *
      * }
      * 
      * Because there is no need to create an instance outside our repository, we mark it as private
      * Notive each FetchingStrategy must match a named query
      */
    private static class GIFT_CARDS_WITH_BUYER extends AbstractFetchingStrategy {}    
    private static class GIFT_CARDS_WITHOUT_NO_RELATIONSHIP extends AbstractFetchingStrategy {}
}

Теперь мы вынесем наш именованный запрос на внешний вид в многострочном - читаемом и обслуживаемом - файле xml.

// app.hbl.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <query name="GIFT_CARDS_WITH_BUYER">
        <![CDATA[
            from 
                GiftCard c
            left join fetch 
                c.buyer
            where
                c.recipientNotificationRequested = 1
        ]]>
    </query>
    <query name="GIFT_CARDS_WITHOUT_NO_RELATIONSHIP">
        <![CDATA[
            from 
                GiftCard
        ]]>
    </query>
</hibernate-mapping>

Поэтому, если вы хотите получить свою подарочную карту у покупателя, просто позвоните

Repository<GiftCard, GiftCard, String> giftCardRepository;

List<GiftCard> giftCardList = giftCardRepository.findAll(GiftCardRepository.GIFT_CARDS_WITH_WITH_BUYER);

И чтобы получить нашу GiftCard без каких-либо отношений, просто позвоните

List<GiftCard> giftCardList = giftCardRepository.findAll(GiftCardRepository.GIFT_CARDS_WITHOUT_NO_RELATIONSHIP);

или используйте import static

import static packageTo.GiftCardRepository.*;

И

List<GiftCard> giftCardList = giftCardRepository.findAll(GIFT_CARDS_WITHOUT_NO_RELATIONSHIP);

Надеюсь, это может быть вам полезно!

person Arthur Ronald    schedule 29.05.2010
comment
Очень интересно ... это отличная пища для размышлений. И это поднимает побочный вопрос. Как мне выполнить запрос, который не извлекает никаких связанных объектов, кроме целевой GiftCard? - person Ben Benson; 30.05.2010
comment
@Ben Benson См. GiftCardRepository (последняя строка), именованный файл запроса и дополнительный код внизу. Если мой ответ соответствует вашим требованиям, отметьте его как принятый. Спасибо - person Arthur Ronald; 31.05.2010

В JPA тип выборки по умолчанию для ассоциаций ManyToOne является нетерпеливым (т. Е. Неленивым), поэтому не могли бы вы попробовать:

@ManyToOne(fetch=FetchType.LAZY)

Затем в любом запросе JPA ассоциацию можно быстро получить с помощью left join fetch.

person Pascal Thivent    schedule 29.05.2010