The best way to handle the LazyInitializationException - Vlad Mihalcea

标签: | 发表时间:2018-03-14 11:00 | 作者:
出处:https://vladmihalcea.com
(Last Updated On: January 29, 2018)

Introduction

The LazyInitializationExceptionis undoubtedly one of the most common exceptions you can get when using Hibernate. This article is going to summarize the best and the worst ways of handling lazy associations.

Fetching 101

With JPA, not only you can fetch entities from the database, but you can also fetch entity associations as well. For this reason, JPA defines two FetchTypestrategies:

  • EAGER
  • LAZY

The problem with EAGER fetching

EAGERfetching means that associations are always retrieved along with their parent entity. In reality, EAGER fetching is very bad from a performance perspectivebecause it’s very difficult to come up with a global fetch policy that applies to every business use case you might have in your enterprise application.

Once you have an EAGERassociation, there is no way you can make it LAZY. This way, the association will always be fetched even if the user does not necessarily need it for a particular use case. Even worse, if you forget to specify that an EAGER association needs to be JOIN FETCH-ed by a JPQL query, Hibernate is going to issue a secondary select for every uninitialized association, leading to N+1 query problems.

Unfortunately, JPA 1.0 decided that @ManyToOneand @OneToOneshould default to FetchType.EAGER, so now you have to explicitly mark these two associations as FetchType.LAZY:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

LAZY fetching

For this reason, it’s better to use LAZYassociations. A LAZYassociation is exposed via a Proxy, which allows the data access layer to load the association on demand. Unfortunately, LAZYassociations can lead to LazyInitializationException.

For our next example, we are going to use the following entities:

postpostcommentlazyinitializationexception

When executing the following logic:

List<PostComment> comments = null;

EntityManager entityManager = null;
EntityTransaction transaction = null;
try {
    entityManager = entityManagerFactory()
        .createEntityManager();
    transaction = entityManager.getTransaction();
    transaction.begin();

    comments = entityManager.createQuery(
        "select pc " +
        "from PostComment pc " +
        "where pc.review = :review", PostComment.class)
    .setParameter("review", review)
    .getResultList();

    transaction.commit();
} catch (Throwable e) {
    if (transaction != null && 
        transaction.isActive())
        transaction.rollback();
    throw e;
} finally {
    if (entityManager != null) {
        entityManager.close();
    }
}

try {
    for(PostComment comment : comments) {
        LOGGER.info(
            "The post title is '{}'", 
            comment.getPost().getTitle()
        );
    }
} catch (LazyInitializationException expected) {
    assertEquals(
        "could not initialize proxy - no Session", 
        expected.getMessage()
    );
}

Hibernate is going to throw a LazyInitializationExceptionbecause the PostCommententity did not fetch the Postassociation while the EntityManagerwas still opened, and the Postrelationship was marked with FetchType.LAZY:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

How NOT to handle LazyInitializationException

Unfortunately, there are also bad ways of handling the LazyInitializationExceptionlike:

These two Anti-Patterns are very inefficient from a database perspective, so you should never use them in your enterprise application.

JOIN FETCH to the rescue

Entities are only needed when the current running application-level transactionneeds to modify the entities that are being fetched. Because of the automatic dirty checking mechanism, Hibernate makes it very easy to translate entity state transitionsinto SQL statements.

Considering that we need to modify the PostCommententities, and we also need the Postentities as well, we just need to use the JOIN FETCHdirective like in the following query:

comments = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "join fetch pc.post " +
    "where pc.review = :review", PostComment.class)
.setParameter("review", review)
.getResultList();

The JOIN FETCHdirective instructs Hibernate to issue an INNER JOIN so that Postentities are fetched along with the PostCommentrecords:

SELECT pc.id AS id1_1_0_ ,
       p.id AS id1_0_1_ ,
       pc.post_id AS post_id3_1_0_ ,
       pc.review AS review2_1_0_ ,
       p.title AS title2_0_1_
FROM   post_comment pc
INNER JOIN post p ON pc.post_id = p.id
WHERE  pc.review = 'Excellent!'

That’s it! It’s as simple as that!

DTO projection to the rescue

Now, we are not done yet. What if you don’t even want entities in the first place. If you don’t need to modify the data that’s being read, why would you want to fetch an entity in the first place? A DTO projectionallows you to fetch fewer columns and you won’t risk any LazyInitializationException.

For instance, we can have the following DTO class:

public class PostCommentDTO {

    private final Long id;

    private final String review;

    private final String title;

    public PostCommentDTO(
        Long id, String review, String title) {
        this.id = id;
        this.review = review;
        this.title = title;
    }

    public Long getId() {
        return id;
    }

    public String getReview() {
        return review;
    }

    public String getTitle() {
        return title;
    }
}

If the business logic only needs a projection, DTOs are much more suitable than entities. The previous query can be rewritten as follows:

List<PostCommentDTO> comments = doInJPA(entityManager -> {
    return entityManager.createQuery(
        "select new " +
        "   com.vladmihalcea.book.hpjp.hibernate.fetching.PostCommentDTO(" +
        "       pc.id, pc.review, p.title" +
        "   ) " +
        "from PostComment pc " +
        "join pc.post p " +
        "where pc.review = :review", PostCommentDTO.class)
    .setParameter("review", review)
    .getResultList();
});

for(PostCommentDTO comment : comments) {
    LOGGER.info("The post title is '{}'", comment.getTitle());
}

