1. 程式人生 > >hibernate處理批量更新和批量刪除

hibernate處理批量更新和批量刪除


9.4 批量處理資料


通常,在一個Session物件的快取中只存放數量有限的持久化物件,等到Session物件處理事務完畢,還要關閉Session物件,從而及時釋放Session的快取佔用的記憶體。
批量處理資料是指在一個事務中處理大量資料。以下程式在一個事務中批量更新CUSTOMERS表中年齡大於零的所有記錄的AGE欄位:
Transaction tx = session.beginTransaction();
Iterator customers=
session.createQuery("from Customer c where c.age>0").list().iterator();
while(customers.hasNext()){
Customer customer=(Customer)customers.next();
customer.setAge(customer.getAge()+1);
}

tx.commit();
session.close();

如果CUSTOMERS表中有1萬條年齡大於零的記錄,那麼Hibernate會一下子載入1萬個Customer物件到記憶體。當執行tx.commit()方法時,會清理快取,Hibernate執行1萬條更新CUSTOMERS表的update語句:
update CUSTOMERS set AGE=? …. where ID=i;
update CUSTOMERS set AGE=? …. where ID=j;
……
update CUSTOMERS set AGE=? …. where ID=k;

以上批量更新方式有兩個缺點:
佔用大量記憶體,必須把1萬個Customer物件先載入到記憶體,然後一一更新它們。
執行的update語句的數目太多,每個update語句只能更新一個Customer物件,必須通過1萬條update語句才能更新1萬個Customer物件,頻繁地訪問資料庫,會大大降低應用的效能。

一般說來,應該儘可能避免在應用層進行批量操作,而應該在資料庫層直接進行批量操作,例如直接在資料庫中執行用於批量更新或刪除的SQL語句,如果批量操作的邏輯比較複雜,則可以通過直接在資料庫中執行的儲存過程來完成批量操作。
並不是所有的資料庫系統都支援儲存過程。例如目前的MySQL就不支援儲存過程,因此不能通過儲存過程來進行批量更新或批量刪除。
當然,在應用層也可以進行批量操作,主要有以下方式:
(1)通過Session來進行批量操作。
(2)通過StatelessSession來進行批量操作。
(3)通過HQL來進行批量操作。
(4)直接通過JDBC API來進行批量操作。

9.4.1 通過Session來進行批量操作

Session的save()以及update()方法都會把處理的物件存放在自己的快取中。如果通過一個Session物件來處理大量持久化物件,應該及時從快取中清空已經處理完畢並且不會再訪問的物件。具體的做法是在處理完一個物件或小批量物件後,立刻呼叫flush()方法清理快取,然後再呼叫clear()方法清空快取。

通過Session來進行批量操作會受到以下約束:
(1)需要在Hibernate的配置檔案中設定JDBC單次批量處理的數目,合理的取值通常為10到50之間,例如:
hibernate.jdbc.batch_size=20
在按照本節介紹的方法進行批量操作時,應該保證每次向資料庫傳送的批量SQL語句數目與這個batch_size屬性一致。
(2)如果物件採用"identity"識別符號生成器,則Hibernate無法在JDBC層進行批量插入操作。
(3)進行批量操作時,建議關閉Hibernate的第二級快取。本書的姊妹篇《精通Hibernate:高階篇》對第二級快取做了詳細介紹。Session的快取為Hibernate的第一級快取,通常它是事務範圍內的快取,也就是說,每個事務都有單獨的第一級快取。SessionFactory的外接快取為Hibernate的第二級快取,它是應用範圍內的快取,也就是說,所有事務都共享同一個第二級快取。在任何情況下,Hibernate的第一級快取總是可用的。而預設情況下,Hibernate的第二級快取是關閉的,此外也可以在Hibernate的配置檔案中通過如下方式顯式關閉第二級快取:
hibernate.cache.use_second_level_cache=false

1.批量插入資料
以下程式碼一共向資料庫中插入十萬條CUSTOMERS記錄,單次批量插入20條CUSTOMERS記錄:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //單次批量操作的數目為20
session.flush(); //清理快取,執行批量插入20條記錄的SQL insert語句
session.clear(); //清空快取中的Customer物件
}
}

tx.commit();
session.close();

在以上程式中,每次執行session.flush()方法,就會向資料庫中批量插入20條記錄。接下來session.clear()方法把20個剛儲存的Customer物件從快取中清空。
為了保證以上程式順利執行,需要遵守以下約束。
在Hibernate的配置檔案中,應該把hibernate.jdbc.batch_size屬性也設為20。
關閉第二級快取。因為假如使用了第二級快取,那麼所有在第一級快取(即Session的快取)中建立的Customer物件還要先複製到第二級快取中,然後再儲存到資料庫中,這會導致大量不必要的開銷。
Customer物件的識別符號生成器不能為"identity"。

