欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot項(xiàng)目中建議關(guān)閉Open-EntityManager-in-view原因

 更新時(shí)間:2022年02月24日 15:07:22   作者:kl  
這篇文章主要為大家解析了在Spring Boot項(xiàng)目中建議關(guān)閉Open-EntityManager-in-view的原因示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助

前言

一天,開(kāi)發(fā)突然找過(guò)來(lái)說(shuō)KLock分布式鎖失效了,高并發(fā)情況下沒(méi)有鎖住請(qǐng)求,導(dǎo)致數(shù)據(jù)庫(kù)拋樂(lè)觀鎖的異常。一開(kāi)始我是不信的,KLock是經(jīng)過(guò)線上大量驗(yàn)證的,怎么會(huì)出現(xiàn)這么低級(jí)的問(wèn)題呢?然后,協(xié)助開(kāi)發(fā)一起排查了一下午,最后經(jīng)過(guò)不懈努力和一探到底的摸索精神最終查明不是KLock鎖的問(wèn)題,問(wèn)題出在Spring Data Jpa的Open-EntityManager-in-view這個(gè)配置上,這里先建議各位看官關(guān)閉Open-EntityManager-in-view,具體緣由下面慢慢道來(lái)

問(wèn)題背景

假設(shè)我們有一張賬戶表account,業(yè)務(wù)邏輯是先用id查詢出來(lái),校驗(yàn)下,然后用于其他的邏輯操作,最后在用id查詢出來(lái)更新這個(gè)account,業(yè)務(wù)流程如下:

  • 請(qǐng)求一:
    查詢id =6的記錄,此時(shí)JpaVersion =6,業(yè)務(wù)處理,再次查詢id =6的記錄,JpaVersion =6,然后更新數(shù)據(jù)提交
  • 請(qǐng)求二:
    查詢id =6的記錄,此時(shí)JpaVersion =6, 業(yè)務(wù)處理,此時(shí)請(qǐng)求一結(jié)束了,再次查詢id=6的記錄,JpaVersion =6,更新數(shù)據(jù)提交失敗

首先,請(qǐng)求一和請(qǐng)求二是模擬的并發(fā)請(qǐng)求,然后問(wèn)題出在,當(dāng)請(qǐng)求一事務(wù)正常提交結(jié)束后,請(qǐng)求二最后一次查詢的JpaVersion還是沒(méi)有變化,導(dǎo)致了當(dāng)前版本和數(shù)據(jù)庫(kù)中的版本不一致二拋樂(lè)觀鎖異常,而KLock鎖是加在第二次查詢更新的方法上面的,可以肯定KLock鎖沒(méi)有問(wèn)題,鎖住了請(qǐng)求,直到請(qǐng)求一結(jié)束后,請(qǐng)求二才進(jìn)方法。

2019-11-20 18:32:00.573 [/] pay-settlement-app [http-nio-8086-exec-4] ERROR c.k.p.p.s.a.e.ControllerExceptionHandler - Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:320)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)

OPEN-ENTITYMANAGER-IN-VIEW的前世今生

Open-EntityManager-in-view簡(jiǎn)述下就是在視圖層打開(kāi)EntityManager,spring boot2.x中默認(rèn)是開(kāi)啟這個(gè)配置的,作用是綁定EntityManager到當(dāng)前線程中,然后在試圖層就開(kāi)啟Hibernate Session。用于在Controller層直接操作游離態(tài)的對(duì)象,以及懶加載查詢。在應(yīng)用配置中可以使用spring.jpa.open-in-view=true/false來(lái)開(kāi)啟和關(guān)閉它,最終控制的其實(shí)是OpenEntityManagerInViewInterceptor攔截器,如果開(kāi)啟就添加此攔截器,如果關(guān)閉則不添加。然后在這個(gè)攔截器中會(huì)開(kāi)啟連接,打開(kāi)Session,業(yè)務(wù)Controller執(zhí)行完畢后關(guān)閉資源。打開(kāi)關(guān)閉代碼如下:

public void preHandle(WebRequest request) throws DataAccessException {
		String key = getParticipateAttributeName();
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) {
			return;
		}
		EntityManagerFactory emf = obtainEntityManagerFactory();
		if (TransactionSynchronizationManager.hasResource(emf)) {
			// Do not modify the EntityManager: just mark the request accordingly.
			Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST);
			int newCount = (count != null ? count + 1 : 1);
			request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
		}
		else {
			logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
			try {
				EntityManager em = createEntityManager();
				EntityManagerHolder emHolder = new EntityManagerHolder(em);
				TransactionSynchronizationManager.bindResource(emf, emHolder);
				AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
				asyncManager.registerCallableInterceptor(key, interceptor);
				asyncManager.registerDeferredResultInterceptor(key, interceptor);
			}
			catch (PersistenceException ex) {
				throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
			}
		}
	}
	public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException {
		if (!decrementParticipateCount(request)) {
			EntityManagerHolder emHolder = (EntityManagerHolder)
					TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory());
			logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
			EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
		}
	}

