I am having trouble optimizing Hibernate queries to avoid performing joins or secondary selects.
When a Hibernate query is performed (criteria or hql), such as the following:
return getSession().createQuery(("from GiftCard as card where card.recipientNotificationRequested=1").list();
... and the where clause examines properties that do not require any joins with other tables... but Hibernate still performs a full join with other tables (or secondary selects depending on how I set the fetchMode).
The object in question (GiftCard) has a couple ManyToOne associations that I would prefer to be lazily loaded in this case (but not necessarily all cases). I want a solution that I can control what is lazily loaded when I perform the query.
Here's what the GiftCard Entity looks like:
@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;
}
}
As said
I want a solution that I can control what is lazily loaded when I perform the query
If you have a mapping like this one
@Entity
public class GiftCard implements Serializable {
private User buyer;
@ManyToOne
@JoinColumn(name="buyerUserId")
public User getBuyer() {
return this.buyer;
}
}
Any *ToOne relationship, such as @OneToOne and @ManyToOne, is, by default, FetchType.EAGER which means it will be always fetched. But, it could not be what you want. What you say as I can control what is lazily loaded can be translated as Fetching Strategy. POJO in Action book supports a pattern like this one (Notice method signature)
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();
}
}
So based on your use case, you can create your own find...With...And... method. It will take care of fetching just what you want
But it has a problem: It does not support a generic method signature. For each @Entity repository, you have to define your custom find...With...And method. Because of that, i show you how i define a generic repository
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);
}
But, sometimes, you do not want all of methods defined by generic Repository interface. Solution: create an AbstractRepository class which will implement a dummy repository. Spring Framework, for instance, heavily use this kind of pattern Interface >> 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.");
}
}
So your GiftCardRepository can be re-written as (See extends instead of implements) and just overrides what you really want
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 {}
}
Now we externalize our named query in a multiline - and readable and maintainable - xml file
// 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>
So if you want to retrieve you GiftCard with Buyer, just call
Repository<GiftCard, GiftCard, String> giftCardRepository;
List<GiftCard> giftCardList = giftCardRepository.findAll(GiftCardRepository.GIFT_CARDS_WITH_WITH_BUYER);
And to retrieve our GiftCard without no relationship, just call
List<GiftCard> giftCardList = giftCardRepository.findAll(GiftCardRepository.GIFT_CARDS_WITHOUT_NO_RELATIONSHIP);
or use import static
import static packageTo.GiftCardRepository.*;
And
List<GiftCard> giftCardList = giftCardRepository.findAll(GIFT_CARDS_WITHOUT_NO_RELATIONSHIP);
I hope it can be useful to you!
In JPA the default fetch type for ManyToOne
associations is eager (i.e. non-lazy) so could you try with:
@ManyToOne(fetch=FetchType.LAZY)
Then, in any JPA query the association can be eagerly fetched using left join fetch
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With