2.批量更新資料

進行批量更新時,如果一下子把所有物件到載入到Session的快取中,然後再在快取中一一更新它們,顯然是不可取的。為了解決這一問題,可以使用可滾動的結果集org.hibernate.ScrollableResults,Query的scroll()方法返回一個ScrollableResults物件。以下程式碼演示批量更新Customer物件,該程式碼一開始利用ScrollableResults物件來載入所有的Customer物件:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults customers= session.createQuery("from Customer")
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.setAge(customer.getAge()+1); //更新Customer物件的age屬性
if ( ++count % 20 == 0 ) { //單次批量操作的數目為20

session.flush();//清理快取,執行批量更新20條記錄的SQL update語句
session.clear();//清空快取中的Customer物件
}
}

tx.commit();
session.close();

在以上程式碼中,Query的scroll()方法返回的ScrollableResults物件中實際上並不包含任何Customer物件,它僅僅包含了用於線上定位資料庫中CUSTOMERS記錄的遊標。只有當程式遍歷訪問ScrollableResults物件中的特定元素時,它才會到資料庫中載入相應的Customer物件。
為了保證以上程式順利執行,需要遵守以下約束:
在Hibernate的配置檔案中,應該把hibernate.jdbc.batch_size屬性也設為20。
關閉第二級快取。假如已經在配置檔案中啟用了第二級快取,也可以通過以下方式在程式中忽略第二級快取:
ScrollableResults customers= session.createQuery("from Customer")
//忽略第二級快取
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);

9.4.2 通過StatelessSession來進行批量操作

Session具有一個用於保持記憶體中物件與資料庫中相應資料保持同步的快取,位於Session快取中的物件為持久化物件。但在進行批量操作時,把大量物件存放在Session快取中會消耗大量記憶體空間。作為一種替代方案,可以採用無狀態的StatelessSession來進行批量操作。
以下程式碼利用 StatelessSession來進行批量更新操作:
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();

ScrollableResults customers = session.getNamedQuery("GetCustomers")
.scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.setAge(customer.getAge()+1); //在記憶體中更新Customer物件的age屬性;
session.update(customer);//立即執行update語句,更新資料庫中相應CUSTOMERS記錄
}

tx.commit();
session.close();

從形式上看,StatelessSession與Session的用法有點相似。StatelessSession與Session相比,有以下區別:
(1)StatelessSession沒有快取,通過StatelessSession來載入、儲存或更新後的物件都處於遊離狀態。
(2)StatelessSession不會與Hibernate的第二級快取互動。
(3)當呼叫StatelessSession的save()、update()或delete()方法時,這些方法會立即執行相應的SQL語句,而不會僅僅計劃執行一條SQL語句。
(4)StatelessSession不會對所載入的物件自動進行髒檢查。所以在以上程式中,修改了記憶體中Customer物件的屬性後,還需要通過StatelessSession的update()方法來更新資料庫中的相應資料。
(5)StatelessSession不會對關聯的物件進行任何級聯操作。舉例來說,通過StatelessSession來儲存一個Customer物件時,不會級聯儲存與之關聯的Order物件。
(6)StatelessSession所做的操作可以被Interceptor攔截器捕獲到,但會被Hibernate的事件處理系統忽略。
(7)通過同一個StatelessSession物件兩次載入OID為1的Customer物件時,會得到兩個具有不同記憶體地址的Customer物件,例如:
StatelessSession session = sessionFactory.openStatelessSession();
Customer c1=(Customer)session.get(Customer.class,new Long(1));
Customer c2=(Customer)session.get(Customer.class,new Long(1));
System.out.println(c1==c2); //列印false

9.4.3 通過HQL來進行批量操作

Hibernate3中的HQL(Hibernate Query Language,Hibernate查詢語言)不僅可以檢索資料,還可以用於進行批量更新、刪除和插入資料。批量操作實際上直接在資料庫中完成,所處理的資料不會被儲存在Session的快取中,因此不會佔用記憶體空間。
Query.executeUpdate()方法和JDBC API中的PreparedStatement.executeUpdate()很相似,前者執行用於更新、刪除和插入的HQL語句,而後者執行用於更新、刪除和插入的SQL語句。

1.批量更新資料
以下程式程式碼演示通過HQL來批量更新Customer物件:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlUpdate =
"update Customer c set c.name = :newName where c.name = :oldName";
int updatedEntities = session.createQuery( hqlUpdate )
.setString( "newName", "Mike" )
.setString( "oldName", "Tom" )
.executeUpdate();

