使用 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 级别透明的关闭插入语句的批量执行。 15.1. 批量插入(Batch inserts)15.2. 批量更新(Batch updates)15.3. StatelessSession(无状态 session)接口15.4. DML(数据操作语言)风格的操作(DML-style operations)HQL)来执行大批量 SQL 风格的 DML 语句的方法。
你也可能想在执行批量处理时完全关闭二级缓存:
hibernate.cache.use_second_level_cache false
但是,这不是绝对必须的,因为我们可以显式设置 CacheMode
来关闭与二级缓存的交互。
如果要将很多对象持久化,你必须通过经常的调用 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
<!-- -->
//flush a batch of inserts and release memory:
<!-- -->
session.flush();
<!-- -->
session.clear();
<!-- -->
}
<!-- -->
}
<!-- -->
<!-- -->
tx.commit();
<!-- -->
session.close();
此方法同样适用于检索和更新数据。此外,在进行会返回很多行数据的查询时,你需要使用 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();
作为选择,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()
操作有很大的不同。
就像已经讨论的那样,自动和透明的对象/关系映射(object/relational mapping)关注于管理对象的状态。这就意味着对象的状态存在于内存,因此直接操作(使用 SQL Data Manipulation Language
(DML,数据操作语言)语句 :INSERT
,UPDATE
和 DELETE
) 数据库中的数据将不会影响内存中的对象状态和对象数据。不过,Hibernate 提供通过 Hibernate 查询语言(
UPDATE
和 DELETE
语句的伪语法为:( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?
。
要注意的事项:
-
在 FROM 子句(from-clause)中,FROM 关键字是可选的
-
在 FROM 子句(from-clause)中只能有一个实体名,它可以是别名。如果实体名是别名,那么任何被引用的属性都必须加上此别名的前缀;如果不是别名,那么任何有前缀的属性引用都是非法的。
-
不能在大批量 HQL 语句中使用 joins 连接(显式或者隐式的都不行)。不过在 WHERE 子句中可以使用子查询。可以在 where 子句中使用子查询,子查询本身可以包含 join。
-
整个 WHERE 子句是可选的。
举个例子,使用 Query.executeUpdate()
方法执行一个 HQL UPDATE
语句(方法命名是来源于 JDBC 的 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
语句,默认不会影响更新实体的 version 或 the 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();
分享到:
相关推荐
批处理是一种在Windows操作系统中广泛使用的自动化工具,它允许用户编写一系列命令,形成一个批处理文件(.bat或.cmd扩展名),然后一次性执行这些命令,极大地提高了工作效率。本压缩包“经典批处理大全”显然是一...
在IT行业中,批处理脚本是一种非常实用的自动化工具,特别是在Windows系统环境下。"BAT"是批处理文件的扩展名,这些文件包含了可执行的DOS命令序列,用于执行一系列操作,无需用户交互。本资源"BAT批处理脚本-提示...
批处理文件编程是一种在DOS或Windows环境下使用命令行脚本执行一系列操作的方法。它主要依赖于几个核心命令,如`echo`、`@`、`call`、`pause`和`rem`,以及更高级的`if`、`goto`、`choice`和`for`等。 1. `echo`: ...
批处理是一种在计算机科学中广泛使用的自动化技术,它允许用户预先编写一系列命令,然后一次性执行这些命令,而无需逐个手动输入。在这个压缩包中,我们聚焦于"经典批处理",这通常涉及到Windows操作系统中的批处理...
本文将深入探讨“打印机错误解决批处理”的概念,以及如何利用批处理脚本来解决打印机故障。 首先,我们需要了解打印机错误的类型。打印机错误可能源自硬件故障、驱动程序问题、打印队列堵塞或网络连接问题。常见的...
"dos批处理命令大全" dos批处理命令是dos操作系统中的一种批处理语言,用于自动执行一系列命令。以下是dos批处理命令的详细解释和示例: 1. Echo 命令 Echo 命令用于打开回显或关闭请求回显功能,或显示消息。...
可视化批处理编辑器是一种强大的工具,它允许用户通过图形用户界面(GUI)创建、编辑和管理批处理脚本,而不是传统的命令行方式。这样的编辑器通常具有友好的拖放功能和预览机制,使得编写批处理命令变得直观且易于...
手机批处理,是一种在移动设备上实现自动化任务执行的技术,主要通过特定的软件来实现。在PC端,批处理通常指的是使用批处理脚本(如Windows的.bat或Linux的.sh文件),而在手机平台上,这样的功能可能由第三方应用...
批处理文件,又称为批处理脚本,是一种在Windows操作系统中批量执行命令的方法,能够极大地提高工作效率,尤其在需要重复执行相同或类似任务时。 在Oracle环境中,服务通常包括监听器(Listener)和服务(Service)...
在IT行业中,批处理脚本是一种非常实用的自动化工具,特别是在Windows系统中,BAT脚本的应用广泛且功能强大。本文将深入探讨“BAT批处理脚本-加密解密-解密.zip”这一主题,以及如何利用它进行文件的加密与解密。 ...
批处理阶段教程奥运最终版是由“英雄”出品的一份针对批处理技术的详细教程,适合零基础的学习者。批处理,又称批处理命令,是一种在计算机操作系统中预先编写好一系列命令,然后一次性执行这些命令的技术。它允许...
标题“自动批量拨号批处理.rar”所涉及的知识点主要集中在使用批处理脚本来实现自动化的宽带拨号连接。批处理是一种在Windows操作系统中执行一系列命令的文本文件,通常以.bat或.cmd为扩展名。在这个特定的例子中,...
在Windows操作系统中,批处理(Batch File)是一种简单的脚本语言,它允许用户通过命令行界面执行一系列连续的操作。 批处理脚本通常由一系列DOS命令组成,比如`net send`、`copy`、`del`等,这些命令可以自动化...
批处理(Batch Processing)是一种在计算机系统中批量处理任务的技术,尤其在早期的计算机系统中广泛应用。它允许用户一次性提交多个命令或者程序,系统会按照预定的顺序执行这些任务,而无需人工持续干预。在现代...
标题中的“Win11一键生成WinPE的批处理”指的是创建一个Windows预安装环境(Windows Preinstallation Environment,简称WinPE)的自动化过程,利用批处理脚本来简化操作。批处理是一种在DOS或Windows系统中运行一...
批处理文件夹权限修改是一种高效的操作方式,尤其适用于管理大量文件夹访问权限的场景,例如在服务器维护中。本文将详细介绍批处理技术以及如何利用它来修改文件夹权限。 批处理(Batch Processing)是Microsoft ...
批处理是一种在Windows操作系统中广泛使用的自动化脚本技术,它允许用户通过编写一系列命令来执行多个操作,从而简化复杂的任务管理。批处理文件通常以`.cmd`或`.bat`为扩展名,这两种格式在功能上基本相同,都是...
根据提供的文件信息,我们可以深入探讨如何使用批处理脚本来给文本文件中的每一行头部和尾部添加特定内容。虽然描述中提到的是一个简单的操作,但深入理解背后的逻辑和技术细节对于扩展此类脚本的功能非常有帮助。 ...
在IT行业中,批处理批量文件改名是一种常见的文件管理任务,尤其对于处理大量文件时非常高效。批处理(Batch Processing)是计算机系统中的一种处理方式,它允许用户一次性提交多个命令或任务,由系统自动执行,无需...
在Windows 10操作系统中,批处理(Batch)是一种简单而强大的工具,它允许用户通过编写脚本来自动化一系列命令执行。在这个场景中,"win10批处理自动关机、定时关机_win10_脚本_批处理_定时关机_自动关机_" 提供了三...