第 13 章 批量处理(Batch processing)
使用Hibernate将 100 000 条记录插入到数据库的一个很自然的做法可能是这样的
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
}
tx.commit();
session.close();
这段程序大概运行到 50 000 条记录左右会失败并抛出 内存溢出异常(OutOfMemoryException) 。 这是因为 Hibernate 把所有新插入的 客户(Customer)实例在 session级别的缓存区进行了缓存的缘故。
我们会在本章告诉你如何避免此类问题。首先,如果你要执行批量处理并且想要达到一个理想的性能, 那么使用JDBC的批量(batching)功能是至关重要。将JDBC的批量抓取数量(batch size)参数设置到一个合适值 (比如,10-50之间):
hibernate.jdbc.batch_size 20注意,假若你使用了identiy标识符生成器,Hibernate在JDBC级别透明的关闭插入语句的批量执行。
你也可能想在执行批量处理时关闭二级缓存:
hibernate.cache.use_second_level_cache false但是,这不是绝对必须的,因为我们可以显式设置CacheMode来关闭与二级缓存的交互。
13.1. 批量插入(Batch inserts)
如果要将很多对象持久化,你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小。
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, same as the JDBC batch size //20,与JDBC批量设置相同
//flush a batch of inserts and release memory:
//将本批插入的对象立即写入数据库并释放内存
session.flush();
session.clear();
}
}
tx.commit();session.close();
13.2. 批量更新(Batch updates)
此方法同样适用于检索和更新数据。此外,在进行会返回很多行数据的查询时, 你需要使用 scroll() 方法以便充分利用服务器端游标所带来的好处。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers").setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0); customer.updateStuff(...);
if ( ++count % 20 == 0 ) {
//flush a batch of updates and release memory: session.flush();
session.clear();
}
}
tx.commit();
session.close();
13.3. StatelessSession (无状态session)接口
作为选择,Hibernate提供了基于命令的API,可以用detached object的形式把数据以流的方法加入到数据库,或从数据库输出。StatelessSession没有持久化上下文,也不提供多少高层的生命周期语义。特别是,无状态session不实现第一级cache,也不和第二级缓存,或者查询缓存交互。它不实现事务化写,也不实现脏数据检查。用stateless session进行的操作甚至不级联到关联实例。stateless session忽略集合类(Collections)。通过stateless session进行的操作不触发Hibernate的事件模型和拦截器。无状态session对数据的混淆现象免疫,因为它没有第一级缓存。无状态session是低层的抽象,和低层JDBC相当接近。
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.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();
注意在上面的例子中,查询返回的Customer实例立即被脱管(detach)。它们与任何持久化上下文都没有关系。
StatelessSession 接口定义的insert(), update() 和 delete()操作是直接的数据库行级别操作,其结果是立刻执行一条INSERT, UPDATE 或 DELETE 语句。因此,它们的语义和Session 接口定义的save(), saveOrUpdate() 和delete() 操作有很大的不同。
13.4. DML(数据操作语言)风格的操作(DML-style operations)
hence manipulating (using the SQL Data Manipulation Language (DML) statements: INSERT, UPDATE, DELETE) data directly in the database will not affect in-memory state. However, Hibernate provides methods for bulk SQL-style DML statement execution which are performed through the Hibernate Query Language (第 14 章 HQL: Hibernate查询语言). 就像已经讨论的那样,自动和透明的 对象/关系 映射(object/relational mapping)关注于管理对象的状态。 这就意味着对象的状态存在于内存,因此直接操作 (使用 SQL Data Manipulation Language(DML,数据操作语言)语句 :INSERT ,UPDATE 和 DELETE) 数据库中的数据将不会影响内存中的对象状态和对象数据。 不过,Hibernate提供通过Hibernate查询语言(第 14 章 HQL: Hibernate查询语言)来执行大批 量SQL风格的DML语句的方法。
UPDATE 和 DELETE语句的语法为: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)? 有几点说明:
在FROM子句(from-clause)中,FROM关键字是可选的
在FROM子句(from-clause)中只能有一个实体名,它可以是别名。如果实体名是别名,那么任何被引用的属性都必须加上此别名的前缀;如果不是别名,那么任何有前缀的属性引用都是非法的。
不能在大批量HQL语句中使用第 14.4 节 “join 语法的形式”(显式或者隐式的都不行)。不过在WHERE子句中可以使用子查询。可以在where子句中使用子查询,子查询本身可以包含join。
整个WHERE子句是可选的。
举个例子,使用Query.executeUpdate()方法执行一个HQL UPDATE语句(: (方法命名是来源于JDBC's PreparedStatement.executeUpdate()):
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate();
tx.commit();
session.close();
HQL UPDATE语句,默认不会影响更新实体的第 5.1.7 节 “版本(version)(可选)”或者第 5.1.8 节 “timestamp (可选)”属性值。这和EJB3规范是一致的。但是,通过使用versioned update,你可以强制Hibernate正确的重置version或者timestamp属性值。这通过在UPDATE关键字后面增加VERSIONED关键字来实现的。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate ).setString( "newName", newName ).setString( "oldName", oldName ) .executeUpdate();
tx.commit();
session.close();
注意,自定义的版本类型(org.hibernate.usertype.UserVersionType)不允许和update versioned语句联用。
执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法:
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlDelete = "delete Customer c where c.name = :oldName"; // or String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close();由Query.executeUpdate()方法返回的整型值表明了受此操作影响的记录数量。 注意这个数值可能与数据库中被(最后一条SQL语句)影响了的“行”数有关,也可能没有。一个大批量HQL操作可能导致多条实际的SQL语句被执行, 举个例子,对joined-subclass映射方式的类进行的此类操作。这个返回值代表了实际被语句影响了的记录数量。在那个joined-subclass的例子中, 对一个子类的删除实际上可能不仅仅会删除子类映射到的表而且会影响“根”表,还有可能影响与之有继承关系的joined-subclass映射方式的子类的表。
INSERT语句的伪码是: INSERT INTO EntityName properties_list select_statement. 要注意的是:
只支持INSERT INTO ... SELECT ...形式,不支持INSERT INTO ... VALUES ...形式.
properties_list和SQL INSERT语句中的字段定义(column speficiation)类似。对参与继承树映射的实体而言,只有直接定义在给定的类级别的属性才能直接在properties_list中使用。超类的属性不被支持;子类的属性无意义。换句话说,INSERT天生不支持多态。
select_statement可以是任何合法的HQL选择查询,不过要保证返回类型必须和要插入的类型完全匹配。目前,这一检查是在查询编译的时候进行的,而不是把它交给数据库。注意,在HibernateType间如果只是等价(equivalent)而非相等(equal),会导致问题。定义为org.hibernate.type.DateType和org.hibernate.type.TimestampType的两个属性可能会产生类型不匹配错误,虽然数据库级可能不加区分或者可以处理这种转换。
对id属性来说,insert语句给你两个选择。你可以明确地在properties_list表中指定id属性(这样它的值是从对应的select表达式中获得),或者在properties_list中省略它(此时使用生成指)。后一种选择只有当使用在数据库中生成值的id产生器时才能使用;如果是“内存”中计算的类型生成器,在解析时会抛出一个异常。注意,为了说明这一问题,数据库产生值的生成器是org.hibernate.id.SequenceGenerator(和它的子类),以及任何org.hibernate.id.PostInsertIdentifierGenerator接口的实现。这儿最值得注意的意外是org.hibernate.id.TableHiLoGenerator,它不能在此使用,因为它没有得到其值的途径。
对映射为version 或 timestamp的属性来说,insert语句也给你两个选择,你可以在properties_list表中指定(此时其值从对应的select表达式中获得),或者在properties_list中省略它(此时,使用在org.hibernate.type.VersionType 中定义的seed value(种子值))。
执行HQL INSERT语句的例子如下:
Session session = sessionFactory.openSession();Transaction tx = session.beginTransaction();String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";int createdEntities = s.createQuery( hqlInsert ) .executeUpdate();tx.commit();session.close();
分享到:
相关推荐
然而,实际上,通过适当的技术手段,我们可以有效地解决Hibernate在批量处理时可能出现的性能问题。以下是一些关于如何在Hibernate环境下优化批量数据处理的策略。 首先,了解问题的根源。在上述例子中,当尝试向...
#### 策略一:利用Hibernate的批量处理机制 1. **使用`flush()`和`evict()`方法**:在修改实体后,主动调用`session.flush()`方法,使Hibernate将缓存中的变更同步到数据库,然后调用`session.evict(entity)`方法,...
为了优化批量更新操作,可以采取以下策略: #### 1. 使用`flush()`和`evict()` 在每次更新后调用`session.flush()`强制Hibernate执行当前的数据库操作,然后使用`session.evict(entity)`将实体从缓存中移除。这样...
- **JDBC批量大小设置**:为了优化性能,需要在Hibernate配置文件中设置JDBC批量处理的大小。合理的数值通常介于10到50之间,例如: ```xml <property name="hibernate.jdbc.batch_size">20 ``` 这意味着每次向...
### Hibernate批量处理数据 #### 一、概述 Hibernate作为一款流行的Java持久层框架,它能够以面向对象的方式处理数据库操作,极大地简化了开发过程。然而,在处理大量数据时,如果仍然采用逐条处理的方式,将会...
为了解决这一问题,Hibernate提供了对JDBC的支持,使得开发者能够利用JDBC的批量处理能力来优化批量删除操作。 #### 实现方式一:使用Hibernate API 原始的实现方式是直接通过Hibernate API来执行批量删除操作,...
标题"浅析Hibernate下数据批量处理方法"和描述中提到,早期的Hibernate在批量插入时可能存在效率问题,但最新版本已经进行了优化,例如通过设置`batch_size`参数来提高批量处理的性能。`batch_size`设置的是JDBC...
在当今的IT开发领域中,特别是在使用Java语言开发的Web应用程序中,JSP和Hibernate是构建动态网站和Web服务时经常使用的两种技术。...开发者应当根据具体的应用场景和数据库特性,选择合适的批量处理策略。
3. **批处理**:开启Hibernate的批处理功能,通过一组SQL语句批量处理多条记录,减少数据库交互次数。 二、查询优化 1. **避免N+1查询**:在一对多关系中,使用`fetch`策略或`JOIN`查询,防止加载单个实体时引发...
- **避免内存溢出**:对于批量数据处理,为了避免一次性加载大量实体导致内存溢出,可以在批量插入之前调用 `session.flush()` 和 `session.clear()`,这样可以确保数据被持久化到数据库并且释放缓存空间。...
总的来说,这个批量删除源码示例展示了如何利用Struts2处理HTTP请求,Spring管理依赖,以及Hibernate与数据库交互,是学习和理解三大框架整合开发的一个实践案例。通过分析和理解这段代码,开发者可以提升自己在企业...
以下是针对Hibernate批量处理海量数据的一些关键知识点和优化策略: 1. **理解Hibernate的工作原理**:Hibernate通过查询数据库获取数据,并将其转化为Java对象存储在内存中,这种做法在处理小量数据时非常便捷,但...
针对这种情况,Hibernate提供了一些批量处理的解决方案。 批量插入是处理大量数据插入的一种有效方法。通常,初学者可能会尝试一次性创建大量对象并调用Session的save()方法来保存,但这会导致所有对象被存储在...
本文将深入探讨如何通过配置优化Hibernate的多表查询,以提高应用的性能。 1. **联合查询优化**: 在进行多表查询时,Hibernate默认可能会生成多个单表查询,这会导致大量的数据库往返,降低效率。通过配置`@Join...
本文件主要探讨了如何使用Hibernate进行批量更新和批量删除处理,这些操作在处理大量数据时尤其重要,因为它们可以显著提高应用的性能。下面我们将深入解析这两个主题。 批量更新在Hibernate中通常涉及在一个事务中...
本文将深入探讨Java如何进行数据批量处理,涉及的主要知识点包括批量读取、处理和存储数据,以及优化策略。 一、批量读取数据 1. 文件I/O:Java的`java.io`和`java.nio`包提供了读取大量数据的能力。例如,使用`...
Hibernate 性能优化 在 Hibernate 中,性能优化是非常重要的,因为它直接影响着应用程序的效率和可扩展性。...Hibernate 性能优化需要我们注意批量处理和 1+n 问题,并使用相应的解决方法来避免这些问题。
2. **Fetch Size和Batch Size**:这两个参数分别控制了每次从数据库读取的数据量和批量处理的数据量,合理设置可以提高效率。 3. **关闭SQL语句打印**:在生产环境中,关闭Hibernate的日志输出可以减少不必要的I/O...
【Hibernate优化方案】 在开发Java应用时,Hibernate作为流行的ORM框架,其性能优化至关重要。本文主要探讨了在Hibernate中如何优化数据操作,包括批量修改和删除、使用SQL执行批量操作以及提升数据库查询性能。 ...
在进行数据库操作时,经常会遇到需要批量处理数据的情况,例如批量更新或批量删除等。利用Hibernate框架可以方便地实现这些功能,提高开发效率并减少资源消耗。本文将详细介绍如何通过Hibernate进行批量删除,并探讨...