tx.commit();
session.close();
以上程式程式碼向資料庫傳送的SQL語句為:
update CUSTOMERS set NAME="Mike" where NAME="Tom"

2.批量刪除資料
Session的delete()方法一次只能刪除一個物件,不適合進行批量刪除操作。以下程式程式碼演示通過HQL來批量刪除Customer物件:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlDelete = "delete Customer c where c.name = :oldName";
int deletedEntities = session.createQuery( hqlDelete )
.setString( "oldName", "Tom" )
.executeUpdate();
tx.commit();
session.close();
以上程式程式碼向資料庫提交的SQL語句為:
delete from CUSTOMERS where NAME="Tom"

3.批量插入資料
插入資料的HQL語法為:
insert into EntityName properties_list select_statement
以上EntityName表示持久化類的名字,properties_list表示持久化類的屬性列表,select_statement表示子查詢語句。
HQL只支援insert into ... select ... 形式的插入語句,而不支援"insert into ... values ... "形式的插入語句。
下面舉例說明如何通過HQL來批量插入資料。假定有DelinquentAccount和Customer類,它們都有id和name屬性,與這兩個類對應的表分別為DELINQUENT_ACCOUNTS和CUSTOMERS表。DelinquentAccount.hbm.xml和Customer.hbm.xml檔案分別為這兩個類的對映檔案。以下程式碼能夠把CUSTOMERS表中的資料複製到DELINQUENT_ACCOUNTS表中:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where c.id>1";
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();

以上程式程式碼向資料庫提交的SQL語句為:
insert into DELINQUENT_ACCOUNTS(ID,NAME) select ID,NAME from CUSTOMERS where ID>1

9.4.4 直接通過JDBC API來進行批量操作

當通過JDBC API來執行SQL insert、update和delete語句時,SQL語句中涉及到的資料不會被載入到記憶體中,因此不會佔用記憶體空間。
以下程式直接通過JDBC API來執行用於批量更新的SQL語句:
Transaction tx = session.beginTransaction();
//獲得該Session使用的資料庫連線
java.sql.Connection con=session.connection();
//通過JDBC API執行用於批量更新的SQL語句
PreparedStatement stmt=con.prepareStatement("update CUSTOMERS set AGE=AGE+1 "
+"where AGE>0 ");
stmt.executeUpdate();

tx.commit();

以上程式通過Session的connection()方法獲得該Session使用的資料庫連線,然後通過它建立PreparedStatement物件並執行SQL語句。值得注意的是,應用程式仍然通過Hibernate的Transaction介面來宣告事務邊界。
值得注意的是,在Hibernate3中,儘管Session的connection()方法還存在,但是已經被廢棄,不提倡使用了,不過Hibernate3提供了替代方案:org.hibernate.jdbc.Work介面表示直接通過JDBC API來訪問資料庫的操作,Work介面的execute()方法用於執行直接通過JDBC API來訪問資料庫的操作:
public interface Work {
//直接通過JDBC API來訪問資料庫的操作
public void execute(Connection connection) throws SQLException;
}
Session的doWork(Work work)方法用於執行Work物件指定的操作,即呼叫Work物件的execute()方法。Session會把當前使用的資料庫連線傳給execute()方法。

以下程式演示了通過Work介面以及Session的doWork()方法來執行批量操作的過程:
Transaction tx=session.beginTransaction();
//定義一個匿名類,實現了Work介面
Work work=new Work(){
public void execute(Connection connection)throws SQLException{
//通過JDBC API執行用於批量更新的SQL語句
PreparedStatement stmt=connection
.prepareStatement("update CUSTOMERS set AGE=AGE+1 "
+"where AGE>0 ");
stmt.executeUpdate();
}
};

//執行work
session.doWork(work);
tx.commit();

相關推薦

hibernate處理批量更新批量刪除

9.4 批量處理資料 通常,在一個Session物件的快取中只存放數量有限的持久化物件,等到Session物件處理事務完畢,還要關閉Session物件,從而及時釋放Session的快取佔用的記憶體。 批量處理資料是指在一個事務中處理大量資料。以下程式在一個事務中批量更新

Hibernate應用中批量更新批量刪除

批量更新是指在一個事務中更新大批量資料,批量刪除是指在一個事務中刪除大批量資料。以下程式直接通過Hibernate API批量更新CUSTOMERS表中年齡大於零的所有記錄的AGE欄位: tx = s

關於Neo4jCypher批量更新批量插入優化的5個建議

原文連結: http://jexp.de/blog/2017/03/5-tips-tricks-for-fast-batched-updates-of-graph-structures-with-neo4j-and-cypher 注:我在測試後,對原文中的部分Cypher語句進行修改,使得其

Jeesite 批量更新批量插入

