使用Hibernate批量获取
如果需要從Java處理大型數(shù)據(jù)庫(kù)結(jié)果集,則可以選擇JDBC,以提供所需的低級(jí)控制。 另一方面,如果您已在應(yīng)用程序中使用ORM,則回退到JDBC可能意味著額外的麻煩。 在域模型中導(dǎo)航時(shí),您將失去樂(lè)觀鎖定,緩存,自動(dòng)獲取等功能。 幸運(yùn)的是,大多數(shù)ORM,例如Hibernate,都有一些選項(xiàng)可以幫助您。 盡管這些技術(shù)不是新技術(shù),但有兩種可能可供選擇。
一個(gè)簡(jiǎn)化的例子; 假設(shè)我們有一個(gè)表(映射到類(lèi)'DemoEntity'),具有100.000條記錄。 每個(gè)記錄都由一個(gè)列(映射到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)一步指定的處理,最后存儲(chǔ)結(jié)果。 我們假設(shè)批量操作產(chǎn)生的實(shí)體沒(méi)有被修改。 首先,我們將首先嘗試顯而易見(jiàn)的方法,即執(zhí)行查詢以簡(jiǎn)單地檢索所有數(shù)據(jù):
幾秒鐘后:
Exception in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded
顯然,這不會(huì)削減。 為了解決這個(gè)問(wèn)題,我們將切換到Hibernate可滾動(dòng)結(jié)果集,這可能是大多數(shù)開(kāi)發(fā)人員都知道的。 上面的示例指示hibernate執(zhí)行查詢,將整個(gè)結(jié)果映射到實(shí)體并返回它們。 使用滾動(dòng)結(jié)果集時(shí),記錄一次轉(zhuǎn)換為一個(gè)實(shí)體:
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
盡管我們使用的是可滾動(dòng)的結(jié)果集,但每個(gè)返回的對(duì)象都是一個(gè)附加對(duì)象,并成為持久性上下文(也稱為會(huì)話)的一部分。 結(jié)果實(shí)際上與我們使用“ session.createQuery('from DemoEntity')。list() '的第一個(gè)示例相同。 但是,采用這種方法,我們無(wú)法控制。 一切都在幕后發(fā)生,如果休眠完成了工作,您將獲得包含所有數(shù)據(jù)的列表。 另一方面,使用可滾動(dòng)的結(jié)果集使我們迷上了檢索過(guò)程,并允許我們?cè)谛枰獣r(shí)釋放內(nèi)存。 如我們所見(jiàn),它不會(huì)自動(dòng)釋放內(nèi)存,您必須指示Hibernate實(shí)際執(zhí)行此操作。 存在以下選項(xiàng):
- 處理對(duì)象后將其從持久性上下文中逐出
- 偶爾清除整個(gè)會(huì)話
我們將選擇第一個(gè)。 在上面的示例的第13行( // Process和write result )下,我們將添加:
session.evict(demoEntity);重要:
- 如果您要對(duì)實(shí)體(或與它有關(guān)聯(lián)的實(shí)體進(jìn)行級(jí)聯(lián)逐出的實(shí)體)進(jìn)行任何修改,請(qǐng)確保在逐出或清除之前刷新會(huì)話,否則由于Hibernate的回寫(xiě)而導(dǎo)致的查詢將不會(huì)發(fā)送到數(shù)據(jù)庫(kù)
- 逐出或清除不會(huì)將實(shí)體從二級(jí)緩存中刪除。 如果啟用了二級(jí)緩存并正在使用它,并且還希望將其刪除,請(qǐng)使用所需的sessionFactory.getCache()。evictXxx()方法
- 從您退出實(shí)體的那一刻起,該實(shí)體將不再附加(不再與會(huì)話關(guān)聯(lián))。 在該階段對(duì)實(shí)體所做的任何修改將不再自動(dòng)反映到數(shù)據(jù)庫(kù)中。 如果您使用的是延遲加載,則訪問(wèn)驅(qū)逐之前未加載的任何屬性都會(huì)產(chǎn)生著名的org.hibernate.LazyInitializationException。 因此,基本上,在逐出或清除之前,請(qǐng)確保已完成對(duì)該實(shí)體的處理(或至少已初始化以進(jìn)一步滿足需要)
再次運(yùn)行該應(yīng)用程序后,我們看到它現(xiàn)在已成功執(zhí)行:
... Fetched 99800 entities Fetched 99900 entities Fetched 100000 entities
順便說(shuō)一句; 您還可以將查詢?cè)O(shè)置為只讀,以允許休眠狀態(tài)執(zhí)行一些其他優(yōu)化:
ScrollableResults scrollableResults = session.createQuery('from DemoEntity').setReadOnly(true).scroll(ScrollMode.FORWARD_ONLY);這樣做只會(huì)在內(nèi)存使用方面產(chǎn)生很小的差異,在此特定的測(cè)試設(shè)置中,它使我們能夠在給定的內(nèi)存量下額外讀取約300個(gè)實(shí)體。 就我個(gè)人而言,我不會(huì)僅將此功能僅用于內(nèi)存優(yōu)化,而僅當(dāng)它適合您的整體不變性策略時(shí)才使用。 使用休眠模式,您可以使用不同的選項(xiàng)將實(shí)體設(shè)置為只讀:在實(shí)體本身上,整個(gè)會(huì)話為只讀,依此類(lèi)推。 分別對(duì)查詢?cè)O(shè)置只讀為false可能是最不推薦的方法。 (例如,之前在會(huì)話中加載的實(shí)體將保持不受影響,可能可修改。即使查詢返回的根對(duì)象是只讀的,惰性關(guān)聯(lián)也將可修改地加載)。
好的,我們能夠處理我們的100.000條記錄,生活很美好。 但是事實(shí)證明,Hibernate對(duì)于批量操作還有另一個(gè)選擇:無(wú)狀態(tài)會(huì)話。 您可以從無(wú)狀態(tài)會(huì)話中獲取可滾動(dòng)結(jié)果集,方法與從普通會(huì)話中獲取方法相同。 無(wú)狀態(tài)會(huì)話直接位于JDBC之上。 Hibernate將在幾乎“所有功能禁用”模式下運(yùn)行。 這意味著沒(méi)有持久上下文,沒(méi)有第二級(jí)緩存,沒(méi)有臟檢測(cè),沒(méi)有延遲加載,基本上什么也沒(méi)有。 從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)換為對(duì)象。 這可能是一個(gè)有吸引力的選擇,因?yàn)樗梢詭椭鷶[脫手動(dòng)驅(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;} });
除了無(wú)狀態(tài)會(huì)話具有最佳的內(nèi)存使用情況外,使用它還會(huì)帶來(lái)一些副作用。 您可能已經(jīng)注意到,我們正在打開(kāi)一個(gè)無(wú)狀態(tài)會(huì)話并顯式關(guān)閉它:既沒(méi)有sessionFactory.getCurrentStatelessSession()也沒(méi)有(在撰寫(xiě)本文時(shí))任何用于管理無(wú)狀態(tài)會(huì)話的Spring集成。打開(kāi)無(wú)狀態(tài)會(huì)話會(huì)分配一個(gè)新的Java。默認(rèn)情況下sql.Connection(如果使用openStatelessSession() )執(zhí)行其工作,因此間接產(chǎn)生第二個(gè)事務(wù)。 您可以通過(guò)使用Hibernate work API來(lái)減輕這些副作用,如提供當(dāng)前Connection并將其傳遞給openStatelessSession(Connection connection)的示例中所示。 最后關(guān)閉會(huì)話對(duì)物理連接沒(méi)有影響,因?yàn)樗怯蒘pring基礎(chǔ)結(jié)構(gòu)捕獲的:打開(kāi)無(wú)狀態(tài)會(huì)話時(shí),僅關(guān)閉邏輯連接句柄,并創(chuàng)建新的邏輯連接句柄。
還要注意,您必須自己關(guān)閉無(wú)狀態(tài)會(huì)話,并且上面的示例僅適用于只讀操作。 從您打算使用無(wú)狀態(tài)會(huì)話進(jìn)行修改的那一刻起,還有一些警告。 如前所述,休眠模式在“所有功能都已禁用”模式下運(yùn)行,因此直接導(dǎo)致實(shí)體以分離狀態(tài)返回。 對(duì)于您修改的每個(gè)實(shí)體,您都必須顯式調(diào)用: statelessSession.update(entity) 。 首先,我嘗試使用此方法來(lái)修改實(shí)體:
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;} });這個(gè)想法是我們用現(xiàn)有的數(shù)據(jù)庫(kù)Connection打開(kāi)一個(gè)無(wú)狀態(tài)會(huì)話。 正如StatelessSession javadoc指示不會(huì)發(fā)生任何回寫(xiě)一樣,我確信無(wú)狀態(tài)會(huì)話執(zhí)行的每個(gè)語(yǔ)句都將直接發(fā)送到數(shù)據(jù)庫(kù)。 最終,當(dāng)提交事務(wù)(由TransactionTemplate開(kāi)始)時(shí),結(jié)果將在數(shù)據(jù)庫(kù)中可見(jiàn)。 但是,hibernate使用無(wú)狀態(tài)會(huì)話來(lái)執(zhí)行BATCH語(yǔ)句。 我不是100%知道批處理和回寫(xiě)之間有什么區(qū)別,但是結(jié)果是相同的,因此與javadoc的字典相反,因?yàn)檎Z(yǔ)句在以后排入隊(duì)列并刷新。 因此,如果您沒(méi)有做任何特別的事情,批處理的語(yǔ)句將不會(huì)被刷新,這就是我的情況:'statelessSession.update(demoEntity);' 被分批處理,從不沖洗。 強(qiáng)制刷新的一種方法是使用休眠事務(wù)API:
StatelessSession statelessSession = sessionFactory.openStatelessSession(); statelessSession.beginTransaction(); ... statelessSession.getTransaction().commit(); ...在這種情況下,您可能不希望僅因?yàn)槭褂脽o(wú)狀態(tài)會(huì)話而開(kāi)始以編程方式控制交易。 此外,由于沒(méi)有傳遞我們的Connection,因此我們?cè)俅卧诘诙€(gè)事務(wù)場(chǎng)景中運(yùn)行無(wú)狀態(tài)會(huì)話工作,因此將獲得新的數(shù)據(jù)庫(kù)連接。 我們無(wú)法通過(guò)外部連接的原因是,如果我們提交內(nèi)部事務(wù)(“無(wú)狀態(tài)會(huì)話事務(wù)”),并且它將使用與外部事務(wù)相同的連接(由TransactionTemplate開(kāi)始),則會(huì)破壞外部事務(wù)事務(wù)的原子性,因?yàn)閷⑼獠渴聞?wù)發(fā)送到數(shù)據(jù)庫(kù)的語(yǔ)句與內(nèi)部事務(wù)一起提交。 因此,不通過(guò)連接意味著打開(kāi)一個(gè)新的連接,從而創(chuàng)建第二筆交易。 更好的選擇是觸發(fā)Hibernate刷新無(wú)狀態(tài)會(huì)話。 但是,statelessSession沒(méi)有“ flush”方法來(lái)手動(dòng)觸發(fā)刷新。 解決方案是稍微依賴Hibernate內(nèi)部API。 該解決方案使手動(dòng)事務(wù)處理和第二個(gè)事務(wù)處理變得過(guò)時(shí):所有語(yǔ)句成為我們(唯一的)外部事務(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ā)布了一個(gè)更好的解決方案: https : //jira.springsource.org/browse/SPR-2495這還不是Spring的一部分,但是工廠bean的實(shí)現(xiàn)非常簡(jiǎn)單: StatelessSessionFactoryBean。使用此Java時(shí),您可以簡(jiǎn)單地注入StatelessSession:
@Autowired private StatelessSession statelessSession;它將注入一個(gè)無(wú)狀態(tài)的會(huì)話代理,這等效于正常的“當(dāng)前”會(huì)話的工作方式(稍有不同的是,您注入一個(gè)SessionFactory并且每次都需要獲取currentSession)。 調(diào)用代理時(shí),它將查找綁定到正在運(yùn)行的事務(wù)的無(wú)狀態(tài)會(huì)話。 如果已經(jīng)不存在,它將創(chuàng)建一個(gè)與普通會(huì)話相同的連接(就像我們?cè)谑纠兴龅哪菢?#xff09;,并為無(wú)狀態(tài)會(huì)話注冊(cè)自定義事務(wù)同步。 提交事務(wù)后,由于同步,將刷新無(wú)狀態(tài)會(huì)話,并最終將其關(guān)閉。 使用此方法,您可以直接注入無(wú)狀態(tài)會(huì)話,并將其用作當(dāng)前會(huì)話(或與注入JPA PeristentContext相同的方式)。 這使您不必處理無(wú)狀態(tài)會(huì)話的打開(kāi)和關(guān)閉,而不必處理一種或多種方法以使其變得暢通無(wú)阻。 該實(shí)現(xiàn)是針對(duì)JPA的,但是JPA部分僅限于在getPhysicalConnection()中獲得物理連接。 您可以輕松地省略EntityManagerFactory并直接從Hibernate會(huì)話獲取物理連接。
非常謹(jǐn)慎的結(jié)論:最好的方法顯然取決于您的情況。 如果您使用普通會(huì)話,則在讀取或保留實(shí)體時(shí)必須自行解決。 如果您有一個(gè)混合事務(wù),那么除了必須手動(dòng)執(zhí)行操作之外,還可能影響會(huì)話的進(jìn)一步使用。 你們都在同一筆交易中執(zhí)行“批量”和“正常”操作。 如果繼續(xù)進(jìn)行正常操作,您將在會(huì)話中分離實(shí)體,這可能會(huì)導(dǎo)致意外結(jié)果(因?yàn)榕K檢測(cè)將不再起作用,依此類(lèi)推)。 另一方面,您仍將具有主要的休眠好處(只要不驅(qū)逐該實(shí)體),例如延遲加載,緩存,臟檢測(cè)等。 在編寫(xiě)本文時(shí)使用無(wú)狀態(tài)會(huì)話需要額外注意管理它(打開(kāi),關(guān)閉和刷新),這也容易出錯(cuò)。 假設(shè)您可以繼續(xù)使用建議的工廠bean,那么您將擁有一個(gè)非常裸露的會(huì)話,該會(huì)話與正常會(huì)話是分開(kāi)的,但仍參與同一事務(wù)。 使用此工具,您無(wú)需執(zhí)行內(nèi)存管理即可擁有執(zhí)行批量操作的強(qiáng)大工具。 缺點(diǎn)是您沒(méi)有其他可用的休眠功能。
參考:在Koen Serneels – Technology博客博客上,從我們的JCG合作伙伴 Koen Serneels 與Hibernate進(jìn)行批量獲取 。
翻譯自: https://www.javacodegeeks.com/2013/03/bulk-fetching-with-hibernate.html
總結(jié)
以上是生活随笔為你收集整理的使用Hibernate批量获取的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2000元音箱加电脑(2000元左右电脑
- 下一篇: 编年史与微云