在Spring MVC時(shí)代,懶加載的問(wèn)題也比較常見(jiàn),那個(gè)時(shí)候是通過(guò)定義一個(gè)OpenEntityManagerInViewFilter的過(guò)濾器解決問(wèn)題的,效果和攔截器是一樣的,算是同門師兄弟的關(guān)系。如果沒(méi)有配置,在懶加載的場(chǎng)景下就會(huì)拋出LazyInitializationException的異常。

問(wèn)題的真實(shí)原因

了解了Open-EntityManager-in-view后,我們來(lái)分析下具體的原因。由于在view層就開(kāi)啟Session了,導(dǎo)致了同一個(gè)請(qǐng)求第二次查詢時(shí)根本就沒(méi)走數(shù)據(jù)庫(kù),直接獲取的Hibernate Session緩存中的數(shù)據(jù),此時(shí)無(wú)論怎么加鎖,都讀不到數(shù)據(jù)庫(kù)中的數(shù)據(jù),所以只要有并發(fā)就會(huì)拋樂(lè)觀鎖異常。這讓我聯(lián)想到了老早前一個(gè)同事和我說(shuō)的他們遇到的一個(gè)并發(fā)問(wèn)題,即使給@Transactional事務(wù)的隔離級(jí)別設(shè)置為串行化執(zhí)行,還是會(huì)報(bào)樂(lè)觀鎖的異常。有可能就是這個(gè)問(wèn)題導(dǎo)致的,在這個(gè)案例中,加鎖不好使,即使使用數(shù)據(jù)庫(kù)的串行化隔離級(jí)別也不好使。因?yàn)榈诙尾樵兏揪筒蛔邤?shù)據(jù)庫(kù)了。

解決方案

真實(shí)原因已經(jīng)定位到了,KL博主給出了幾種方案解決問(wèn)題,如下:

  • 方案一、將KLock前置,把加分布式鎖的邏輯移到第一次使用id查詢之前,即讓查詢發(fā)生在別的請(qǐng)求事務(wù)結(jié)束之前,這樣無(wú)論第一次查詢還是第二次查詢獲取到的都是別的事務(wù)已提交的內(nèi)容
  • 方案二、使用spring.jpa.open-in-view=false關(guān)閉,這個(gè)方案比較簡(jiǎn)單粗暴,但是影響會(huì)比較大,其他的代碼很可能已經(jīng)依賴了懶加載的功能特性,貿(mào)然去掉會(huì)帶來(lái)大量的回歸測(cè)試工作,所以雖然博主建議關(guān)閉這個(gè)特性,但是在已經(jīng)使用了的系統(tǒng)中不推薦
  • 方案三、局部控制Open-EntityManager-in-view行為,就是人為編碼控制EntityManager的綁定,在有影響的地方先取消綁定,然后執(zhí)行完后在添加回來(lái),不添加回來(lái)會(huì)導(dǎo)致Jpa自己的解綁邏輯報(bào)錯(cuò)。代碼如下:
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/20
 */
@Component
public class OpenEntityManagerInViewManager extends EntityManagerFactoryAccessor {
    public void cancel() {
        EntityManagerFactory emf = obtainEntityManagerFactory();
        EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResourceIfPossible(emf);
        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
    }
    public void add() {
        EntityManagerFactory emf = obtainEntityManagerFactory();
        if (!TransactionSynchronizationManager.hasResource(emf)) {
            EntityManager em = createEntityManager();
            EntityManagerHolder emHolder = new EntityManagerHolder(em);
            TransactionSynchronizationManager.bindResource(emf,emHolder);
        }
    }
}
  • 方案四:方案三為了達(dá)到效果有點(diǎn)費(fèi)勁哈,其實(shí)還有一種方案,在第二次查詢前使用EntityManager的clear清除Session緩存即可,

建議關(guān)閉OPEN-ENTITYMANAGER-IN-VIEW

在Spring boot2.x中,如果沒(méi)有顯示配置spring.jpa.open-in-view,默認(rèn)開(kāi)啟的這個(gè)特性Spring會(huì)給出一個(gè)警告提示:

logger.warn("spring.jpa.open-in-view is enabled by default. "
                        + "Therefore, database queries may be performed during view "
                        + "rendering. Explicitly configure spring.jpa.open-in-view to disable this warning");

用來(lái)告訴你,我開(kāi)啟這個(gè)特性了,你可以顯示配置來(lái)關(guān)閉這個(gè)提示。博主猜測(cè)就是告知用戶,你可能用不著吧。確實(shí),現(xiàn)在微服務(wù)中的應(yīng)用在使用Spring Data JPA時(shí),已經(jīng)很少使用懶加載的特性了。而且如果你的代碼規(guī)范點(diǎn),也用不著直接在Controller層寫Dao層的代碼??偨Y(jié)下就是根本就不需要Open-EntityManager-in-view的特性,然后它還有副作用,開(kāi)啟Open-EntityManager-in-view,會(huì)使數(shù)據(jù)庫(kù)租用連接時(shí)長(zhǎng)變長(zhǎng),長(zhǎng)時(shí)間占用連接直接影響整體事務(wù)吞吐量。然后一不小心就會(huì)陷進(jìn)Session緩存的坑里。所以,新項(xiàng)目就直接去掉吧,老項(xiàng)目去掉后回歸驗(yàn)證下

