Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change FetchType.LAZY to FetchType.EAGER temporary at runtime (Hibernate/JPA)

I have entity with some fields marked as LAZY. In order to load LAZY fields one must just access a getter while session is opened so ORM proxy perform select sub-queries.

But this is suboptimal in case when you need a fully resolved object, using the same strategy as "eager" relations fetched by join.

How is it possible to mark some fields as EAGER temporary only for one query?

Is that possible with JPA standard or does it require proprietary extension like Hibernate?

like image 723
gavenkoa Avatar asked Mar 28 '14 16:03

gavenkoa


3 Answers

Hibernate has a feature called Fetch Profiles that solves this problem. It requires access to Hibernate Session, but you can use unwrap() to access it from EntityManager.

If you want a pure JPA solution, you can use queries with join fetch when loading objects in use cases that require eager fetching.

UPDATE: JPA 2.1 (implemented by Hibernate 4.3) supports a feature similar to fetch profiles - entity graphs.

like image 184
axtavt Avatar answered Nov 09 '22 12:11

axtavt


Spring JpaRepository allows marking queries (including custom) with org.springframework.data.jpa.repository.EntityGraph:

@Entity Book {
     @Id Long id;
     @OneToMany List<Author> authors;
}

@EntityGraph(attributePaths = {"authors", "author.address"})
@Query("select b from Book b" +
        " where b.id in (:ids)")
List<Book> loadAll(@Param("ids") List<Long> ids);
like image 34
gavenkoa Avatar answered Nov 09 '22 10:11

gavenkoa


Due to fetch = FetchType.LAZY at the associated collection Faced problem like org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: could not initialize proxy-no session

Now changing fetch = FetchType.LAZY to fetch = FetchType.EGAR is the worst solution as it opens to some other regerations

Then i thought what if there is a way to make fetch type lazy to egar at runtime for some operations...

Then i tried to understand the problem like why i faced LazyInitializationException

SO i realized that this problems occurs because we are doing db operation after the session is closed so we have to make all db operations within the session

So the solution is to wrap the method which has as db operation performed within a Transaction . Check below code snippet-->

 @Transactional
    public void fetchThroughLazy() {

        List<UserEntity> userList = aeqplUserRepository.findByEmailEndingWith("@amazon.com");
        userList.forEach(user->{
            logger.info("user --->{}",user);
            logger.info("user permission--->{}",user.getPermissions());
        });
    }

But again this is not a good solution because it will lead to Hibernate N+1 problem

Best Solution---> Either Use EntityGraph or Join Fetch

Check Below code snippet --->>

public void fetchThroughJPQLJoinFetch() {

            List<UserEntity> userList = entityManager.createQuery("select distinct u from UserEntity u left join fetch u.permissions Where u.email like :endsWith",UserEntity.class)
            .setParameter("endsWith", "%@google.com")
            .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
            .getResultList();
        userList.forEach(user->{
            logger.info("user --->{}",user);
            logger.info("user permission--->{}",user.getPermissions());
        });
        
    }
    
    public void fetchThroughJPQLEntityGraph() {
        EntityGraph<UserEntity> userEntityGraph = em.createEntityGraph(UserEntity.class);
        userEntityGraph.addSubgraph("permissions");
        
        List<UserEntity> userList = em.createQuery("Select u From UserEntity u Where u.email Like :endsWith",UserEntity.class)
            .setParameter("endsWith", "%@facebook.com")
            .setHint(QueryHints.LOADGRAPH, userEntityGraph)
            .getResultList();
        userList.forEach(user->{
            logger.info("user --->{}",user);
            logger.info("user permission--->{}",user.getPermissions());
        });
    }


@Entity
@Table(name="user")
public class UserEntity {

    @Id
    @Column(name = "id")
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "email")
    private String email;
    
    @OneToMany(mappedBy="userEntity",cascade = CascadeType.All,fetch= FetchType.LAZY)
    private List<PermissionEntity> cmList =new ArrayList<>();

    //setters and getters
}

@Entity
@Table(name="permission")
public class PermissionEntity {

    @Id
    @Column(name = "id")
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "type")
    private String type;
    
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private UserEntity userEntity;

    //setters and getters
}

Note-> Avoid using Join Fetch more Than once otherwise it will throw MultipleBagFetchException

like image 41
abhinav kumar Avatar answered Nov 09 '22 11:11

abhinav kumar