Jeesite 批量更新和批量插入 xml配置 <insert id="batchInsertList"> INSERT INTO t_dc_bug( zt_bug_id, product_id ) VALUES <foreach item="item

ibatis對oracle資料庫的批量更新批量插入的操作

今天有個批量的東西很是耽誤我的時間,那就是ibatis對Oracle的批量操作,雖然覺得這種批量的沒什麼,以前也有做過,但是對於mysql和oracle是不一樣的,那麼接下來我們就來介紹一下,oracle的批量操作,至於MySQL百度上面有的是。 1.批量插入: <

EF結合SqlBulkCopy實現高效的批量資料插入 |EF外掛EntityFramework.Extended實現批量更新刪除

批量插入 (17597條資料批量插入耗時1.7秒) using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; na

sql Server如何執行批量插入批量刪除

emc pcs -- frog bps eno bbu ads spc 平時我們sql server執行查詢語句都是通過 insert into 表名(字段名,字段名) values(插入值,插入值) --單條插入語句--- insert into Reader(read

jdbcTemplate高效批量插入批量更新 模擬原生jdbc批量10s插入10萬條到mysql

1.專案中親測一次性插入10萬條資料差不多用了10s,這時候只能使用原生jdbc,spring框架為我們薄薄的封裝了個jdbcTemplate 思路就是準備插入資料庫的分隔成1萬條(自己根據資料包或網路定具體數量),將sql拼成字串(減少mysql多次訪問的壓力,拿連線和mysql日誌生成的數量

mybatis+mysql批量插入批量更新

一、批量插入 批量插入資料使用的sql語句是: insert into table (aa,bb,cc) values(xx,xx,xx),(oo,oo,oo)   mybatis中mapper.xml的程式碼如下:   <!-- 批量插入資料 -->

mysql mybatis 批量更新新增

一、mybatis執行批量更新batch update 的方法(mysql資料庫) 1、資料庫連線必須配置:&allowMultiQueries=true(切記一定要加上這個屬性,否則會有問題,切記!切記!切記!)   我的配置如下:jdbc:mysql://127.0.0.1:3306/t

mybatis批量更新插入

  批量更新(1) <update id="updateBatch" parameterType="java.util.List"> update supplier_unit_price <trim prefix="set" suffixOverrides=

MaBatis:使用foreach進行批量插入批量刪除

MaBatis:使用foreach進行批量插入和批量刪除 foreach可以在SQL語句中通過拼接的方式進行集合迭代。foreach元素的屬性主要有collection,item,index,separator,open,close。 1.item屬性:表示迴

批量新增批量刪除(statement.addBatch() )

1 . 首先在工具類中封裝下連線物件方法.,返回的是Connection物件. 2 . 注意看註釋,先新增再用一句話執行. @Test public static boolean batchInsert() { Statem

Mybatis(Oracle)批量插入、批量更新批量刪除

mybatis對映中可以通過<foreach></foreach>標籤來實現Oracle的批量插入、更新和刪除     <foreach>標籤中主要有以下屬性:     collection、item、index、open、separat

批量更新打包多個Git庫目錄

# 批量更新多個Git庫建立一個專門存放git庫專案的目錄,例如D:\git。project專案的本地git庫目錄為D:\git\project\.git。更新(git pull)D:\git目錄下的所有Git/Hg專案,在該目錄下執行gitpullall.bat批處理檔案:

jdbcTemplate高效批量插入批量更新

批量插入(資料量超一萬時建議分批次提交,每次一萬條) /** * 生成goodsIssue的幸運碼<大量資料一次性插入> * @param goodsIssue * @a

mybatis如何實現批量更新插入新增例項詳解(附SQL以及mapper配置)

Mybatis批量插入、批量修改 批量插入 step1:建立DB表 CREATE TABLE `student_info` ( `STUDENT_ID` BIGINT(20) NOT NULL A

【java】【mybatis】在使用mybatis進行批量插入,批量更新批量操作時,切割In集合List進行分批批量操作的java中的切割代碼

lse span ati 批量更新 次數 sublist 調用 size ==       紅字部分代表mybatis的批量操作調用方法:       int num = 0; int maxLength = 200; in

mysql 批量更新批量更新多條記錄的不同值實現方法

批量更新 mysql更新語句很簡單,更新一條資料的某個欄位,一般這樣寫: UPDATE mytable SET myfield = 'value' WHERE other_field = 'other_value';   如果更新同一欄位為同一個值,mysql也很簡單,修改

資料庫批量操作(批量更新批量插入)

資料庫的批量操作 為了儘可能提高我們的sql執行效率,一般我們針對多條資料的操作,使用批量更新或者批量插入的方式 方式如下: --批量插入 <insert id="saveUserList" parameterType="java.util.List">