結(jié)語(yǔ)

因?yàn)閷?duì)業(yè)務(wù)不熟悉,不知道業(yè)務(wù)邏輯中查詢了兩次相同的實(shí)體,導(dǎo)致整個(gè)排錯(cuò)過(guò)程比較曲折。先是開(kāi)發(fā)懷疑鎖的問(wèn)題,驗(yàn)證鎖沒(méi)問(wèn)題后,又陷進(jìn)了IDEA斷點(diǎn)的問(wèn)題,因?yàn)槟M的并發(fā)請(qǐng)求,斷點(diǎn)釋放一次會(huì)通過(guò)多個(gè)請(qǐng)求,看上去就像很多請(qǐng)求沒(méi)進(jìn)來(lái)一樣。然后又懷疑了事務(wù)和加鎖前后的邏輯問(wèn)題,如果釋放鎖在釋放事務(wù)前就會(huì)有問(wèn)題,將斷點(diǎn)打在了JDBC的Commit方法里,確認(rèn)了這個(gè)也是正常的。最后才聯(lián)想到Spring boot中默認(rèn)開(kāi)啟了spring.jpa.open-in-view,會(huì)不會(huì)有關(guān)系,也不確定,懷著死馬當(dāng)活馬醫(yī)的心態(tài)試了下,果然是這個(gè)導(dǎo)致的,這個(gè)時(shí)候只知道是這個(gè)導(dǎo)致的,還沒(méi)發(fā)現(xiàn)是這個(gè)導(dǎo)致的Session問(wèn)題,以為是進(jìn)KLock前就開(kāi)啟了事務(wù)鎖定了數(shù)據(jù)庫(kù)版本記錄,所以查詢的時(shí)候返回的老的記錄,最后把事務(wù)串行化后還不行,才發(fā)現(xiàn)的業(yè)務(wù)查詢了兩次進(jìn)而發(fā)現(xiàn)了Session緩存的問(wèn)題。至此,水落石出,所有問(wèn)題迎刃而解。

以上就是SpringBoot項(xiàng)目中建議關(guān)閉Open-EntityManager-in-view原因的詳細(xì)內(nèi)容,更多關(guān)于Spring Boot關(guān)閉Open-EntityManager-in-view的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

 

相關(guān)文章

  • 關(guān)于Nacos和Eureka的區(qū)別及說(shuō)明

    關(guān)于Nacos和Eureka的區(qū)別及說(shuō)明

    這篇文章主要介紹了關(guān)于Nacos和Eureka的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 面試Spring中的bean線程是否安全及原因

    面試Spring中的bean線程是否安全及原因

    這篇文章主要為大家介紹了面試中常問(wèn)的Spring中bean線程是否安全及原因,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-03-03
  • springboot 集成支付寶支付的示例代碼

    springboot 集成支付寶支付的示例代碼

    這篇文章主要介紹了springboot 集成支付寶支付的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • 深入了解Spring控制反轉(zhuǎn)IOC原理

    深入了解Spring控制反轉(zhuǎn)IOC原理

    IOC-Inversion?of?Control,即控制反轉(zhuǎn)。它不是什么技術(shù),而是一種設(shè)計(jì)思想。這篇文章將為大家介紹一下Spring控制反轉(zhuǎn)IOC的原理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • Spring?Boot?教程之創(chuàng)建項(xiàng)目的三種方式

    Spring?Boot?教程之創(chuàng)建項(xiàng)目的三種方式

    這篇文章主要分享了Spring?Boot?教程之創(chuàng)建項(xiàng)目的三種方式,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • JAVA多線程間通訊常用實(shí)現(xiàn)方法解析

    JAVA多線程間通訊常用實(shí)現(xiàn)方法解析

    這篇文章主要介紹了JAVA多線程間通訊常用實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Spring Bean屬性注入的兩種方式詳解

    Spring Bean屬性注入的兩種方式詳解

    Spring 屬性注入(DI依賴注入)有兩種方式:setter注入,構(gòu)造器注入。本文將詳細(xì)為大家介紹一下這兩種方式的具體用法,感興趣的可以了解一下
    2022-06-06
  • Java中的Phaser使用詳解

    Java中的Phaser使用詳解

    這篇文章主要介紹了Java中的Phaser使用詳解,與其他障礙不同,注冊(cè)在phaser上進(jìn)行同步的parties數(shù)量可能會(huì)隨時(shí)間變化,任務(wù)可以隨時(shí)進(jìn)行注冊(cè),需要的朋友可以參考下
    2023-11-11
  • Java調(diào)用dll文件的實(shí)現(xiàn)解析

    Java調(diào)用dll文件的實(shí)現(xiàn)解析

    這篇文章主要介紹了Java調(diào)用dll文件的實(shí)現(xiàn)解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • 使用String轉(zhuǎn)換到Map結(jié)構(gòu)

    使用String轉(zhuǎn)換到Map結(jié)構(gòu)

    這篇文章主要介紹了使用String轉(zhuǎn)換到Map結(jié)構(gòu),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評(píng)論