日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用Hibernate批量获取

發(fā)布時間:2023/12/3 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用Hibernate批量获取 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

如果需要從Java處理大型數(shù)據(jù)庫結(jié)果集,則可以選擇JDBC,以提供所需的低級控制。 另一方面,如果您已在應(yīng)用程序中使用ORM,則回退到JDBC可能意味著額外的麻煩。 在域模型中導(dǎo)航時,您將失去樂觀鎖定,緩存,自動獲取等功能。 幸運(yùn)的是,大多數(shù)ORM,例如Hibernate,都有一些選項可以幫助您。 盡管這些技術(shù)不是新技術(shù),但有兩種可能可供選擇。

一個簡化的例子; 假設(shè)我們有一個表(映射到類'DemoEntity'),具有100.000條記錄。 每個記錄都由一個列(映射到DemoEntity中的屬性“ property”)組成,其中包含一些大約2KB的隨機(jī)字母數(shù)字?jǐn)?shù)據(jù)。
JVM與-Xmx250m一起運(yùn)行。 假設(shè)250MB是可以分配給系統(tǒng)上JVM的總最大內(nèi)存。 您的工作是讀取表中當(dāng)前的所有記錄,進(jìn)行一些未進(jìn)一步指定的處理,最后存儲結(jié)果。 我們假設(shè)批量操作產(chǎn)生的實體沒有被修改。 首先,我們將首先嘗試顯而易見的方法,即執(zhí)行查詢以簡單地檢索所有數(shù)據(jù):

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {Session session = sessionFactory.getCurrentSession();List<DemoEntity> demoEntitities = (List<DemoEntity>) session.createQuery('from DemoEntity').list();for(DemoEntity demoEntity : demoEntitities){//Process and write result}return null;} });

幾秒鐘后:

Exception in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded

顯然,這不會削減。 為了解決這個問題,我們將切換到Hibernate可滾動結(jié)果集,這可能是大多數(shù)開發(fā)人員都知道的。 上面的示例指示hibernate執(zhí)行查詢,將整個結(jié)果映射到實體并返回它們。 使用滾動結(jié)果集時,記錄一次轉(zhuǎn)換為一個實體:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {Session session = sessionFactory.getCurrentSession();ScrollableResults scrollableResults = session.createQuery('from DemoEntity').scroll(ScrollMode.FORWARD_ONLY);int count = 0;while (scrollableResults.next()) {if (++count > 0 && count % 100 == 0) {System.out.println('Fetched ' + count + ' entities');}DemoEntity demoEntity = (DemoEntity) scrollableResults.get()[0];//Process and write result}return null;} });

運(yùn)行之后,我們得到:

... Fetched 49800 entities Fetched 49900 entities Fetched 50000 entities Exception in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded

盡管我們使用的是可滾動的結(jié)果集,但每個返回的對象都是一個附加對象,并成為持久性上下文(也稱為會話)的一部分。 結(jié)果實際上與我們使用“ session.createQuery('from DemoEntity')。list() '的第一個示例相同。 但是,采用這種方法,我們無法控制。 一切都在幕后發(fā)生,如果休眠完成了工作,您將獲得包含所有數(shù)據(jù)的列表。 另一方面,使用可滾動的結(jié)果集使我們迷上了檢索過程,并允許我們在需要時釋放內(nèi)存。 如我們所見,它不會自動釋放內(nèi)存,您必須指示Hibernate實際執(zhí)行此操作。 存在以下選項:

  • 處理對象后將其從持久性上下文中逐出
  • 偶爾清除整個會話

我們將選擇第一個。 在上面的示例的第13行( // Process和write result )下,我們將添加:

session.evict(demoEntity);

重要:

  • 如果您要對實體(或與它有關(guān)聯(lián)的實體進(jìn)行級聯(lián)逐出的實體)進(jìn)行任何修改,請確保在逐出或清除之前刷新會話,否則由于Hibernate的回寫而導(dǎo)致的查詢將不會發(fā)送到數(shù)據(jù)庫
  • 逐出或清除不會將實體從二級緩存中刪除。 如果啟用了二級緩存并正在使用它,并且還希望將其刪除,請使用所需的sessionFactory.getCache()。evictXxx()方法
  • 從您退出實體的那一刻起,該實體將不再附加(不再與會話關(guān)聯(lián))。 在該階段對實體所做的任何修改將不再自動反映到數(shù)據(jù)庫中。 如果您使用的是延遲加載,則訪問驅(qū)逐之前未加載的任何屬性都會產(chǎn)生著名的org.hibernate.LazyInitializationException。 因此,基本上,在逐出或清除之前,請確保已完成對該實體的處理(或至少已初始化以進(jìn)一步滿足需要)

再次運(yùn)行該應(yīng)用程序后,我們看到它現(xiàn)在已成功執(zhí)行:

... Fetched 99800 entities Fetched 99900 entities Fetched 100000 entities

順便說一句; 您還可以將查詢設(shè)置為只讀,以允許休眠狀態(tài)執(zhí)行一些其他優(yōu)化:

ScrollableResults scrollableResults = session.createQuery('from DemoEntity').setReadOnly(true).scroll(ScrollMode.FORWARD_ONLY);

這樣做只會在內(nèi)存使用方面產(chǎn)生很小的差異,在此特定的測試設(shè)置中,它使我們能夠在給定的內(nèi)存量下額外讀取約300個實體。 就我個人而言,我不會僅將此功能僅用于內(nèi)存優(yōu)化,而僅當(dāng)它適合您的整體不變性策略時才使用。 使用休眠模式,您可以使用不同的選項將實體設(shè)置為只讀:在實體本身上,整個會話為只讀,依此類推。 分別對查詢設(shè)置只讀為false可能是最不推薦的方法。 (例如,之前在會話中加載的實體將保持不受影響,可能可修改。即使查詢返回的根對象是只讀的,惰性關(guān)聯(lián)也將可修改地加載)。

好的,我們能夠處理我們的100.000條記錄,生活很美好。 但是事實證明,Hibernate對于批量操作還有另一個選擇:無狀態(tài)會話。 您可以從無狀態(tài)會話中獲取可滾動結(jié)果集,方法與從普通會話中獲取方法相同。 無狀態(tài)會話直接位于JDBC之上。 Hibernate將在幾乎“所有功能禁用”模式下運(yùn)行。 這意味著沒有持久上下文,沒有第二級緩存,沒有臟檢測,沒有延遲加載,基本上什么也沒有。 從javadoc:

/*** A command-oriented API for performing bulk operations against a database.* A stateless session does not implement a first-level cache nor interact with any * second-level cache, nor does it implement transactional write-behind or automatic * dirty checking, nor do operations cascade to associated instances. Collections are * ignored by a stateless session. Operations performed via a stateless session bypass * Hibernate's event model and interceptors. Stateless sessions are vulnerable to data * aliasing effects, due to the lack of a first-level cache. For certain kinds of * transactions, a stateless session may perform slightly faster than a stateful session.** @author Gavin King*/

它唯一要做的就是將記錄轉(zhuǎn)換為對象。 這可能是一個有吸引力的選擇,因為它可以幫助您擺脫手動驅(qū)逐/沖洗的麻煩:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {sessionFactory.getCurrentSession().doWork(new Work() {@Overridepublic void execute(Connection connection) throws SQLException {StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {ScrollableResults scrollableResults = statelessSession.createQuery('from DemoEntity').scroll(ScrollMode.FORWARD_ONLY);int count = 0;while (scrollableResults.next()) {if (++count > 0 && count % 100 == 0) {System.out.println('Fetched ' + count + ' entities');}DemoEntity demoEntity = (DemoEntity) scrollableResults.get()[0];//Process and write result }} finally {statelessSession.close();}}});return null;} });

除了無狀態(tài)會話具有最佳的內(nèi)存使用情況外,使用它還會帶來一些副作用。 您可能已經(jīng)注意到,我們正在打開一個無狀態(tài)會話并顯式關(guān)閉它:既沒有sessionFactory.getCurrentStatelessSession()也沒有(在撰寫本文時)任何用于管理無狀態(tài)會話的Spring集成。打開無狀態(tài)會話會分配一個新的Java。默認(rèn)情況下sql.Connection(如果使用openStatelessSession() )執(zhí)行其工作,因此間接產(chǎn)生第二個事務(wù)。 您可以通過使用Hibernate work API來減輕這些副作用,如提供當(dāng)前Connection并將其傳遞給openStatelessSession(Connection connection)的示例中所示。 最后關(guān)閉會話對物理連接沒有影響,因為它是由Spring基礎(chǔ)結(jié)構(gòu)捕獲的:打開無狀態(tài)會話時,僅關(guān)閉邏輯連接句柄,并創(chuàng)建新的邏輯連接句柄。

還要注意,您必須自己關(guān)閉無狀態(tài)會話,并且上面的示例僅適用于只讀操作。 從您打算使用無狀態(tài)會話進(jìn)行修改的那一刻起,還有一些警告。 如前所述,休眠模式在“所有功能都已禁用”模式下運(yùn)行,因此直接導(dǎo)致實體以分離狀態(tài)返回。 對于您修改的每個實體,您都必須顯式調(diào)用: statelessSession.update(entity) 。 首先,我嘗試使用此方法來修改實體:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {sessionFactory.getCurrentSession().doWork(new Work() {@Overridepublic void execute(Connection connection) throws SQLException {StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {DemoEntity demoEntity = (DemoEntity) statelessSession.createQuery('from DemoEntity where id = 1').uniqueResult();demoEntity.setProperty('test');statelessSession.update(demoEntity);} finally {statelessSession.close();}}});return null;} });

這個想法是我們用現(xiàn)有的數(shù)據(jù)庫Connection打開一個無狀態(tài)會話。 正如StatelessSession javadoc指示不會發(fā)生任何回寫一樣,我確信無狀態(tài)會話執(zhí)行的每個語句都將直接發(fā)送到數(shù)據(jù)庫。 最終,當(dāng)提交事務(wù)(由TransactionTemplate開始)時,結(jié)果將在數(shù)據(jù)庫中可見。 但是,hibernate使用無狀態(tài)會話來執(zhí)行BATCH語句。 我不是100%知道批處理和回寫之間有什么區(qū)別,但是結(jié)果是相同的,因此與javadoc的字典相反,因為語句在以后排入隊列并刷新。 因此,如果您沒有做任何特別的事情,批處理的語句將不會被刷新,這就是我的情況:'statelessSession.update(demoEntity);' 被分批處理,從不沖洗。 強(qiáng)制刷新的一種方法是使用休眠事務(wù)API:

StatelessSession statelessSession = sessionFactory.openStatelessSession(); statelessSession.beginTransaction(); ... statelessSession.getTransaction().commit(); ...

在這種情況下,您可能不希望僅因為使用無狀態(tài)會話而開始以編程方式控制交易。 此外,由于沒有傳遞我們的Connection,因此我們再次在第二個事務(wù)場景中運(yùn)行無狀態(tài)會話工作,因此將獲得新的數(shù)據(jù)庫連接。 我們無法通過外部連接的原因是,如果我們提交內(nèi)部事務(wù)(“無狀態(tài)會話事務(wù)”),并且它將使用與外部事務(wù)相同的連接(由TransactionTemplate開始),則會破壞外部事務(wù)事務(wù)的原子性,因為將外部事務(wù)發(fā)送到數(shù)據(jù)庫的語句與內(nèi)部事務(wù)一起提交。 因此,不通過連接意味著打開一個新的連接,從而創(chuàng)建第二筆交易。 更好的選擇是觸發(fā)Hibernate刷新無狀態(tài)會話。 但是,statelessSession沒有“ flush”方法來手動觸發(fā)刷新。 解決方案是稍微依賴Hibernate內(nèi)部API。 該解決方案使手動事務(wù)處理和第二個事務(wù)處理變得過時:所有語句成為我們(唯一的)外部事務(wù)的一部分:

StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {DemoEntity demoEntity = (DemoEntity) statelessSession.createQuery('from DemoEntity where id = 1').uniqueResult();demoEntity.setProperty('test');statelessSession.update(demoEntity);((TransactionContext) statelessSession).managedFlush();} finally {statelessSession.close(); }

幸運(yùn)的是,最近在Spring jira上發(fā)布了一個更好的解決方案: https : //jira.springsource.org/browse/SPR-2495這還不是Spring的一部分,但是工廠bean的實現(xiàn)非常簡單: StatelessSessionFactoryBean。使用此Java時,您可以簡單地注入StatelessSession:

@Autowired private StatelessSession statelessSession;

它將注入一個無狀態(tài)的會話代理,這等效于正常的“當(dāng)前”會話的工作方式(稍有不同的是,您注入一個SessionFactory并且每次都需要獲取currentSession)。 調(diào)用代理時,它將查找綁定到正在運(yùn)行的事務(wù)的無狀態(tài)會話。 如果已經(jīng)不存在,它將創(chuàng)建一個與普通會話相同的連接(就像我們在示例中所做的那樣),并為無狀態(tài)會話注冊自定義事務(wù)同步。 提交事務(wù)后,由于同步,將刷新無狀態(tài)會話,并最終將其關(guān)閉。 使用此方法,您可以直接注入無狀態(tài)會話,并將其用作當(dāng)前會話(或與注入JPA PeristentContext相同的方式)。 這使您不必處理無狀態(tài)會話的打開和關(guān)閉,而不必處理一種或多種方法以使其變得暢通無阻。 該實現(xiàn)是針對JPA的,但是JPA部分僅限于在getPhysicalConnection()中獲得物理連接。 您可以輕松地省略EntityManagerFactory并直接從Hibernate會話獲取物理連接。

非常謹(jǐn)慎的結(jié)論:最好的方法顯然取決于您的情況。 如果您使用普通會話,則在讀取或保留實體時必須自行解決。 如果您有一個混合事務(wù),那么除了必須手動執(zhí)行操作之外,還可能影響會話的進(jìn)一步使用。 你們都在同一筆交易中執(zhí)行“批量”和“正常”操作。 如果繼續(xù)進(jìn)行正常操作,您將在會話中分離實體,這可能會導(dǎo)致意外結(jié)果(因為臟檢測將不再起作用,依此類推)。 另一方面,您仍將具有主要的休眠好處(只要不驅(qū)逐該實體),例如延遲加載,緩存,臟檢測等。 在編寫本文時使用無狀態(tài)會話需要額外注意管理它(打開,關(guān)閉和刷新),這也容易出錯。 假設(shè)您可以繼續(xù)使用建議的工廠bean,那么您將擁有一個非常裸露的會話,該會話與正常會話是分開的,但仍參與同一事務(wù)。 使用此工具,您無需執(zhí)行內(nèi)存管理即可擁有執(zhí)行批量操作的強(qiáng)大工具。 缺點是您沒有其他可用的休眠功能。

參考:在Koen Serneels – Technology博客博客上,從我們的JCG合作伙伴 Koen Serneels 與Hibernate進(jìn)行批量獲取 。

翻譯自: https://www.javacodegeeks.com/2013/03/bulk-fetching-with-hibernate.html

總結(jié)

以上是生活随笔為你收集整理的使用Hibernate批量获取的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。