- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 Hibernate 快速入门
- 第 1 章 安装和设置
- 第 2 章 映射简介
- 第 3 章 驾驭 Hibernate
- 第 4 章 集合与关联
- 第 5 章 更复杂的关联
- 第 6 章 自定义值类型
- 第 7 章 映射标注
- 第 8 章 条件查询
- 第 9 章 浅谈 HQL
- 第二部分 与其他工具的集成
- 第 10 章 将 Hibernate 连接到 MySQL
- 第 11 章 Hibernate 与 Eclipse:Hibernate Tools 使用实战
- 第 12 章 Maven 进阶
- 第 13 章 Spring 入门:Hibernate 与 Spring
- 第 14 章 画龙点睛:用 Stripes 集成 Spring 和 Hibernate
- 附录 A Hibernate 类型
- 附录 B Criteria API
- 附录 C Hibernate SQL 方言
- 附录 D Spring 事务支持
- 附录 E 参考资源
- 作者简介
- 封面介绍
创建持久化对象
我们先创建几个新的 Track 实例,再把它们持久化保存到数据库,这样就能看看对象到底是怎样转换成数据库表的行和列的。因为我们完全按照标准的 Hibernate 要求来组织映射文档和配置文件,所以配置 Hibernate 的会话工厂(session factory)也就变得相当容易了。
应该怎么做
这里的讨论假设你已经按照第 2 章的示例,创建好了数据库模式,并生成了 Java 代码。如果你还没有做这些,可以从本书的网站( [1] )下载示例文件,直接跳转到 ch03 目录,使用命令 ant prepare 和 ant codegen( [2] ),再接着执行 ant schema,就可以自动取回这个示例所需要的 Hibernate 和 HSQLDB 库,并生成 Java 代码和数据库模式。(和其他示例一样,这些命令都应该在 shell 窗口中执行,而当前工作目录就是你的项目目录树的顶级目录,也就是包含 Ant build.xml 文件的地方。)
我们先从一个简单的示范用的 CreateTest 类开始,它包含了必要的导入(import)语句,以及一些辅助代码,用于设定 Hibernate 环境,再创建几个用 XML 映射文件来进行持久化存储的 Track 实例。源代码如例 3-3 所示,它位于 src/com/oreilly/hh 目录中。
例 3-3:数据创建测试,CreateTest.java
package com.oreilly.hh;
import org.hibernate.*;❶
import org.hibernate.cfg.Configuration;
import com.oreilly.hh.data.*;
import java.sql.Time;
import java.util.Date;
/**
*Create sample data, letting Hibernate persist it for us.
*/
public class CreateTest{
public static void main(String args[])throws Exception{
//Create a configuration based on the XML file we've put
//in the standard place.
Configuration config=new Configuration();❷
config.configure();
//Get the session factory we can use for persistence
SessionFactory sessionFactory=config.buildSessionFactory();❸
//Ask for a session using the JDBC information we've configured
Session session=sessionFactory.openSession();❹
Transaction tx=null;
try{
//Create some data and persist it
tx=session.beginTransaction();❺
Track track=new Track("Russian Trance",
"vol2/album610/track02.mp3",
Time.valueOf("00:03:30"),new Date(),
(short)0);
session.save(track);
track=new Track("Video Killed the Radio Star",
"vol2/album611/track12.mp3",
Time.valueOf("00:03:49"),new Date(),
(short)0);
session.save(track);
track=new Track("Gravity's Angel",
"vol2/album175/track03.mp3",
Time.valueOf("00:06:06"),new Date(),
(short)0);
session.save(track);
//We're done;make our changes permanent
tx.commit();❻
}catch(Exception e){
if(tx!=null){
//Something went wrong;discard all partial changes
tx.rollback();
}
throw new Exception("Transaction failed",e);
}finally{
//No matter what, close the session
session.close();
}
//Clean up after ourselves
sessionFactory.close();❼
}
}
需要对 CreateTest.java 的第一部分做些解释:
❶我们导入一些有用的 Hibernate 类(包括 Configuration),用它们来建立 Hibernate 运行环境。此外还需要导入 Hibernate 根据映射文档生成的所有数据类,这些都在 data 包中。数据对象中用 Time 和 Date 类来代表曲目的播放时间和创建时间戳(timestamp)。在 CreateTest 中实现的惟一方法是 main()方法,以支持从命令行的调用。
❷当运行这个类时,它首先创建一个 Hibernate Configuration 对象。由于我们没有告诉 Hibernate 什么其他信息,所以它默认在类路径的根上查找名为 hibernate.cfg.xml 的文件。Hibernate 会找到我们前面创建的这个配置文件,通过这个配置文件来告诉 Hibernate 我们正在使用 HSQLDB,以及如何找到数据库。这个例子的 XML 配置文件就包含了对 Track 对象的 Hibernate Mapping XML 文档的引用。调用 config.configure()就会自动加载 Track 类的映射文档。
❸为了创建和持久化曲目数据,这就是我们需要的所有配置,这样就为创建 SessionFactory 做好了准备。该对象的用途是为我们提供会话对象,它是同 Hibernate 进行交互的主要途径。SessionFactory 是线程安全的,在整个应用程序中只需要创建它的一个实例(更准确地说,对于每一个需要提供持久化服务的数据库环境,只需要一个 SessionFactory 实例;因此大多数应用程序只需要一个这样的实例)。创建会话工厂是一个需要花费相当代价和耗时的操作,所以应该在整个应用程序中共享这个实例。在只包含一个类的应用程序中进行这样的操作显得有些繁琐,不过,参考文档提供了一些在更实现的场景中应该如何应用的好例子。
注意:牢固地理解这些对象的目的和生命周期,值!本书介绍的知识足以让你起步了;你应该继续花些时间阅读参考文档,深入理解各个例子。
❹这一步才到了真正执行持久化的时候,让 SessionFactory 打开一个会话,这会建立一个到数据库的连接,并提供一个上下文(context)环境,在这个上下文中我们可以创建、获取、处理以及删除持久化对象。只要保持会话为打开状态,就会维护一个到数据库的连接,与会话相关联的持久对象的变化都可以被跟踪;这样,当关闭会话时,这些变化就可以应用到数据库。从概念上说,你可以把会话认为是持久化对象和数据库之间的一个“大型事务”,它可以包含多个数据库级的事务。不过,和数据库事务一样,在应用程序运行期间长时间地打开 Hibernate session(例如在等候用户输入时)。在应用程序中一个会话只用于一个特定的、边界有限的操作,例如生成用户界面或者根据用户提交的信息做出相应的变化。而随后的操作则使用一个新的会话。还要注意的是,会话对象本身不是线程安全的,所以不能在线程之间共享它们。每个线程需要从会话工厂类中获取它们自己的会话。
有必要深入介绍一下 Hibernate 中映射对象的生命周期,以及它与会话的关系,因为这一术语相当特殊,相关的概念也很重要。一个映射对象(例如我们的 Track 类的一个实例)会在与 Hibernate 相关的两个状态之间回来转换:瞬时状态(transient)和持久化状态(persistent)。处于瞬时状态的对象不与任何会话关联。当用 new()第一次创建一个 Track 实例时,它就是瞬时状态的;除非告诉 Hibernate 持久化这个瞬时状态的对象,否则当应用程序结束时,这个对象就会永远消失。
将一个瞬时状态的映射对象传递给会话的 save()方法,就会持久化保存这个对象,这样,在 Java VM 结束以后,这个数据对象还能够存在,直到以后显式地删除它。如果你已经有了一个持久化对象,并对它调用会话的 delete()方法,这个对象就会再回到瞬时状态。虽然这个对象在应用程序中仍然作为一个实例而存在,但它不会持久保存起来,除非你改变了主意,要再保存它一次。另一方面,如果你还没有删除这个对象(所以它仍然处于持久化状态),当修改这个对象后,为了让对象的变化得以反映到数据库中,并不需要再显式地保存它一次。Hibernate 会自动跟踪对任何持久化对象作出的修改,并在适当的时机将这些变化刷新写入到数据库中。当关闭会话时,任何延迟的变化都会被保存到数据库中。
注意:坚持一下,我们很快就回到例子的介绍!
当在已经关闭的会话中使用持久化对象,例如,当运行完一个查询,找到所有匹配某条件的实体(本章稍后的“检索持久化对象”一节将介绍如何做到这一点)以后,处理这些实体的状态就涉及一个重要但又微妙的要点。如前所述,保持会话打开的时间最好不要超过执行数据库操作的必要时间,所以在查询完成以后,就应该马上关闭会话。如果这时再处理已经加载的映射对象,会怎么样?嗯,当会话打开时,这些对象确实是持久化对象,但是它们现在不再与任何活动的会话相关联了(在这个例子中,是因为关闭了会话),所以它们就不再是持久化对象了。不过,这并不意味着它们代表的数据在数据库中也不存在了;相反,如果再次运行查询(假设这时没有人修改过数据),还是能够取回同样的一组对象。这只是意味着:数据对象的状态在虚拟机和数据库之间当前没有通信,它们处于脱管状态(detached)。完全有理由可以继续使用这种对象。如果以后需要修改这些对象,还想把这些修改持久保存,你可以打开一个新的会话,用它来保存修改过的对象。因为每个实体都有一个惟一的 ID,在新的会话中,Hibernate 没有问题地可以知道如何把处于瞬时状态的对象链接到正确的持久化对象。
当然,对脱管对象信息的修改都要由具体的数据库环境作为支持,所以你需要考虑应用程序级的数据完整性约束。可能需要设计某种高级的锁定或版本化协议来支持脱管对象的管理。虽然 Hibernate 为这一任务也提供了一定帮助,但是设计和实现细节还得由你负责。参考手册强烈推荐使用一个 version 字段,而且也有多种办法可供选用。
❺有了这些概念和术语,这个例子的其他部分就很容易理解了。我们用打开的会话建立了一个数据库事务,在这个事务内部,又创建了一些包含示例数据的 Track 实例,并在会话中进行保存,这样就把它们由瞬时状态的实例转变为持久化的实体。
❻最后,我们提交事务,自动地(作为一个单独的、不可分割的单元)将所有数据库修改持久化。环绕着所有这些代码周围的 try/catch/finally 块演示了在进行事务处理时一种重要而且有用的习惯用法。如果操作期间发生了任何错误,catch 块就会回滚(roll back)事务,再抛出异常。在 finally 部分中会关闭会话,以确保无论我们是成功提交事务后沿着“愉快的小路”正常退出,还是因为导致回滚的异常而退出,最终都可以关闭会话。
❼在方法最后,我们也关闭了会话工厂本身。这是应用程序中“优雅地关闭”(graceful shutdown)部分应该进行的操作。在 Web 应用环境中,这应该是一定的生命周期事件处理器。在这个简单的例子中,当 main()方法返回时,应用程序就正在结束。
现在一切就绪,通知 Ant 如何编译和运行这个测试程序就更简单了。将例 3-4 所示的构建目标添加到 build.xml 末尾的</project>关闭标签之前。
例 3-4:用于编译所有 Java 源代码,并调用数据创建测试的 Ant 构建目标
<!--Compile the java source of the project-->
<target name="compile"depends="prepare"❶
description="Compiles all Java classes">
<javac srcdir="${source.root}"
destdir="${class.root}"
debug="on"
optimize="off"
deprecation="on">
<classpath refid="project.class.path"/>
</javac>
</target>
<target name="ctest"description="Creates and persists some sample data"
depends="compile">❷
<java classname="com.oreilly.hh.CreateTest"fork="true">
<classpath refid="project.class.path"/>
</java>
</target>
❶命名得体的编译构建目标使用内建的 javac 构建任务,将 src 目录中的所有 Java 源文件编译到 classes 目录中。幸好,这个构建任务也支持我们已经建立的项目类路径,所以编译器能够找到我们正在使用的所有依赖库。构建目标定义中的 depends=prepare 属性是告诉 Ant 在运行编译构建目标以前,必须先运行 prepare。Ant 负责管理依赖关系,当构建具有依赖关系的多个目标时,能够以正确的顺序执行,每个依赖只执行一次,即便多个构建目标都引用了同一个依赖。
如果你习惯使用 shell 脚本来编译很多 Java 源代码,那么可能会对这么快的编译速度感到吃惊。Ant 调用的是它自己使用的虚拟机内部的 Java 编译器,所以没有针对每个编译的处理启动延迟。
❷ctest 构建目标使用编译来确保已经构建好了所有类文件,接着再创建一个新的 Java 虚拟机来运行我们的 CreateTest 类。
好了,我们可以创建一些数据了!例 3-5 显示了调用新的 ctest 构建目标的结果。ctest 要依赖于 compile 构建目标,这样就能确保在使用之前 CreateTest 类会先编译好。ctest 本身的输出结果会显示 Hibernate 所发出的日志信息(建立环境和映射数据以及数据库连接的关闭)。
例 3-5:调用 CreateTest 类
%ant ctest
prepare:
compile:
[javac]Compiling 2 source files to/Users/jim/svn/oreilly/hib_dev_2e/
current/examples/ch03/classes
ctest:
[java]00:21:45,833 INFO Environment:514-Hibernate 3.2.5
[java]00:21:45,852 INFO Environment:547-hibernate.properties not found
[java]00:21:45,864 INFO Environment:681-Bytecode provider name:cglib
[java]00:21:45,875 INFO Environment:598-using JDK 1.4 java.sql.Timestam
p handling
[java]00:21:46,032 INFO Configuration:1426-configuring from resource:/
hibernate.cfg.xml
[java]00:21:46,034 INFO Configuration:1403-Configuration resource:/hib
ernate.cfg.xml
[java]00:21:46,302 INFO Configuration:553-Reading mappings from resourc
e:com/oreilly/hh/data/Track.hbm.xml
[java]00:21:46,605 INFO HbmBinder:300-Mapping class:com.oreilly.hh.dat
a.Track->TRACK
[java]00:21:46,678 INFO Configuration:1541-Configured SessionFactory:n
ull
[java]00:21:46,860 INFO DriverManagerConnectionProvider:41-Using Hibern
ate built-in connection pool(not for production use!)
[java]00:21:46,862 INFO DriverManagerConnectionProvider:42-Hibernate co
nnection pool size:1
[java]00:21:46,864 INFO DriverManagerConnectionProvider:45-autocommit m
ode:false
[java]00:21:46,879 INFO DriverManagerConnectionProvider:80-using driver
:org.hsqldb.jdbcDriver at URL:jdbc:hsqldb:data/music
[java]00:21:46,891 INFO DriverManagerConnectionProvider:86-connection p
roperties:{user=sa, password=****,shutdown=true}
[java]00:21:47,533 INFO SettingsFactory:89-RDBMS:HSQL Database Engine,
version:1.8.0
[java]00:21:47,538 INFO SettingsFactory:90-JDBC driver:HSQL Database E
ngine Driver, version:1.8.0
[java]00:21:47,613 INFO Dialect:152-Using dialect:org.hibernate.dialec
t.HSQLDialect
[java]00:21:47,638 INFO TransactionFactoryFactory:31-Using default tran
saction strategy(direct JDBC transactions)
[java]00:21:47,646 INFO TransactionManagerLookupFactory:33-No Transacti
onManagerLookup configured(in JTA environment, use of read-write or transaction
al second-level cache is not recommended)
[java]00:21:47,649 INFO SettingsFactory:143-Automatic flush during befo
reCompletion():disabled
[java]00:21:47,650 INFO SettingsFactory:147-Automatic session close at
end of transaction:disabled
[java]00:21:47,657 INFO SettingsFactory:154-JDBC batch size:15
[java]00:21:47,659 INFO SettingsFactory:157-JDBC batch updates for vers
ioned data:disabled
[java]00:21:47,664 INFO SettingsFactory:162-Scrollable result sets:ena
bled
[java]00:21:47,666 INFO SettingsFactory:170-JDBC3 getGeneratedKeys():d
isabled
[java]00:21:47,668 INFO SettingsFactory:178-Connection release mode:au
to
[java]00:21:47,671 INFO SettingsFactory:205-Default batch fetch size:1
[java]00:21:47,678 INFO SettingsFactory:209-Generate SQL with comments:
disabled
[java]00:21:47,680 INFO SettingsFactory:213-Order SQL updates by primar
y key:disabled
[java]00:21:47,681 INFO SettingsFactory:217-Order SQL inserts for batch
ing:disabled
[java]00:21:47,684 INFO SettingsFactory:386-Query translator:org.hiber
nate.hql.ast.ASTQueryTranslatorFactory
[java]00:21:47,690 INFO ASTQueryTranslatorFactory:24-Using ASTQueryTran
slatorFactory
[java]00:21:47,694 INFO SettingsFactory:225-Query language substitution
s:{}
[java]00:21:47,695 INFO SettingsFactory:230-JPA-QL strict compliance:d
isabled
[java]00:21:47,702 INFO SettingsFactory:235-Second-level cache:enabled
[java]00:21:47,704 INFO SettingsFactory:239-Query cache:disabled
[java]00:21:47,706 INFO SettingsFactory:373-Cache provider:org.hiberna
te.cache.NoCacheProvider
[java]00:21:47,707 INFO SettingsFactory:254-Optimize cache for minimal
puts:disabled
[java]00:21:47,709 INFO SettingsFactory:263-Structured second-level cac
he entries:disabled
[java]00:21:47,724 INFO SettingsFactory:283-Echoing all SQL to stdout
[java]00:21:47,731 INFO SettingsFactory:290-Statistics:disabled
[java]00:21:47,732 INFO SettingsFactory:294-Deleted entity synthetic id
entifier rollback:disabled
[java]00:21:47,734 INFO SettingsFactory:309-Default entity-mode:pojo
[java]00:21:47,735 INFO SettingsFactory:313-Named query checking:enab
led
[java]00:21:47,838 INFO SessionFactoryImpl:161-building session factory
[java]00:21:48,464 INFO SessionFactoryObjectFactory:82-Not binding fact
ory to JNDI, no JNDI name configured
[java]Hibernate:insert into TRACK(TRACK_ID, title, filePath, playTime, a
dded, volume)values(null,?,?,?,?,?)
[java]Hibernate:call identity()
[java]Hibernate:insert into TRACK(TRACK_ID, title, filePath, playTime, a
dded, volume)values(null,?,?,?,?,?)
[java]Hibernate:call identity()
[java]Hibernate:insert into TRACK(TRACK_ID, title, filePath, playTime, a
dded, volume)values(null,?,?,?,?,?)
[java]Hibernate:call identity()
[java]00:21:49,365 INFO SessionFactoryImpl:769-closing
[java]00:21:49,369 INFO DriverManagerConnectionProvider:147-cleaning up
connection pool:jdbc:hsqldb:data/music
BUILD SUCCESSFUL
Total time:2 seconds
发生了什么事
如果你查看 Hibernate 打印输出的所有消息(因为我们打开了日志的"info"级别的输出标志),你可以看到我们的测试类会启动 Hibernate,加载 Track 类的映射信息,打开一个持久会话(persistence session)以连接相关的 HSQLDB 数据库,再创建一些实例,并用会话将它们持久化保存到 TRACK 数据库表中。接着,再关闭这个会话和数据库连接,以确保数据正确地完成存储。
运行完这个测试以后,你可以用 ant db 看一看数据库的内容。现在,你应该可以在 TRACK 表中看到三条记录,如图 3-1 所示(在窗口顶部的文本框中输入查询语句,点击"Execute"按钮。也可以选择菜单栏中的"Command"→"Select",会得到一个 SQL 命令的框架和语法说明文档)。
图 3-1 持久保存到 TRACK 表中的测试数据
现在,我们停下来一会儿,反思一个事实—我们没有编写任何连接数据库或执行 SQL 命令的代码。再回想上一节,我们甚至也不必亲自创建数据库表和封装数据的 Track 对象。但是,图 3-1 中查询输出显示的那些整整齐齐的数据表明,我们简短的测试程序确实已经创建了 Java 对象,并正确地进行了持久化处理。希望你可以因此而认同,作为一种持久化服务,Hibernate 真的功能强大、使用方便。作为一种免费的、轻型的工具,Hibernate 确实为我们完成了很多工作,这一切又是那么的快捷而简单!
如果你以前直接用过 JDBC,尤其是当你还不太熟悉它时,你可能习惯使用数据库驱动程序的"auto-commit"(自动提交)模式,而不是使用数据库事务处理(transaction)。Hibernate 同样认为这是构造应用程序的一种错误方法,“自动提交”模式惟一有意义的使用场合就是供人使用的数据库控制台环境。所以,在 Hibernate 应用中,持久化操作总是需要使用事务处理。
如第 1 章所述,你可以在 data 目录下的 music.script 文件中直接查看用于创建数据的 SQL 语句,如例 3-6 所示。
例 3-6:查看原始的数据库脚本文件
%cat data/music.script
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE MEMORY TABLE TRACK(TRACK_ID INTEGER GENERATED BY DEFAULT AS IDENTITY(STAR
T WITH 1)NOT NULL PRIMARY KEY, TITLE VARCHAR(255)NOT NULL, FILEPATH VARCHAR(255)
NOT NULL, PLAYTIME TIME, ADDED DATE, VOLUME SMALLINT NOT NULL)
ALTER TABLE TRACK ALTER COLUMN TRACK_ID RESTART WITH 4
CREATE USER SA PASSWORD""
GRANT DBA TO SA
SET WRITE_DELAY 10
SET SCHEMA PUBLIC
INSERT INTO TRACK VALUES(1,'Russian Trance','vol2/album610/track02.mp3','00:03:3
0','2007-06-17',0)
INSERT INTO TRACK VALUES(2,'Video Killed the Radio Star','vol2/album611/track12.
mp3','00:03:49','2007-06-17',0)
INSERT INTO TRACK VALUES(3,'Gravity''s Angel','vol2/album175/track03.mp3','00:06
:06','2007-06-17',0)
最后三条语句是我们的 TRACK 表的数据行。脚本的最前面包含了当创建新的数据库时,默认使用的模式和用户名。(当然,在实际应用环境中,你可能需要修改这些身份验证信息,除非数据库只供内存访问(in-memory access)。)
注意:想多学点 HSQLDB?我们支持你!
其他
对象和其他对象之间的关系呢?对象集合(collection)呢?没错,有些情况会让持久化处理更具挑战性(如果做得好的话,就相当有价值)。Hibernate 能妥善处理这种关联性。事实上,我们不需要为此花费什么力气。在第 4 章将会讨论这一点。就目前而言,让我们先看看如何将先前会话中持久保存的对象取出来。
[1] http://www.oreilly.com/catalog/9780596517724/. [2] 虽然 codegen 构建目标要依赖 prepare 构建目标,但第一次处理示例目录时,你需要先显式地运行 prepare 以创建正确的类路径结构,这样 Ant 以后才可以正常工作,如第 2 章的 2.3 节所述。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论