And Hibernate can execute a SQL query which only needs to select threecolumns instead of five:

SELECT pc.id AS col_0_0_ ,
       pc.review AS col_1_0_ ,
       p.title AS col_2_0_
FROM   post_comment pc
INNER JOIN post p ON pc.post_id = p.id
WHERE  pc.review = 'Excellent!'

Not only that we got rid of the LazyInitializationException, but the SQL query is even more efficient. Cool, right?

If you enjoyed this article, I bet you are going to love my Bookand Video Coursesas well.

Conclusion

LazyInitializationExceptionis a code smell because it might hide the fact that entities are used instead of DTO projections. Sometimes, fetching entities is the right choice, in which case, a JOIN FETCHdirective is the simplest and the best way to initialize the LAZYHibernate proxies.


相关 [the best way] 推荐:

The best way to handle the LazyInitializationException - Vlad Mihalcea

- -
This way, the association will always be fetched even if the user does not necessarily need it for a particular use case. Even worse, if you forget to specify that an EAGER association needs to be JOIN FETCH-ed by a JPQL query, Hibernate is going to issue a secondary select for every uninitialized association, leading to.

The Right Way to Architect iOS App with Swift

- - limboy's HQ
关于 iOS 架构的文章感觉已经泛滥了,前一阵正好 Android 官方推了一套. App Architecture ,于是就在想,对于 iOS 来说,怎样的架构才是最适合的. 这是第一个也是最重要的问题,为什么会出现各种 Architecture Pattern. 我们来想一下,无论是做一个 App 还是搭一套后台系统,如果是一次性的,今天用完明天就可以扔掉,那么怎么快怎么来,代码重复、代码逻辑、代码格式统统不重要.

N9,the best phone you should NOT buy

- archer - 爱范儿 · Beats of Bits
这标题不是我发明的,它来自 Engadget 的一条评论. 这条评论针对的文章题为《Nokia’s Stephen Elop is still over MeeGo, even if the N9 is a hit》. 没错,就是《即使 N9 成功了,史蒂芬·艾洛普仍会放弃 MeeGo》. 根据芬兰当地报纸《HELSINGIN SANOMAT》上周采访了艾洛普,采访中,艾洛普表示:.

免费电子书:《Learn Vimscript the Hard Way》

- sunxphere - LinuxTOY
Vim 令人喜爱的地方之一是它支持通过插件来扩展自己,从而满足不同用户的需要. 如果你想为 Vim 编写插件,那么就必须学习 Vimscript 这个内建于 Vim 中的脚本语言. Steve Losh 的《Learn Vimscript the Hard Way》这本免费的电子书恰好可以让你对 Vimscript 上手.

[小技巧] JavaScript Cross Browser Best Practices

- - 小惡魔 - 電腦技術 - 工作筆記 - AppleBOY
我們來看看 Javascript 的小技巧. 不要再使用 navigator.userAgent. 簡單來說 Canvas 在 IE9 才有支援,所以針對 IE 部份,我們使用 navigator.userAgent 來判斷. 但是如果遇到 Safari, Chrome, Android, IPad, IPhone 版本呢,也很好解決,就是一直些判斷式,那為什麼不換個角度去想,直接判斷有無 Cnavas 功能即可,透過 Modernizr 套件可以簡單做到.

Easy way—不要被定势思维绑死了!

- 斌 - 乐淘吧
【3】【Easy way】不要被定势思维绑死了~. 【5】快乐男生不为人知的秘密. 换个角度,意境就变了,自己找亮点……. 【9】2011年度 最新款凉鞋. 【10】毕老师,你在想啥呢~. 【11】老人常教导年轻人,做人不能太直白,可是好像有个部位除外……. 【12】【10个可考虑跳槽的信号】1工作中学不到新知识.

宅男斗胆翻唱Love the way you lie – Eminem ft. Rihanna

- Linlun - 河蟹娱乐
这一切的亮点都是陪衬,对都是浮云,一切一切都被你不停张动的嘴所散发的能照亮宇宙的光芒和能穿透时间的发音所深深的掩盖,不的不说很牛B. 囧囧有神的兔斯基 love love love. 史上最蛋疼的一首歌I have no penis. 原文链接: http://hxyl.net/2011/04/13/love-the-way-you-lie/.

网络图书:《Learn Ruby The Hard Way》繁体版本

- MessyCS - 道喜技术日记 .^. 天天红玉世界
《Learn Ruby The Hard Way》繁体版本.

白色版 HTC Droid Incredible 2 在美国 Best Buy 有售

- ArmadilloCommander - Engadget 中国版
在两个多星期前,我们曾经报导过白色版本的 HTC Droid Incredible 2 (港台两地称 Incredible S)有可能会在美国电讯商 Verizon 发售;而美国 Best Buy 在昨天以行动证实了这个传闻. 由 8 月 21 日起,当地消费者便可以从该网站以 US$149.99 和两年的 Verizon 合约将这小白带回家.

Galaxy Tab 8.9 WiFi 抵达 Best Buy,美国本周开卖

- ZeeJee - Engadget 中国版
三星 Galaxy Tab 8.9 WiFi 逐渐揭开神秘的面纱,马上就要和消费者见面了. 这次我们来看看美国方面的消息,据主站所知,这款 8.9 寸平板已经抵达 Best Buy ,最快的发售日是在 9 月 22 日,不过是指定的一些门店才有,而其它零售店要等到 25 日. 美国地区售价未知,不过你可以参考英国的售价.