深入浅出 Hibernate4 开发


1 深 入浅 出 Hibernate4 Hibernate4 Hibernate4 Hibernate4 开发 2 第一部分Hibernate4 Hibernate4 Hibernate4 Hibernate4 入门 一、一、一、一、Hibernate Hibernate Hibernate Hibernate 是什么是什么是什么是什么 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 是什么 Hibernate 是 一个 轻量 级的 ORMapping 框架。 2.2.2.2. ORMapping ORMapping ORMapping ORMapping 原理(ObjectObjectObjectObject RelationalRelationalRelationalRelational MappingMappingMappingMapping) 3.3.3.3. ORMapping ORMapping ORMapping ORMapping 的基本对应规则 1: 类跟 表相 对应 2: 类的 属性 跟表 的字 段相 对应 3: 类的 实例 与表 中具 体的 一条 记录 相对 应 4: 一个 类可 以对 应多 个表 ,一 个表 也可 以对 应 多个类 5:DB中 的表 可以 没有 主键 ,但 是 Object 中 必须 设置 主键 字段 6:DB中 表与 表之 间的 关系 (如 :外 键) 映射 成为 Object 之 间的 关系 7:Object 中 属性 的个 数和 名称 可以 和表 中定 义的 字段 个数 和名 称不 一样 3 4.4.4.4. ORMapping ORMapping ORMapping ORMapping 的实现方式 使用JDBC,用SQL 来 操作 数据 库, 只是 看动 态生 成还 是人 工写 代码 来实 现。 大 家想 想, 我们 实现 过 ORMapping 吗? 二、二、二、二、Hibernate Hibernate Hibernate Hibernate 能干什么能干什么能干什么能干什么 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 能干什么 Hibernate 主 要用 来实 现 Java 对 象和 表之 间的 映射 ,除 此之 外还 提供 还提 供数 据查 询和 4 获 取数 据的 方法 ,可 以大 幅度 减少 开发 时人 工使 用 SQL 和JDBC 处 理数 据的 时间 。Hibernate 的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的95%。对于以数据为 中心的程序来说,它们往往只在数据库中使用存储过程来实现商业逻辑,Hibernate 可能不是 最 好的 解决 方案 ;对 于那 些在 基于 Java 的 中间 层应 用中 ,它 们实 现面 向对 象的 业务 模型 和商 业 逻辑 的应 用, Hibernate 是 最有 用的 。 Hibernate 可 以帮 助你 消除 或者 包装 那些 针对 特定 厂商 的 SQL 代码,并 且帮 你把 结果 集 从 表格 式的 表示 形式 转换 到一 系列 的对 象去 。 5 三、三、三、三、Hibernate Hibernate Hibernate Hibernate 有什么有什么有什么有什么 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 体系结构 6 2.2.2.2. Hibernate Hibernate Hibernate Hibernate 运行时体系结构 “最小”的 体系 结构 方案 ,要 求应 用程 序提 供自 己的 JDBC 连 接并 管理 自己 的事 务 。这 种方 案使 用了 Hibernate API 的 最小 子集 . 3.3.3.3. “全面解决”的体系结构方案 将 应用 层从 底层 的 JDBC/JTA API 中 抽象 出来 ,而 让 Hibernate 来 处理 这些 细节 7 4.4.4.4. SessionFactorySessionFactorySessionFactorySessionFactory org.hibernate.SessionFactory,针对单个数据库映射关系经过编译后的内存镜像,是线 程 安全 的( 不可 变 )。 它是 生成 Session 的 工厂 ,本 身要 用到 ConnectionProvider。 5.5.5.5. SessionSessionSessionSession org.hibernate.Session, 表示 应用 程序 与持 久储 存层 之间 交互 操作 的一 个单 线程 对象 ,此 对 象生 存期 很短 ,隐 藏了 JDBC 连 接, 也是 Transaction 的 工厂 。 6.6.6.6. TransactionTransactionTransactionTransaction org.hibernate.Transaction,应 用程 序用 来指 定原 子操 作单 元范 围的 对象 ,它 是单 线程 的 , 生 命周 期很 短。 它通 过抽 象将 应用 从底 层具 体的 JDBC、JTA 以及CORBA 事 务隔 离开 。 7.7.7.7. ConnectionProviderConnectionProviderConnectionProviderConnectionProvider org.hibernate.connection.ConnectionProvider,生成JDBC 连 接的 工厂 (有 连接 池的 作用 )。 它 通过 抽象 将应 用从 底层 的 Datasource 或DriverManager 隔 离开 。仅 供开 发者 扩展 /实 现用 , 并 不暴 露给 应用 程序 使用 。 8.8.8.8. TransactionFactoryTransactionFactoryTransactionFactoryTransactionFactory org.hibernate.TransactionFactory,生成Transaction 对象实例的工厂。仅供开发者扩展/ 实 现用 ,并 不暴 露给 应用 程序 使用 。 四、四、四、四、Hibernate Hibernate Hibernate Hibernate 的的的的HelloHelloHelloHello WorldWorldWorldWorld 1.1.1.1. 创建数据库 Create database hibernate; Use hibernate; Create table student(id int primary key auto_increment, name varchar(25) not null, age int); 8 Create table admin(id int primary key auto_increment, name varchar(25) not null, age int); 2.2.2.2. 创建域对象 1111))))Student.javaStudent.javaStudent.javaStudent.java————————编写 编写 编写 编写 hbm.xmlhbm.xmlhbm.xmlhbm.xml public public public public class class class class Student { private private private private Long id ; private private private private String name ; private private private private Integer age ; public public public public Long getId() {return return return return id ;} public public public public void void void void setId(Long id) {this this this this .id =id; } public public public public String getName() {return return return return name ;} public public public public void void void void setName(String name) {this this this this .name =name; } public public public public Integer getAge() {return return return return age ;} public public public public void void void void setAge(Integer age) {this this this this .age =age; } } Student.hbm.xml 9 其 中的 : http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd 要想提示,需要在Eclipse 中关联这个文件,hibernate-mapping-3.0.dtd 可以在 hibernate 发 布的 包中 搜索 到, 然后 在 Eclipse 中,preference->xml->category 添 加映 射: 如 下图 : 2222))))Admin.javaAdmin.javaAdmin.javaAdmin.java————————使用 使用 使用 使用 AnnotationAnnotationAnnotationAnnotation Admin.java @Entity @Table(name="admin") publicpublicpublicpublic classclassclassclass Admin { privateprivateprivateprivate Long id; privateprivateprivateprivate String name; privateprivateprivateprivate Integer age; 10 @Id @GeneratedValue () public public public public Long getId() {return return return return id ;} public public public public void void void void setId(Long id) {this this this this .id =id; } public public public public String getName() {return return return return name ;} public public public public void void void void setName(String name) {this this this this .name =name;} public public public public Integer getAge() {return return return return age ;} public public public public void void void void setAge(Integer age) {this this this this .age =age;} } 其中 @Entity 表示该对象是对应与数据库中的表,是实体对象 @Table (name= "admin" )表示对应于数据库中的 admin 表 @Id 放在 get 方法上面,表示是主键 @GeneratedValue ()ID自增 3333 编写hibernate.cfg.xml hibernate.cfg.xml hibernate.cfg.xml hibernate.cfg.xml 文件 关于这个文件,可以在开发文档中国找到例子,当然你也可以查看解压后projects/etc 目 录下 的 hibernate.cfg.xml 文 件。 另外,您还需要参见hibernate-configuration-3.0.dtd,可以利用windows 的搜索功能找 到 这个 文件 ,然 后关 联到 Eclipse 中 让其 能够 提示 。 11 Hibernate.cfg.xml com.mysql.jdbc.Driver jdbc:mysql://127.0.0.1:3306/hibernate root admin org.hibernate.dialect.MySQLDialect true 12 org.hibernate.cache.internal.NoCacheProvider 在 上面 我们 也看 到了 : 这个是以两种方式加载这个配置,Hibernate 需要通过这个配置来对实体类进行反射以 及 相关 的操 作。 数 据库 链接 配置 : org.hibernate.dialect.MySQLDialect com.mysql.jdbc.Driver jdbc:mysql://127.0.0.1:3306/hibernate 13 root admin 当 然我 们也 可以 通过 另一 种方 式来 配置 这些 项 : 使用hibernate.properties 文 件: hibernate.dialect org.hibernate.dialect.MySQLDialect hibernate.connection.driver_class com.mysql.jdbc.Driver hibernate.connection.url jdbc :mysql://127.0.0.1:3306/ hibernate hibernate.connection.username root hibernate.connection.password admin 将 此文 件置 于 classpath 的 根目 录下 。 4444 编写测试用例 1111))))HibernateUtil.javaHibernateUtil.javaHibernateUtil.javaHibernateUtil.java 本 类是 一个 单例 对象 ,专 门用 于产 生 session factory, 由于Session Factory 对 象很 大 ,要 加载hibernate 相 关的 配置 文件 ,因 此只 需要 在系 统启 动的 时候 创建 一份 实例 就行 了。 public public public public class class class class HibernateUtil { private private private private static static static static final final final final SessionFactory sessionFactory =buildSessionFactory (); /** *build the session factory with the hibernate.cfg.xml *@return @return @return @return */ private private private private static static static static SessionFactory buildSessionFactory() { try try try try { //Create the SessionFactory from hibernate.cfg.xml Configuration cfg =new new new new Configuration(); //This is necessary, no setting of dialect will be warned 14 cfg.configure(); ServiceRegistry sr =new new new new ServiceRegistryBuilder() .applySettings( cfg.getProperties() ).buildServiceRegistry(); SessionFactory sf =cfg.buildSessionFactory(sr); return return return return sf; }catch catch catch catch (Throwable ex) { System. err .println( "Initial SessionFactory creation failed!" +ex); throw throw throw throw new new new new ExceptionInInitializerError(ex); } } public public public public synchronized synchronized synchronized synchronized static static static static SessionFactory getSessionFactory() { return return return return sessionFactory ; } } 2222)测试实例 )测试实例 )测试实例 )测试实例 public public public public class class class class AllTests { @Test public public public public void void void void testSave() { Student student =new new new new Student(); student.setId(1L); student.setName( "诸葛亮 "); student.setAge(22); SessionFactory sf = HibernateUtil. getSessionFactory (); Session session =sf.openSession(); 15 session.beginTransaction(); session.save(student); session.getTransaction().commit(); } } 5555 运行测试实例 点 击运 行 Junit 测 试实 例, 如果 没抛 异常 就行 了。 项 目的 目录 结构 如下 : 16 第二部分Hibernate Hibernate Hibernate Hibernate 的基本配置 一、可编程方式配置一、可编程方式配置一、可编程方式配置一、可编程方式配置 1.1.1.1. 在java java java java 代码中配置——不推荐 如 果在 配置 cfg.xml 的 时候 ,不 想在 里面 配置 hbm.xml 怎 么办 呢? 可在 程序 里使 用 可 编程 的配 置方 式, 也就 是使 用程 序来 指定 在 cfg.xml 里 面的 配置 信息 ,不 推荐 这 种方 式。 如下 : Configuration cfg= new Configuration() .addResource("Item.hbm.xml") .addResource("Bid.hbm.xml"); 一 个替 代方 法( 有时 是更 好选 择) 是, 指定 被映 射的 类, 让 Hibernate 帮 你寻 找映 射 定 义文 件 : Configuration cfg= new Configuration() .addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class); 这 种方 式消 除了 任何 对文 件名 的硬 编码 。还 可以 通过 编程 的方 式来 指定 配置 属性 : Configuration cfg = new Configuration() .addClass(org.hibernate.auction.Item.class) .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") .setProperty("hibernate.order_updates", "true"); 2.2.2.2. 其他可以传入配置属性的方式 1:properties 文件 2:xml 文件 3: 设置 Java 的 系统 属性 ,形 如: java –Dproperty=value 17 3.3.3.3. 另外要注意一点 org.hibernate.cfg.Configuration 实例被设计成启动期间(startup-time )对象,一旦 SessionFactory 创 建完 成它 就被 丢弃 了。 二、二、二、二、xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-与数据库链接与数据库链接与数据库链接与数据库链接 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 与数据库连接的方式 ((((1111))))JDBCJDBCJDBCJDBC 属性用途 hibernate.connection.driver_class JDBC 驱动 hibernate.connection.url JDBCURL hibernate.connection.username 数 据库 用户 hibernate.connection.password 数 据库 用户 密码 hibernate.connection.pool_size 连 接池 容量 上限 数目 Mysql 示 例: com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/hibernate root admin 1 18 ((((2222))))DataSourceDataSourceDataSourceDataSource 示 例: java:/javassDs 2.2.2.2. 连接池c3p0 c3p0 c3p0 c3p0 配置 由于Hibernate 自 己实 现的 连接 池不 太好 ,在 项目 中 ,建 议使 用工 业级 的连 接池 ,比如: c3p0,Hibernate 发 行包 中带 有 c3p0, 下面 就是 其基 本配 置示 例: oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@localhost:1521:orcl javass javass 5 20 180 50 19 3.3.3.3. xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-可选配置 20 4.4.4.4. 数据库的catalog catalog catalog catalog 和schemaschemaschemaschema 为 了解 决数 据库 中元 素命 名冲 突的 问题 ,引 入 catalog 和schema 来 解决 。从 概念 上说 , 一 个数 据库 系统 包含 多个 Catalog,每个Catalog 又 包含 多个 Schema,而 每个 Schema 又 包含 多 个数 据库 对象 (表 、视 图、 字段 等 )。 比较简单而常用的实现方式是使用数据库名作为Catalog 名,使用用户名作为Schema 名 ,各 种数 据库 系统 对 Catalog 和Schema 的 支持 具体 可参 见下 表: 21 5.5.5.5. xxx.cfg.xml-JDBC xxx.cfg.xml-JDBC xxx.cfg.xml-JDBC xxx.cfg.xml-JDBC 连接属性 22 6.6.6.6. xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-缓存属性 23 7.7.7.7. xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-事务属性 24 8.8.8.8. xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-事务策略 (1)为了让应用在JDBC 事务和JTA 事务环境中可以移植,建议使用可选的Hibernate Transaction API, 它 包装 并隐 藏了 底层 系统 。 (2)通过设置Hibernate 配置属性hibernate.transaction.factory_class 来指定一个Transaction 实 例的 工厂 类。 (3) 有三 个标 准 (内建)的 选择 1) 委托 给数 据库 (JDBC)事 务( 默认 ) Hibernate4 以前:org.hibernate.transaction.JDBCTransactionFactory Hibernate4 里面:org.hibernate.engine.transaction.internal.jdbc.JDBCTransactionFactory 2)JTA 事务,如 果在 上下 文环 境中 存在 运行 着的 事务 (如, EJB 会话Bean 的 方法 ), 则委 托 给容 器管 理的 事务 , 否 则, 将启 动一 个新 的事 务, 并使 用 Bean 管 理的 事务 。 Hibernate4 以前: org.hibernate.transaction.JTATransactionFactory Hibernate4 里面: org.hibernate.engine.transaction.internal.jta.JTATransactionFactory 3) 委托 给容 器管 理的 JTA 事务 Hibernate4 以前: org.hibernate.transaction.CMTTransactionFactory Hibernate4 里面:org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory 也 可以 定义 属于 你自 己的 事务 策略 (如, 针对CORBA 的 事务 服务 ) 25 9.9.9.9. xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-其他属性 26 10.10.10.10.xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-方言 方 言可 以在 Hibernate 提 供的 文档 中找 到, 不用 可以 去记 忆, 即便 你记 住了 ,也 不代 表 你 是有 多牛 。 27 11.11.11.11.xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-xxx.cfg.xml-日志 (1)Hibernate 使用SLF4J 来做日志记录,可以根据你选择的绑定把日志输出到日志框架 (NOP、Simple、log4j version 1.2、JDK 1.4 logging、JCL 或logback) 上。 (2) 需要 在 classpath 里 加入 slf4j-api.jar 和 你选 择的 绑定 的 JAR 文 件( 使用 Log4J 时 加入slf4j-log4j12.jar), 当然 别忘 了加 入 log4j 自 己的 jar 包。 (3)Hibernate 日 志类 别 三、三、三、三、xxx.hbm.xml xxx.hbm.xml xxx.hbm.xml xxx.hbm.xml 配置配置配置配置 1.1.1.1. Hibernate-mapping Hibernate-mapping Hibernate-mapping Hibernate-mapping 元素 这 个元 素是 xxx.hbm.xml 配 置的 根元 素, 定义 如下 : 28 schemaschemaschemaschema(可选) (可选) (可选) (可选) 数 据库 schema 的 名称 ,表 名会 加上 所指 定的 schema 的 名字 扩展 为表 的全 限定 名。 CatalogCatalogCatalogCatalog(可选) (可选) (可选) (可选) 数 据库 catalog 的 名称 ,表 名会 加上 所指 定的 catalog 的 名字 扩展 为表 的全 限定 名。 default-cascadedefault-cascadedefault-cascadedefault-cascade ((((可选 可选 可选 可选 ----默认为 默认为 默认为 默认为 none)none)none)none) 默认的级联风格。指定了未明确注明cascade 属性的Java 属性和集合类,Hibernate 会 采 取什 么样 的默 认级 联风 格 。auto-import 属 性默 认让 我们 在查 询语 言中 可以 使用 非全 限定 名 的 类名 。 default-accessdefault-accessdefault-accessdefault-access ((((可选 可选 可选 可选 ----默认为 默认为 默认为 默认为 property)property)property)property) Hibernate 用 来访 问所 有属 性的 策略 。可 以通 过实 现 PropertyAccessor 接 口自 定义 。 default-lazy(default-lazy(default-lazy(default-lazy(可选 可选 可选 可选 ---- 默认为 默认为 默认为 默认为 true)true)true)true) 指 定了 未明 确注 明 lazy 属 性的 Java 属 性和 集合 类 ,Hibernate 会 采取 什么 样的 默认 加载 风 格。 auto-importauto-importauto-importauto-import ((((可选 可选 可选 可选 ----默认为 默认为 默认为 默认为 true)true)true)true) 指 定我 们是 否可 以在 查询 语言 中使 用非 全限 定的 类名 (仅 限于 本映 射文 件中 的类 )。 packagepackagepackagepackage ((((可选 可选 可选 可选 )))) 指 定一 个包 前缀 ,如 果在 映射 文档 中没 有指 定全 限定 的 类名 ,就 使用 这个 作为 包名 。 假 若你 有两 个持 久化 类 ,它 们的 非全 限定 名是 一样 的 (就 是两 个类 的名 字一 样 ,所 在的 包 不一 样 ), 你应 该设 置 auto-import="false"。 如果 你把 一个 “import 过”的 名字 同时 对应 两个 类,Hibernate 会 抛出 一个 异常 。 29 2.2.2.2. Lazy Lazy Lazy Lazy 和1+N 1+N 1+N 1+N 次查询 1+N 1+N 1+N 1+N 次查询的问题 次查询的问题 次查询的问题 次查询的问题 如果设置了装载策略为lazy,那么可能会带来有名的1+N 次查询的问题,1+N 有两种 典 型的 体现 ,一 个是 以 Iterator 为 代表 ,一 个是 以关 系映 射为 代表 。 以以以以Iterator Iterator Iterator Iterator 为代表的 为代表的 为代表的 为代表的 1+N 1+N 1+N 1+N 次查询 次查询 次查询 次查询 1: 第一 次查 询的 时候 :如 果是 设置 了 lazy 的 对象 , Hibernate 会 只装 载主 键的 值 2:那 么以 后每 次真 正调 用一 个对 象的 时候 ,Hibernate 发 现这 个对 象没 有值 ,只 有主 键 , 那么Hibernate 会 用主 键做 条件 重新 查询 一次 。 3: 设若 N条 记录 后来 都访 问了 ,那 么总 的查 询次 数就 是 1+N 次 以关系映射为代表的 以关系映射为代表的 以关系映射为代表的 以关系映射为代表的 1+N 1+N 1+N 1+N 次查询 次查询 次查询 次查询 1: 第一 次查 询出 有 N条Parent 对象 2:当访问每个Parent 对象里面的Child 对象或Child 对象集合的时候,会重新用一条 sql 去 访问 Child 对象。 3: 设若 N条Parent 对 象都 访问 了里 面的 Child, 那么 总的 查询 次数 就是 1+N 次 3.3.3.3. Class Class Class Class 元素 元素 使用class 元 素来 定义 一个 持久 化类 : namenamenamename ((((可选 可选 可选 可选 ):):):): 持 久化 类( 或者 接口 )的 Java 全 限定 名。 如果 这个 属性 不存 在, Hibernate 将 假定 这是 一 个非 POJO 的 实体 映射 。若 指明 的持 久化 类实 际上 是一 个接 口 ,这 也是 可以 的 ,可 以用 元 素来 指定 该接 口的 实际 实现 类。 tabletabletabletable ((((可选 可选 可选 可选 ----默认是类的非全限定名 默认是类的非全限定名 默认是类的非全限定名 默认是类的非全限定名 )))) 对 应的 数据 库表 名 。 discriminator-valuediscriminator-valuediscriminator-valuediscriminator-value ((((可选 可选 可选 可选 ---- 默认和类名一样 默认和类名一样 默认和类名一样 默认和类名一样 )))) 一 个用 于区 分不 同的 子类 的值 ,在 多态 行为 时使 用 。它 可以 接受 的值 包 括null 和not null 31 mutablemutablemutablemutable ((((可选,默认值为 可选,默认值为 可选,默认值为 可选,默认值为 true)true)true)true) 表 明该 类的 实例 是可 变的 或者 不可 变的 。不 可变 类, mutable="false"不 可以 被应 用程 序 更 新或 者删 除。 schemaschemaschemaschema (optional)(optional)(optional)(optional) 覆 盖在 根元 素 中 指定 的 schema。 catalogcatalogcatalogcatalog (optional)(optional)(optional)(optional) 覆 盖在 根元 素 中 指定 的 catalog。 proxyproxyproxyproxy ((((可选 可选 可选 可选 )))) 指 定一 个接 口 ,在 延迟 装载 时作 为代 理使 用 。可 选的 proxy 属 性允 许延 迟加 载类 的持 久 化 实例 。Hibernate 开 始会 返回 实现 了这 个命 名接 口的 CGLIB 代理。当 代理 的某 个方 法被 实 际 调用 的时 候, 真实 的持 久化 对象 才会 被装 载。 dynamic-updatedynamic-updatedynamic-updatedynamic-update ((((可选 可选 可选 可选 ,,,, 默认为 默认为 默认为 默认为 false)false)false)false) 指 定用 于 UPDATE 的SQL 将 会在 运行 时动 态生 成, 并且 只更 新那 些改 变过 的字 段。 dynamic-insertdynamic-insertdynamic-insertdynamic-insert ((((可选 可选 可选 可选 ,,,, 默认为 默认为 默认为 默认为 false)false)false)false)。。。。 指 定用 于 INSERT 的SQL 将 会在 运行 时动 态生 成, 并且 只包 含那 些非 空值 字段 select-before-updateselect-before-updateselect-before-updateselect-before-update ((((可选 可选 可选 可选 ,,,, 默认为 默认为 默认为 默认为 false)false)false)false)。。。。 指定Hibernate 除 非确 定对 象真 正被 修改 了 ,否 则不 会执 行 SQL UPDATE 操作。在 特定 场 合( 实际 上, 它只 在一 个瞬 时对 象( transient object) 关联 到一 个新 的 session 中 时执 行 的 update()中 生效 ),这 说明 Hibernate 会在UPDATE 之 前执 行一 次额 外的 SQL SELECT 操作, 来 决定 是否 应该 执行 UPDATE。 使用 select-before-update 通 常会 降低 性能 。 32 polymorphismpolymorphismpolymorphismpolymorphism(多态) (多态) (多态) (多态) ((((可选 可选 可选 可选 ,,,, 默认值为 默认值为 默认值为 默认值为 implicitimplicitimplicitimplicit ((((隐式 隐式 隐式 隐式 )))))))) 界定是隐式还是显式的使用多态查询,这只在Hibernate 的具体表继承策略中用到。 Implicit 隐 式多 态是 指: 如果 查询 时给 出的 是任 何超 类、 该类 实现 的接 口或 者该 类的 名字 , 都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。Explicit 显 式多 态是 指 :只 有在 查询 时给 出明 确的 该类 名字 时才 会返 回这 个类 的实 例 ;同 时只 有在 这 个的定义中作为 或者出现的子类,才可能被返回。在大 多 数情 况下 ,默 认的 polymorphism="implicit"都 是合 适的 。 wherewherewherewhere ((((可选 可选 可选 可选 )))) 指 定一 个附 加的 SQLWHERE 条 件, 在抓 取这 个类 的对 象时 会一 直增 加这 个条 件 。 persister(persister(persister(persister(可选 可选 可选 可选 )))) 指 定一 个定 制的 Class Persister。 batch-sizebatch-sizebatch-sizebatch-size ((((可选 可选 可选 可选 ,,,,默认是 默认是 默认是 默认是 1)1)1)1) 指 定一 个用 于根 据标 识符 ( identifier) 抓取 实例 时使 用的 “batch size”( 批次 抓取 数量 ) optimistic-lockoptimistic-lockoptimistic-lockoptimistic-lock(乐观锁定) (乐观锁定) (乐观锁定) (乐观锁定) ((((可选,默认 可选,默认 可选,默认 可选,默认 version)version)version)version)。。。。 决 定乐 观锁 定的 策略 。 如 果你 打开 了 dynamic-update, 你可 以选 择几 种乐 观锁 定的 策略 : 1)version( 版本 检查 )检 查 version/timestamp 字段 2)all( 全部 )检 查全 部字 段 3)dirty( 脏检 查) 只检 察修 改过 的字 段 4)none( 不检 查) 不使 用乐 观锁 定 非常强烈建议你在Hibernate 中使用version/timestamp 字段来进行乐观锁定。对性能来 说 ,这 是最 好的 选择 ,并 且这 也是 唯一 能够 处理 在 session 外 进行 操作 的策 略 。 33 lazylazylazylazy ((((可选 可选 可选 可选 )))) 延 迟加 载( Lazy fetching)。 entity-nameentity-nameentity-nameentity-name ((((可选,默认为类名 可选,默认为类名 可选,默认为类名 可选,默认为类名 )))) Hibernate4 允许一个类进行多次映射(前提是映射到不同的表),并且允许使用Maps 或XML 代替Java 层 次的 实体 映射 (也 就是 实现 动态 领域 模型 ,不 用写 持久 化类 )。 checkcheckcheckcheck ((((可选 可选 可选 可选 )))) 这 是一 个 SQL 表 达式 ,用 于为 自动 生成 的 schema 添 加多 行约 束检 查 。 rowid(rowid(rowid(rowid(可选 可选 可选 可选 )))) Hibernate 可以使用数据库支持的所谓的ROWIDs,例如:Oracle 数据库,如果你设置 这 个可 选的 rowid,Hibernate 可 以使 用额 外的 字段 rowid 实 现快 速更 新。 subselect(subselect(subselect(subselect(可选 可选 可选 可选 )))) 它将一个不可变(immutable)并且只读的实体映射到一个数据库的子查询中。当你想 用 视图 代替 一张 基本 表的 时候 ,这 是有 用的 ,但 最好 不要 这样 做 。 abstractabstractabstractabstract ((((可选 可选 可选 可选 )))) 用 于在 的 继承 结构 中标 识抽 象超 类 4.4.4.4. 多次映射一个类 如 果想 要多 次映 射同 一个 类, 可以 采用 如下 的方 式: 对 特定 的持 久化 类, 映射 多次 是允 许的 。这 种情 形下 ,你 必须 指定 entity name 来 区别 同 映射 实体 的对 象实 例。 默认 情况 下, 实体 名字 和类 名是 相同 的。 Hibernate 在 操作 持久 化 对象、编 写查 询条 件 ,或 者把 关联 映射 到指 定实 体时 ,允 许你 指定 这个 entity name(实 体名 字)。 34 5.5.5.5. Version Version Version Version 元素配置 元素 元 素是 可选 的, 表明 表中 包含 附带 版本 信息 的数 据。 columncolumncolumncolumn(可选 (可选 (可选 (可选 ————默认为属性名) 默认为属性名) 默认为属性名) 默认为属性名) 指 定持 有版 本号 的字 段名 。 nnnnameameameame 持 久化 类的 属性 名。 typetypetypetype(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 integerintegerintegerinteger)))) 版 本号 的类 型。 accessaccessaccessaccess(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 propertypropertypropertyproperty)))) Hibernate 用 来访 问属 性值 的策 略。 unsaved-valueunsaved-valueunsaved-valueunsaved-value(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 undefinedundefinedundefinedundefined)))) 用 于标 明某 个实 例是 刚刚 被实 例化 的 (尚 未保 存 )版 本属 性值 ,依 靠这 个值 就可 以把 这 35 种 情况 和已 经在 先前 的 session 中 保存 或装 载的 脱管 (detached)实 例区 分开 来 。(undefined 指 明应 被使 用的 标识 属性 值 。) generatedgeneratedgeneratedgenerated(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 nevernevernevernever)))) 表 明此 版本 属性 值是 否实 际上 是由 数据 库生 成的 。 insertinsertinsertinsert(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 truetruetruetrue)))) 表 明此 版本 列应 该包 含在 SQL 插 入语 句中 。只 有当 数据 库字 段有 默认 值 0 的 时候 ,才 可 以设 置为 false。 6.6.6.6. 乐观锁 (1)首 先在 数据 库表 中添 加一 个字 段: version , 类型 为 number (2)在UserModel 里面添加一个字段:version,类型为int,也要对应的getter 和setter 方 法。 (3)在UserModel.hbm.xml 中 添加 配置 如下 : 36 (4)然 后把 数据 库 tbl_user 中 的数 据全 部清 除掉 ,这 样好 观察 数据 变化 (5)运 行原 来的 client,在 数据 库中 生成 一条 数据 ,值是:uuid=1,userId=id1,name=name1, age=1,version=0 (6)注 意: version 字 段为 Hibernate 自 己维 护, 程序 中不 需要 操作 这个 字段 (7)然 后写 新的 客户 端进 行测 试, 如下 : import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class Client { public static void main(String[] args) { SessionFactory sf= new Configuration().configure().buildSessionFactory(); Session session1 = null; Session session2 = null; try{ // 有使用者 1开启了一个 session1 session1 = sf.openSession(); // 在这之后,马上有另一个使用者 2开启了 session2 session2 = sf.openSession(); // 使用者 1查询数据 UserModeluserV1 = (UserModel) session1.load(UserModel.class, "1"); // 使用者 2查询同一条数据 UserModeluserV2 = (UserModel) session2.load(UserModel.class, "1"); // 此时两个版本号是相同的 System.out.println("v1="+ userV1.getVersion() + ",v2="+ userV2.getVersion()); Transaction tx1 = session1.beginTransaction(); Transaction tx2 = session2.beginTransaction(); // 使用者 1更新数据 userV1.setAge(111); tx1.commit(); // 此时由于数据更新,数据库中的版本号递增了 // 两笔数据版本号不一样了 System.out.println("v1="+userV1.getVersion()+",v2="+ userV2.getVersion()); // userV2 的age 数据还是旧的 // 数据更新 userV2.setName("version test"); // 因版本号比数据库中的旧 // 修改会失败,抛出 StableObjectStateException 例外 tx2.commit(); } catch(Exception err ){ err.printStackTrace(); 37 } finally { session1.close(); session2.close(); } } 7.7.7.7. IdIdIdId元素 被 映射 的类 必须 定义 对应 数据 库表 主键 字段 。大 多数 类有 一个 JavaBeans 风 格的 属性 , 为 每一 个实 例包 含唯 一的 标识 。 元 素定 义了 该属 性到 数据 库表 主键 字段 的映 射。 (5) namenamenamename ((((可选 可选 可选 可选 )))) 标 识属 性的 名字 。 typetypetypetype ((((可选 可选 可选 可选 )))) 标识Hibernate 类 型的 名字 。 columncolumncolumncolumn ((((可选 可选 可选 可选 ----默认为属性名 默认为属性名 默认为属性名 默认为属性名 )))) 主 键字 段的 名字 。 unsaved-valueunsaved-valueunsaved-valueunsaved-value ((((可选 可选 可选 可选 ----默认为一个切合实际( 默认为一个切合实际( 默认为一个切合实际( 默认为一个切合实际( sensiblesensiblesensiblesensible)的值 )的值 )的值 )的值 )))) 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。unsaved-value 属 性在Hibernate4 中 几乎 不再 需要 。 38 accessaccessaccessaccess ((((可选 可选 可选 可选 ----默认为 默认为 默认为 默认为 property)property)property)property) Hibernate 用 来访 问属 性值 得策 略 。 8.8.8.8. Hibernate Hibernate Hibernate Hibernate 的类型 Hibernate 在 进行 映射 的时 候, 既不 使用 Java 的 类型 ,也 不使 用数 据库 的类 型, 而是 使 用 自己 提供 的类 型, Hibernate 提 供大 量的 内建 类型 ,也 支持 自定 义类 型。 (1)Hibernate 的 内建 类型 内 置的 basic mapping types 可 以大 致地 分类 为: 1)integer, long, short, float, double, character, byte, boolean, yes_no, true_false 这 些类 型都 对应 Java 的 原始 类型 或其 封装 类, 来符 合特 定厂 商的 SQL 字 段类 型。 2)boolean,yes_no 和true_false 是Java 中boolean 或者Boolean 的 另外 说法 。 3)string 从java.lang.String 到VARCHAR( 或者 Oracle 的VARCHAR2) 的映 射。 4)date, time, timestamp 从java.util.Date 和 其子 类到 SQL 类型DATE,TIME 和TIMESTAMP( 或等 价类 型 )的 映 射。 5)calendar, calendar_date 从java.util.Calendar 到SQL 类型TIMESTAMP 和DATE( 或等 价类 型) 的映 射。 6)big_decimal, big_integer 从java.math.BigDecimal 和java.math.BigInteger 到NUMERIC(或者Oracle 的NUMBER 类 型) 的映 射。 7)locale, timezone, currency 从java.util.Locale,java.util.TimeZone 和java.util.Currency 到VARCHAR(或者Oracle 的 VARCHAR2 类 型) 的映 射。 Locale 和Currency 的 实例 被映 射为 它们 的 ISO 代 码。TimeZone 的 实例 被影 射为 它的 ID。 8)class 从java.lang.Class 到VARCHAR(或者Oracle 的VARCHAR2 类型)的映射。Class 被 39 映 射为 它的 全限 定名 。 9)binary 把 字节 数组 ( byte arrays) 映射 为对 应的 SQL 二 进制 类型 。 10)text 把长Java 字 符串 映射 为 SQL 的CLOB 或者TEXT 类 型。 11)serializable 把可序列化的Java 类型映射到对应的SQL 二进制类型。你也可以为一个并非默认为 基 本类 型的 可序 列化 Java 类 或者 接口 指定 Hibernate 类型serializable。 12)clob, blob JDBC 类java.sql.Clob 和java.sql.Blob 的 映射 。某 些程 序可 能不 适合 使用 这个 类型 ,因 为blob 和clob 对 象可 能在 一个 事务 之外 是无 法重 用的 。( 而且 , 驱 动程 序对 这种 类型 的 支持 充满 着补 丁和 前后 矛盾 。) 13)imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary 一 般来 说 ,映 射类 型被 假定 为是 可变 的 Java 类型,只 有对 不可 变 Java 类型,Hibernate 会 采取 特定 的优 化措 施 ,应 用程 序会 把这 些对 象作 为不 可变 对象 处理 。比如,你 不应 该对 作 为imm_timestamp 映 射的 Date 执行Date.setTime()。要 改变 属性 的值 ,并 且保 存这 一改 变 , 应用程序必须对这一属性重新设置一个新的(不一样的)对象。在org.hibernate.Hibernate 中 ,定 义了 基础 类型 对应 的 Type 常 量。 比如 , Hibernate.STRING 代表string 类 型。 9.9.9.9. composite-id composite-id composite-id composite-id 元素 如 果表 使用 联合 主键 ,你 可以 映射 类的 多个 属性 为标 识符 属性 。这 种做 法现 在是 非常 不 推 荐的 ,但 可能 有些 遗留 系统 使用 了双 主键 或多 主键 。 配 置如 下示 例: 就 表示 是一 个双 主键 ,由name 和deptment 联 合来 做主 键使 用Composite-id 元 素的 时候 , 40 你 的持 久化 类必 须覆 盖 equals() 和hashCode()方法,来 实现 组合 的标 识符 的相 等判 断 。实现 Serializable 接 口也 是必 须的 。 10.10.10.10.Generator Generator Generator Generator 元素 可选的子元素是一个Java 类的名字,用来为该持久化类的实例生成唯一的 标识。如 果这 个生 成器 实例 需要 某些 配置 值或 者初 始化 参数 ,用元 素来 传递 。如下 示 例: person_id_sequence 下 面是 一些 Hibernate 内 置生 成器 : incrementincrementincrementincrement 用于为long, short 或者int 类型生成唯一标识。只有在没有其他进程往同一张表中插入 数 据时 才能 使用 。在 集群 下不 要使 用。 identityidentityidentityidentity 对DB2,MySQL, MSSQL Server, Sybase 和HypersonicSQL 的内置标识字段提供支持。 返 回的 标识 符是 long, short 或者int 类 型的 。 SequenceSequenceSequenceSequence 在DB2,PostgreSQL, Oracle, SAPDB, McKoi 中使用sequence,而在Interbase 中使用 生 成器 (generator)。 返回 的标 识符 是 long, short 或int 类 型的 。 hilohilohilohilo 使 用一 个高 /低 位算 法高 效的 生成 long, short 或者int 类 型的 标识 符 。给 定一 个表 和 字段(默认分别是hibernate_unique_key 和next_hi)作为高位值的来源。高/低位算法 生 成的 标识 符只 在一 个特 定的 数据 库中 是唯 一的 。 SeqhiloSeqhiloSeqhiloSeqhilo 使 用一 个高 /低 位算 法来 高效 的生 成 long, short 或者int 类 型的 标识 符 ,给 定一 个数 据 库序 列( sequence)的 名字 。 41 UuidUuidUuidUuid 用一个128-bit 的UUID 算法生成字符串类型的标识符,这在一个网络中是唯一的 ( 使用 了 IP 地址)。UUID 被 编码 为一 个 32位16进 制数 字的 字符 串。 guidguidguidguid 在MSSQL Server 和MySQL 中 使用 数据 库生 成的 GUID 字 符串 。 nativenativenativenative 根 据底 层数 据库 的能 力选 择 identity, sequence 或者hilo 中 的一 个 。 AssignedAssignedAssignedAssigned 让 应用 程序 在 save()之 前为 对象 分配 一个 标示 符 。这是元 素没 有指 定时 的 默认 生成 策略 。 selectselectselectselect 通 过数 据库 触发 器选 择一 些唯 一主 键的 行并 返回 主键 值来 分配 一个 主键 。 foreignforeignforeignforeign 使 用另 外一 个相 关联 的对 象的 标识 符。 通常 和 联 合起 来使 用 。 推 荐使 用 UUIDUUIDUUIDUUID UUID 包含:IP 地址、JVM 的启动时间(精确到1/4 秒)、系统时间和一个计数 器 值( 在 JVM 中 唯一 )。 11.11.11.11.Property Property Property Property 元素 元 素为 类定 义了 一个 持久 化的 ,JavaBean 风 格的 属性 。 namenamenamename 属 性的 名字 ,以 小写 字母 开头 columncolumncolumncolumn ((((可选 可选 可选 可选 ---- 默认为属性名字 默认为属性名字 默认为属性名字 默认为属性名字 )))) 对 应的 数据 库字 段名 。也 可以 通过 嵌套 的 元 素指 定。 typetypetypetype ((((可选 可选 可选 可选 )))) 一个Hibernate 类 型的 名字 。 typename 可 以是 如下 几种 : A:Hibernate 基本类型名(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。 B:一个Java 类的名字,这个类属于一种默认基础类型(比如:int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。 C: 一个 可以 序列 化的 Java 类 的名 字。 D: 一个 自定 义类 型的 类的 名字 。( 比如 : cn.javass.type.MyCustomType)。 如 果你 没有 指定 类型 , Hibernarte 会 使用 反射 来得 到这 个名 字的 属性 ,以 此来 猜测 正确 的Hibernate 类 型。 Hibernate 会 按照 规则 B,C,D 的 顺序 对属 性读 取器 (getter 方 法) 的返 回类 进行解释。然而,这还不够。在某些情况下你仍然需要type 属性。(比如,为了区别 Hibernate.DATE 和Hibernate.TIMESTAMP,或 者为 了指 定一 个自 定义 类型 。) update,update,update,update, insertinsertinsertinsert ((((可选 可选 可选 可选 ----默认为 默认为 默认为 默认为 true)true)true)true) 表明用于UPDATE 和/或INSERT 的SQL 语句中是否包含这个被映射了的字段。这二 者 如果 都设 置为 false 则 表明 这是 一个 “外 源性 ( derived)”的 属性 ,它 的值 来源 于映 射到 同 43 一 个( 或多 个) 字段 的某 些其 他属 性, 或者 通过 一个 trigger(触 发器 )或 其他 程序 生成 。 formulaformulaformulaformula ((((可选 可选 可选 可选 )))) 一个SQL 表达式,定义了这个计算属性的值。计算属性没有和它对应的数据库字段, 相 当于 是一 个子 查询 。 accessaccessaccessaccess ((((可选 可选 可选 可选 ––––默认值 默认值 默认值 默认值 property)property)property)property) Hibernate 访问属性值的策略access 属性用来让你控制Hibernate 如何在运行时访问属 性。在默认情况下,Hibernate 会使用属性的get/set 方法对。如果你指明access="field", Hibernate 会忽略get/set 方法对,直接使用反射来访问成员变量。你也可以指定你自己的策 略,这 就需 要你 自己 实现 org.hibernate.property.PropertyAccessor 接口,再在access 中 设置 你 自 定义 策略 类的 名字 。 lazylazylazylazy ((((可选 可选 可选 可选 ---- 默认为 默认为 默认为 默认为 false)false)false)false) 指 定指 定实 例变 量第 一次 被访 问时 ,这 个属 性是 否延 迟抓 取 (fetched lazily)(需 要运 行 时 字节 码增 强 )。 uniqueuniqueuniqueunique ((((可选 可选 可选 可选 )))) 使用DDL 为 该字 段添 加唯 一的 约束 。同 样, 允许 它作 为 property-ref 引 用的 目标 。 not-nullnot-nullnot-nullnot-null ((((可选 可选 可选 可选 )))) 使用DDL 为 该字 段添 加可 否为 空( nullability) 的约 束。 optimistic-lockoptimistic-lockoptimistic-lockoptimistic-lock ((((可选 可选 可选 可选 ---- 默认为 默认为 默认为 默认为 true)true)true)true) 指 定这 个属 性在 做更 新时 是否 需要 获得 乐观 锁定 ( optimistic lock)。 换句 话说 ,它 决定 这 个属 性发 生脏 数据 时版 本( version) 的值 是否 增长 。 44 generatedgeneratedgeneratedgenerated ((((可选 可选 可选 可选 ––––默认值 默认值 默认值 默认值 never)never)never)never) 表 明此 属性 值是 否由 数据 库生 成 12.12.12.12.组件映射 元素 元素把子对象的一些元素与父类对应的表的一些字段映射起来。然后组件可 以 定义 它们 自己 的属 性、 组件 或者 集合 。 NameNameNameName 属 性名 。 classclassclassclass(可选 (可选 (可选 (可选 ————默认为通过反射得到的属性类型) 默认为通过反射得到的属性类型) 默认为通过反射得到的属性类型) 默认为通过反射得到的属性类型) 组 件( 子) 类的 名字 。 45 InsertInsertInsertInsert 被 映射 的字 段是 否出 现在 SQL 的INSERT 语 句中 ? UpdateUpdateUpdateUpdate 被 映射 的字 段是 否出 现在 SQL 的UPDATE 语 句中 ? accessaccessaccessaccess(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 propertypropertypropertyproperty)))) Hibernate 用 来访 问属 性值 的策 略。 lazylazylazylazy(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 falsefalsefalsefalse)))) 表 明此 组件 应在 实例 变量 第一 次被 访问 的时 候延 迟加 载( 需要 编译 时字 节码 装置 器 )。 optimistic-lockoptimistic-lockoptimistic-lockoptimistic-lock(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 truetruetruetrue)))) 表 明更 新此 组件 是否 需要 获取 乐观 锁 。换 句话 说 ,当 这个 属性 变脏 时 ,是 否增 加版 本号 (Version)。 uniqueuniqueuniqueunique(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 falsefalsefalsefalse)))) 表 明组 件映 射的 所有 字段 上都 有唯 一性 约束 。其 子 标签 为子 类的 一些 属性 与 表字段之间建立映射。组件(Component)是一个被包含的对象,在持久化的过程中,它被当 作 值类 型, 而并 非一 个实 体的 引用 ,指 的是 对象 的合 成。 使用 示例 如下 : 1: 新建 一个 PersonModel, 包含 字段 : userId 和name 2:修改UserModel,在 里面 去掉 userId 和name,然 后添 加一 个字 段 :p,类 型为 PersonModel, 也 要对 应的 getter 和setter 方法 3:在UserModel.hbm.xml 中 修改 配置 如下 : 46 4: 相应 的修 改 Client 文 件如 下: //准 备数 据 UserModelum = new UserModel(); um.setUuid("13"); um.setAge(1); PersonModelpm = new PersonModel(); pm.setUserId("id1"); pm.setName("name1"); um.setP(pm); 13.13.13.13.join join join join 元素 元素,使用 元素,假若在表之间存在一对一关联,可以将一个类的属性映 射 到多 张表 中。 47 TableTableTableTable 被 连接 表的 名称 。 schemaschemaschemaschema(可选) (可选) (可选) (可选) 覆 盖在 根 元 素中 指定 的 schema 名 字。 catalogcatalogcatalogcatalog(可选) (可选) (可选) (可选) 覆 盖在 根 元 素中 指定 的 catalog 名 字。 fetchfetchfetchfetch(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 joinjoinjoinjoin)))) 如果设置为默认值join,Hibernate 将使用一个内连接来得到这个类或其超类定义的 ,而 使用 一个 外连 接来 得到 其子 类定 义的 。如 果设 置为 select,则Hibernate 将 为 子类 定义 的 使 用顺 序选 择 。这 仅在 一行 数据 表示 一个 子类 的对 象的 时候 才会 发生 。 对 这个 类和 其超 类定 义的 , 依然 会使 用内 连接 得到 。 inverseinverseinverseinverse(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 falsefalsefalsefalse)))) 如 果打 开, Hibernate 不 会插 入或 者更 新此 连接 定义 的属 性。 optionaloptionaloptionaloptional(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 falsefalsefalsefalse)))) 如 果打 开, Hibernate 只 会在 此连 接定 义的 属性 非空 时插 入一 行数 据, 并且 总是 使用 一 个 外连 接来 得到 这些 属性 。 例 如, 一个 人( person) 的地 址( address) 信息 可以 被映 射到 单独 的表 中( 并保 留 所 有属 性的 值类 型语 义 ): ... ... 此 特性 常常 对遗 留数 据模 型有 用 14.14.14.14.properties properties properties properties 映射 元 素允 许定 义一 个命 名的 逻辑 分组 (grouping)包 含一 个类 中的 多个 属性 。 这个元素最重要的用处是允许多个属性的组合作为property-ref 的目标(target)。这也是定 义 字段 唯一 约束 的一 种方 便途 径。 基本定义如下基本定义如下基本定义如下基本定义如下 49 NamNamNamNameeee 分 组的 逻辑 名称 —不 是实 际属 性的 名称 。 InsertInsertInsertInsert 被 映射 的字 段是 否出 现在 SQL 的INSERT 语 句中 updateupdateupdateupdate 被 映射 的字 段是 否出 现在 SQL 的UPDATE 语 句中 optimistic-lockoptimistic-lockoptimistic-lockoptimistic-lock(可选 (可选 (可选 (可选 ————默认是 默认是 默认是 默认是 truetruetruetrue)))) 表 明更 新此 组件 是否 需要 获取 乐观 锁 。换 句话 说 ,当 这个 属性 变脏 时 ,是 否增 加版 本号 (Version)。 uniqueuniqueuniqueunique(可选 (可选 (可选 (可选 ————默认 默认 默认 默认 falsefalsefalsefalse)))) 表 明组 件映 射的 所有 字段 上都 有唯 一性 约束 。 元素的简单示例 元素的简单示例 元素的简单示例 元素的简单示例 在 其他 元素 里面 ,就 可以 使用 属性 参照 来引 用了 ,如 property-ref="name" 50 15.15.15.15.其他配置 与对象继承相关的配置 与对象继承相关的配置 与对象继承相关的配置 与对象继承相关的配置 鉴 别器 (discriminator)、 子类 、连 接的 子类 、联 合子 类、 连接 、 Any 元素 与关系相关的配置 与关系相关的配置 与关系相关的配置 与关系相关的配置 多 对一 (many-to-one)、 一对 一 (one-to-one)、key 映射 与与与与version version version version 类似的 类似的 类似的 类似的 timestamptimestamptimestamptimestamp 同 样用 在乐 观锁 的配 置上 ,作 为版 本的 替代 。时 间戳 本质 上是 一种 对乐 观锁 定的 一种 不 是 特别 安全 的实 现, 并不 推荐 使用 。 自然 自然 自然 自然 ID(natural-id)ID(natural-id)ID(natural-id)ID(natural-id) 在元素中列出自然键的属性。Hibernate 会帮你生成必须的唯一键值和非空 约 束, 但这 一映 射不 是为 了把 自然 键作 为主 键而 准备 的。 动态组件( 动态组件( 动态组件( 动态组件( dynamic-componentdynamic-componentdynamic-componentdynamic-component)))) 类 似于 Component 元 素, 由 Hibernate 根 据配 置进 行动 态映 射 Map 类 型, 不推 荐。 第三部分Hibernate Hibernate Hibernate Hibernate 基本数据 一、一、一、一、Hibernate Hibernate Hibernate Hibernate 对象状态对象状态对象状态对象状态 1111.... 瞬时(Transient)(Transient)(Transient)(Transient) 由new 操作符创建,且尚未与Hibernate Session 关联的对象被认定为瞬时的。瞬时对 象不会被持久化到数据库中,也不会被赋予持久化标识(identifier)。如果瞬时对象在程序中 没有被引用,它会被垃圾回收器销毁。使用Hibernate Session 可以将其变为持久状态, 51 Hibernate 会 自动 执行 必要 的 SQL 语 句。 2222.... 持久(Persistent)(Persistent)(Persistent)(Persistent) 持 久的 实例 在数 据库 中有 对应 的记 录 ,并 拥有 一个 持久 化标 识 。持 久的 实例 可能 是刚 被 保存的,或刚被加载的,无论哪一种,按定义,它存在于相关联的Session 作用范围内。 Hibernate 会检测到处于持久状态的对象的任何改动,在当前操作单元执行完毕时将对象数 据与数据库同步。开发者不需要手动执行UPDATE。将对象从持久状态变成瞬时状态同样 也 不需 要手 动执 行 DELETE 语 句。 3.3.3.3. 脱管(Detached)(Detached)(Detached)(Detached) 与 持久 对象 关联 的 Session 被 关闭 后 ,对 象就 变为 脱管 的 。对 脱管 对象 的引 用依 然有 效 , 对 象可 继续 被修 改。 脱管 对象 如果 重新 关联 到某 个新 的 Session 上 ,会 再次 转变 为持 久的 , 在 脱管 期间 的改 动将 被持 久化 到数 据库 。 4.4.4.4. Hibernate Hibernate Hibernate Hibernate 对象状态图 二、二、二、二、Hibernate Hibernate Hibernate Hibernate 对象操作对象操作对象操作对象操作CRUDCRUDCRUDCRUD 主 要是 通过 Session 接 口来 操作 Hibernate 52 1.1.1.1. 新增——save save save save 方法、persist persist persist persist 方法 persist()persist()persist()persist() 使 一个 临时 实例 持久 化 。然而,它 不保 证立 即把 标识 符值 分配 给持 久性 实例 ,这会 发生在flush 的时候。persist() 也保证它在事务边界外调用时不会执行INSERT 语句。 这 对于 长期 运行 的带 有扩 展会 话 /持 久化 上下 文的 会话 是很 有用 的。 save()save()save()save() 保证返回一个标识符。如果需要运行INSERT 来获取标识符(如"identity" 而非 "sequence" 生 成器 ),这个INSERT 将 立即 执行 ,不 管你 是否 在事 务内 部还 是外 部 。这 对于 长 期运 行的 带有 扩展 会话 /持 久化 上下 文的 会话 来说 会出 现问 题。 2.2.2.2. 删除——delete delete delete delete 方法 3.3.3.3. 修改——update,merge,SaveOrupdateupdate,merge,SaveOrupdateupdate,merge,SaveOrupdateupdate,merge,SaveOrupdate 修改的方式 修改的方式 修改的方式 修改的方式 1)直 接在 Session 打 开的 时候 load 对象,然 后修 改这 个持 久对 象 ,在 事务 提交 的时 候 , 会 自动 flush 到 数据 库中 。 2)修 改托 管对 象, 可用 update 或merge 方法 3)自 动状 态检 测: saveOrUpdate 方法 Update Update Update Update 和和和和mergemergemergemerge 1)如果数据库里面存在你要修改的记录,update 每次是直接执行修改语句;而merge 是 先在 缓存 中查 找 ,缓 存中 没有 相应 数据 ,就 到数 据库 去查 询 ,然 后再 合并 数据 ,如 果数 据 是 一样 的 ,那么merge 方 法不 会去 做修 改 ,如 果数 据有 不一 样的 地方 ,merge 才 真正 修改 数 53 据 库。 2)如果数据库中不存在你要修改的记录,update 是报错;而merge 方法是当作一条新 增 的值 ,向 数据 库中 新增 一条 数据 。 3)update 后 ,传 入的 TO 对 象就 是 PO 的 了, 而 merge 还是TO 的。 4)如果你确定当前session 没有包含与之具有相同持久化标识的持久实例,使用 update()。如果想随时合并改动而不考虑session 的状态,使用merge()。换句话说,在一个 新session 中通常第一个调用的是update()方法,以保证重新关联脱管对象的操作首先被执 行。 5)请 注意 :使用update 来 把一 个 TO 变成PO,那 么不 管是 否修 改了 对象 ,都 是要 执 行 update sql 语 句的 。 54 55 Update,SaveOrUpdate Update,SaveOrUpdate Update,SaveOrUpdate Update,SaveOrUpdate 使用场景 使用场景 使用场景 使用场景 1)程 序在 第一 个 session 中 加载 对象 2)该 对象 被传 递到 表现 层 3)对 象发 生了 一些 改动 4)该 对象 被返 回到 业务 逻辑 层 5)程 序调 用第 二个 session 的update()方 法持 久这 些改 动 SaveOrUpdate SaveOrUpdate SaveOrUpdate SaveOrUpdate 做了哪些事情 做了哪些事情 做了哪些事情 做了哪些事情 1)如 果对 象已 经在 本 session 中 持久 化了 ,不 做任 何事 2)如 果另 一个 与本 session 关 联的 对象 拥有 相同 的持 久化 标识 ,抛 出一 个异 常 3)如 果对 象没 有持 久化 标识 属性 ,对 其调 用 save() 4)如 果对 象的 持久 标识 表明 其是 一个 新实 例化 的对 象, 对其 调用 save()。 5)如果对象是附带版本信息的(通过)并且版本属性的值表明其 是 一个 新实 例化 的对 象, save() 它。 56 6)否则update()这 个对 象 Merge Merge Merge Merge 做了那些事情 做了那些事情 做了那些事情 做了那些事情 1)如果session 中 存在 相同 持久 化标 识的 实例 ,用 用户 给出 的对 象的 状态 覆盖 旧有 的持 久实 例 2)如果session 中 没有 相应 的持 久实 例, 则尝 试从 数据 库中 加载 ,或 创建 新的 持久 化实 例 3)最 后返 回该 持久 实例 4)用 户给 出的 这个 对象 没有 被关 联到 session 上 ,它 依旧 是脱 管的 4.4.4.4. 按主键查询 1)load 方 法: load 的 时候 首先 查询 一级 缓存 ,没 有就 创建 并返 回一 个代 理对 象, 等到 使用 的 时候 ,才 查二 级缓 存 ,如 果二 级缓 存中 没有 数据 就查 数据 库 ,如 果数 据库 中没 有 ,就 抛例 外。 2)get 方法:先 查缓 存 ,如 果缓 存中 没有 这条 具体 的数 据 ,就 查数 据库 ,如 果数 据库 没有 值 , 就 返回 null, 总之 get 方 法不 管用 不用 ,都 要拿 到真 实的 数据 。 5.5.5.5. Hibernate Hibernate Hibernate Hibernate 实现按条件查询的方式 1: 最重 要的 按条 件查 询的 方法 是使 用 Query 接 口, 使用 HQL 2: 本地 查询 ( native sql): 就是 使用 标准 的 sql, 也是 通过 Query 接 口来 实现 3: 按条 件查 询( Query By Criteria,QBC): 使用 动态 的, 面向 对象 的方 式来 创建 查询 4: 按样 例查 询( Query By Example, 简写 QBE): 类似 我们 自己 写的 getByCondition 5: 命名 查询 :在 hbm.xml 中 配置 hql 语 句, 在程 序里 面通 过名 称来 创建 Query 接口 6.6.6.6. Query Query Query Query 的list list list list 方法 一 个查 询通 常在 调用 list() 时 被执 行 ,执 行结 果会 完全 装载 进内 存中 的一 个集 合 ,查询 返回的对象处于持久状态。如果你知道的查询只会返回一个对象,可使用list()的快捷方式 uniqueResult()。 57 7.7.7.7. Iterator Iterator Iterator Iterator 和ListListListList 某 些情 况下 ,你 可以 使用 iterate()方 法得 到更 好的 性能 。这 通常 是你 预期 返回 的结 果 在 session,或二级缓存(second-level cache)中已经存在时的情况。如若不然,iterate()会比list() 慢,而且可能简单查询也需要进行多次数据库访问:iterate()会首先使用1条语句得到所有 对 象的 持久 化标 识 (identifiers), 再根 据持 久化 标识 执行 n条 附加 的 select 语 句实 例化 实际 的 对 象。 8.8.8.8. 外置命名查询 可 以在 映射 文件 中定 义命 名查 询 (named queries)。 参 数绑 定及 执行 以编 程方 式完 成: List list= s.getNamedQuery("cn.javass.h3.hello.UserModel.javass").list(); 注 意要 用全 限定 名加 名称 的方 式进 行访 问 Flush Flush Flush Flush 方法 方法 方法 方法 每间隔一段时间,Session 会执行一些必需的SQL 语句来把内存中对象的状态同步到 JDBC 连 接中 。这 个过 程被 称为 刷出 (flush), 默认 会在 下面 的时 间点 执行 : 1: 在某 些查 询执 行之 前 2: 在调 用 org.hibernate.Transaction.commit()的 时候 3: 在调 用 Session.flush()的 时候 涉 及的 SQL 语 句会 按照 下面 的顺 序发 出执 行: 1. 所 有对 实体 进行 插入 的语 句, 其顺 序按 照对 象执 行 save() 的 时间 顺序 2. 所 有对 实体 进行 更新 的语 句 3. 所 有进 行集 合删 除的 语句 58 4. 所 有对 集合 元素 进行 删除 ,更 新或 者插 入的 语句 5. 所 有进 行集 合插 入的 语句 6. 所 有对 实体 进行 删除 的语 句, 其顺 序按 照对 象执 行 delete() 的 时间 顺序 除非你明确地发出了flush()指令,关于Session 何时会执行这些JDBC 调用是完全无法 保证的,只能保证它们执行的前后顺序。当然,Hibernate 保证,Query.list(..)绝对不会返回 已 经失 效的 数据 ,也 不会 返回 错误 数据 。 9.9.9.9. Lock Lock Lock Lock 方法 也 允许 程序 重新 关联 某个 对象 到一 个新 session 上。不过,该 脱管 对象 必须 是没 有修 改 过 的。 示例 如: session.lock(user, LockMode.READ); 注 意: lock 主 要还 是用 在事 务处 理上 ,关 联对 象只 是一 个附 带的 功能 10.10.10.10.获取元数据 Hibernate 提 供了 ClassMetadata 接 口和 Type 来 访问 元数 据。 示例 如下 : ClassMetadatacatMeta= sf.getClassMetadata(UserModel.class); String[] propertyNames= catMeta.getPropertyNames(); Type[] propertyTypes= catMeta.getPropertyTypes(); for (inti = 0; i < propertyNames.length; i++) { System.out.println("name=="+propertyNames[i] + ", type==“ +propertyTypes[i]); } 三、三、三、三、HQL(HibernateHQL(HibernateHQL(HibernateHQL(Hibernate QueryQueryQueryQuery Language)Language)Language)Language) 1.1.1.1. HQL HQL HQL HQL 介绍 Hibernate 配备了一种非常强大的查询语言,这种语言看上去很像SQL。但是不要被语 法结构上的相似所迷惑,HQL 是非常有意识的被设计为完全面向对象的查询,它可以理解 59 如 继承 、多 态和 关联 之类 的概 念。 看 个示 例, 看看 sql 和HQL 的 相同 与不 同: Sql:select * from tbl_user where uuid=‘123’ HQL:select Object(o) from UserModelo where o.uuid=‘123 2.2.2.2. HQL HQL HQL HQL 特点 1:HQL 对Java 类 和属 性是 大小 写敏 感的 ,对 其他 不是 大小 写敏 感的 。 2:基 本上 sql 和HQL 是 可以 转换 的 ,因 为按 照 Hibernate 的 实现 原理 ,最 终运 行的 还是 sql, 只 不过 是自 动生 成的 而已 。 3:HQL 支 持内 连接 和外 连接 4:HQL 支 持使 用聚 集函 数, 如: count、avg、sum、min、max 等 5:HQL 支持order by 和group by 6:HQL 支 持条 件表 达式 ,如 : in、like、between 等 3.3.3.3. Select Select Select Select 子句 1: 直接 返回 对象 集合 ,形 如: select o from UserModelo 2: 返回 某个 特定 类型 的集 合, 形如 : select o.namefrom UserModelo 3: 返回 Object[], 形如 : select o.uuid,o.namefrom UserModelo 4: 返回 List, 形如 : select new List(o.uuid,o.name) from UserModelo 5:返回任意的对象,形如:select new cn.javass.h3.hello.A(o.uuid,o.name) from UserModelo , 这 要求 A对 象有 一个 构造 方法 是传 入这 两个 参数 6:返回Map 类型,形如:select new Map(o.uuidas Id,o.nameas N) from UserModelo ,返回 的 结果 ,以 as后 面的 别名 做 map 的key, 对应 的数 据做 值 4.4.4.4. From From From From 子句 1: 直接 from 对 象, 形如 : from UserModel 2: 可以 分配 别名 ,形 如: from UserModelas um ,as关 键字 可以 省略 3: 如果 from 后 面有 多个 对象 ,形 如: from UserModel,DepModel , 相当 于多 表联 合查 询 , 60 返 回他 们的 笛卡 尔积 5.5.5.5. 聚集函数 1: 受支 持的 有 avg,sum,min,max,count 2:关键字distinct 与all 也可以使用,它们具有与SQL 相同的语义,比如:select count(distincto.name) from UserModelo 6.6.6.6. Where Where Where Where 子句 1: 如果 前面 没有 指派 别名 ,那 就直 接使 用属 性名 2: 如果 指派 了别 名, 必须 使用 别名 .属 性的 方式 3:在where 子 句中 允许 使用 的表 达式 包括 大多 数在 SQL 中 使用 的表 达式 ,包 括: (1)数 学运 算符 +,-,*,/ (2)二 进制 比较 运算 符 =, >=, <=, <>, !=, like (3)逻 辑运 算符 and,or,not (4)括号(), 表示 分组 (5)in, not in, between, is null, is not null, is empty, is not empty, member of and not member of (6)字 符串 连接 符 ...||... or concat(...,...) (7)current_date(), current_time(), and current_timestamp() (8)second(...)、minute(...)、hour(...)、day(...)、month(...) 和year(...) (9)EJB-QL 3.0 定 义的 任何 功能 或操 作符 : substring(), trim(), lower(), upper(), length(),locate(), abs(), sqrt(), bit_length(), mod() (10)coalesce() 和nullif() (11)str() 把 数字 或者 时间 值转 换为 可读 的字 符串 (12)cast(... as ...), 其第 二个 参数 是某 Hibernate 类 型的 名字 ,以 及 extract(... from ...), 只要 ANSI cast() 和extract() 被 底层 数据 库支 持 (13)HQL index() 函 数, 作用 于 join 的 有序 集合 的别 名。 (14)HQL 函数,把集合作为参数:size(), minelement(), maxelement(), minindex(), maxindex(), 还 有特 别的 elements() 和indices 函 数, 可以 与数 量词 加以 限定 : some, all, exists, any, in。 61 (15)任 何数 据库 支持 的 SQL 标 量函 数, 比如 sign(), trunc(), rtrim(), sin() (16)JDBC 风 格的 参数 传入 ? (17)命 名参 数 :name,:start_date,:x1 (18)SQL 直 接常 量 'foo', 69, 6.66E+2, '1970-01-01 10:00:01.0' (19)Java public static final 类 型的 常量 eg.Color.TABBY 7.7.7.7. GroupGroupGroupGroup bybybyby子句 1: 对于 返回 聚集 值的 查询 ,可 以按 照任 何属 性进 行分 组 2: 可以 使用 having 子句 3:sql 中 的聚 集函 数, 可以 出现 在 having 子 句中 4:group by 子 句与 order by 子 句中 都不 能包 含算 术表 达式 5: 不能 group by 某 个实 体对 象, 必须 明确 的列 出所 有的 聚集 属性 8.8.8.8. OrderOrderOrderOrder bybybyby子句 查询返回的列表(list)可以按照一个返回的类或组件(components)中的任何属性进 行 排序 ,可 选的 asc 或desc 关 键字 指明 了按 照升 序或 降序 进行 排序 。 9.9.9.9. 子查询 对 于支 持子 查询 的数 据库 , Hibernate 支 持在 查询 中使 用子 查询 。一 个子 查询 必须 被圆 括 号包 围起 来。 10.10.10.10.连接查询(joinjoinjoinjoin) 1:Hibernate 可以在相关联的实体间使用join,类似于sql,支持inner join、left outer join、 right outer join、full join( 全连 接, 并不 常用 )。 2:inner join 可以简写成join,left outer join 和right outer join 在简写的时候可以把outer 去 掉。 62 11.11.11.11.withwithwithwith 通过HQL 的with 关 键字 ,你 可以 提供 额外 的 join 条 件。 如:from Cat as cat left join cat.kittensas kitten with kitten.bodyWeight> 10.0 12.12.12.12.fetchfetchfetchfetch 可 以要 求立 即返 回关 联的 集合 对象 ,如 : from Cat as cat inner join fetch cat.mate left join fetch cat.kittens 13.13.13.13.对于没有关联的实体,如何使用join join join join 呢? 对 于没 有关 联的 实体 ,想 要使 用 join, 可以 采用 本地 查询 的方 式, 使用 sql 来 实现 , 比 如: s.createSQLQuery("selectum.*,dm.* from tbl_user2 um left join tbl_depdm on um.age=dm.uuid") .addEntity("um",UserModel.class) .addEntity("dm",DepModel.class); 14.14.14.14.CRUD CRUD CRUD CRUD 示例 1: 新增 2:load、get 3: 修改 4: 按条 件查 询 (1) 传入 条件 值的 方法 :?或:名 称, 索引 从 0开始 (2) 给参 数赋 值 (3) 返回 对象 (4) 返回 多个 属性 ,形 成 Object[] (5)getByCondition 的Hibernate 版 实现 5: 删除 6: 分页 63 Query q = sess.createQuery("fromDomesticCatcat"); q.setFirstResult(20); q.setMaxResults(10); List cats = q.list(); 四、本地查询四、本地查询四、本地查询四、本地查询 也可以使用你的数据库的Native SQL 语言来查询数据。这对你在要使用数据库的某些 特 性的 时候 (比 如说 在查 询提 示或 者 Oracle 中的CONNECT 关 键字 ),这 是非 常有 用的 。这就 能 够扫 清你 把原 来直 接使 用 SQL/JDBC 的 程序 迁移 到基 于 Hibernate 应 用的 道路 上的 障碍 。 1.1.1.1. 使用SQLQuerySQLQuerySQLQuerySQLQuery 对原生SQL 查询执行的控制是通过SQLQuery 接口进行的,通过执行 Session.createSQLQuery()获 取这 个接 口。 下面 来描 述如 何使 用这 个 API 进 行查 询。 2.2.2.2. 标量查询(ScalarScalarScalarScalar queriesqueriesqueriesqueries) Session.createSQLQuery(“selectuuid,namefrom tbl_user”).list();它将返回一个Object[]组成 的List,Hibernate 会 使用 ResultSetMetadata 来 判定 返回 的标 量值 的实 际顺 序和 类型 。你 也可 以 使用 scalar 来 明确 指明 类型 ,如 : s.createSQLQuery("select* from tbl_user").addScalar("id", LongType.INSTANCE) id 字 段就 明确 是 long 型 ,当 然你 也可 以指 定很 多个 字段 的类 型。 3.3.3.3. 实体查询(EntityEntityEntityEntity queriesqueriesqueriesqueries) 上 面的 查询 都是 返回 标量 值的 ,也 就是 从 resultset 中 返回 的 “裸”数据。下 面展 示如 何通 过addEntity()让 原生 查询 返回 实体 对象 。 (1) s.createSQLQuery("select * from tbl_user").addEntity(UserModel.class); (2) s.createSQLQuery(“selectuuid,userIdfrom tbl_user2”).addEntity (UserModel.class); //一 定要 把表 的所 有字 段罗 列出 来 (3) s.createSQLQuery("select {um}.uuidas {um.uuid},{um}.nameas {um.name} from 64 tbl_user{um}").addEntity("um",UserModel.class);功能跟第二个差不多,也要把表的所有字段 都 罗列 出来 (4)简 单点 的写 法: s.createSQLQuery("select* from tbl_user2 um") .addEntity("um",UserModel.class); (5)添 加条 件的 示例 : session.createSQLQuery("select* from tbl_userwhere uuid=? and name like ?") .addEntity(UserModel.class). setString(0, "3") .setString(1,"%na%"); 4.4.4.4. 命名Sql Sql Sql Sql 查询 可以在映射文档中定义查询的名字,然后就可以象调用一个命名的HQL 查询一样直接 调 用命 名 SQL 查询.在 这种 情况 下, 我们 不需 要调 用 addEntity() 方 法。 在hbm.xml 中 配置 ,示 例如 下: select um.nameas {um.name}, um.ageas {um.age}, um.uuidas {um.uuid} from tbl_userum where um.namelike :name 注 意: 因为 要返 回一 个对 象, 所以 要把 表的 所有 字段 都罗 列上 ,否 则会 报错 “列 名无 效 ”,其 实 是在 反射 向对 象赋 值的 时候 ,从sql 的 返回 中得 不到 这个 数据 。程 序里 面调 用示 例 :Query q = s.getNamedQuery(um.getClass().getName()+".users").setString("name", "%n%"); 5.5.5.5. 命名Sql Sql Sql Sql 查询--------使用return-propertyreturn-propertyreturn-propertyreturn-property 使用 你可以明确的告诉Hibernate 使用哪些字段别名,这取代了使用 65 {}-语 法来 让 Hibernate 注 入它 自己 的别 名。 在 hbm.xml 中 配置 ,示 例如 下: select um.nameas umName, um.ageas age, um.uuidas uuid from tbl_userum where um.namelike :name 具 有一 个直 观的 、可 扩展 的条 件查 询 API 是Hibernate 的 特色 。 6.6.6.6. 创建一个CriteriaCriteriaCriteriaCriteria 实例 org.hibernate.Criteria 接 口表 示特 定持 久类 的一 个查 询。 Session 是Criteria 实 例的 工厂 。 Criteria crit= sess.createCriteria(Cat.class); crit.setMaxResults(50); List cats = crit.list(); 7.7.7.7. 限制结果集内容 一个单独的查询条件是org.hibernate.criterion.Criterion 接口的一个实例 org.hibernate.criterion.Restrictions 类 定义 了获 得某 些内 置 Criterion 类 型的 工厂 方法 。 List list= s.createCriteria(UserModel.class) .add(Restrictions.eq("uuid", "3")) .add(Restrictions.like("name", "%n%")) .list(); 约 束可 以按 照逻 辑分 组, 示例 如下 : 66 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .add( Restrictions.or( Restrictions.eq( "age", new Integer(0) ), Restrictions.isNull("age") ) ).list(); 8.8.8.8. 对结果集排序 可 以使 用 org.hibernate.criterion.Order 来 为查 询结 果排 序。 示例 如下 : List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "F%") .addOrder( Order.asc("name") ) .addOrder( Order.desc("age") ) .setMaxResults(50) .list(); 五、样例查询五、样例查询五、样例查询五、样例查询————————QBEQBEQBEQBE 按 样例 查询 Query By Example,功 能类 似我 们自 己写 的 getByCondition,但 是它 的功 能 不 够完 善和 灵活 ,所 以用 途不 是很 大。 1.1.1.1. 创建一个Eaxmple Eaxmple Eaxmple Eaxmple 实例 Example exa= Example.create(p1);//传 入的 对象 就是 封装 查询 条件 的对 象 exa.enableLike(); exa.excludeNone(); exa.excludeZeroes(); 比 较常 用的 方法 如下 所示 : 1.ignoreCase(): 忽略 模板 类中 所有 String 属 性的 大小 写。 67 2.enableLike(MatchMode mode):表示对模板类中所有的String 属性进行like 模糊匹配, enableLike()中的参数指明以何种方式进行匹配,比如MatchMode.ANYWHERE 的方式是 like“%变量%”。 3.excludeZeroes(): 不把 为 0值 的字 段加 入到 where 条 件句 中。 4.excludeProperty(String name): 不把 属性 为 name 的 字段 加入 到 where 条 件句 中。 2.2.2.2. 执行EaxmpleEaxmpleEaxmpleEaxmple List list = s.createCriteria(Parent.class).add(exa).list(); 六、批处理六、批处理六、批处理六、批处理 1111.... 提出问题 假 如有 如下 程序 ,需 要向 数据 库里 面加 如 100000 条 数据 : Session session= sessionFactory.openSession(); Transaction tx= session.beginTransaction(); for ( inti=0; i<100000; i++ ){ Customer customer= new Customer(.....); session.save(customer); } tx.commit(); session.close(); 这个程序很显然不能正常运行,会抛出内存溢出的例外。按照前面讲过的原理,Hibernate 的save 方 法是 先把 数据 放到 内存 里面 ,数 据太 多, 导致 内存 溢出 。那 么该 如何 解决 呢? 2.2.2.2. 解决方案 1:首 先将 hibernate.jdbc.batch_size 的 批量 抓取 数量 参数 设置 到一 个合 适值 (比如,10 - 50 之 间), 同时 最好 关闭 二级 缓存 ,如 果有 的话 。 2: 批量 插入 ,一 个可 行的 方案 如下 : 68 for ( int i=0; i<100000; i++ ){ Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ){ //将 本批 数据 插入 数据 库, 并释 放内 存 session.flush(); session.clear(); } } 批 量更 新的 做法 跟这 个类 似 七、无状态七、无状态七、无状态七、无状态Session Session Session Session 接口接口接口接口 1: 默认 的 Hibernate 是 有缓 存的 ,称 之为 一级 缓存 。 2: 也可 以使 用 StatelessSession, 来表 示不 实现 一级 缓存 ,也 不和 二级 缓存 和查 询缓 存交 互 3:StatelessSession 是 低层 的抽 象, 和底 层 JDBC 相 当接 近 StatelessSessionsession = sessionFactory.openStatelessSession(); Transaction tx= session.beginTransaction(); ScrollableResultscustomers = 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(); 69 第四部分关系映射 一、关系的数据库表示一、关系的数据库表示一、关系的数据库表示一、关系的数据库表示 1.1.1.1. 数据库表间关系的分类 一 对一 ,一 对多 ,多 对多 2.2.2.2. 一对一数据表 使 用部 门表 和部 门主 管表 3.3.3.3. 一对多数据表 部 门表 和部 门下 的人 员表 70 4.4.4.4. 多对多数据表 部 门表 和 人员 表 二、关系的对象表示二、关系的对象表示二、关系的对象表示二、关系的对象表示 1.1.1.1. 关系分类 根 据对 象相 互导 航的 关系 ,又 分为 【单 向】 和【 双向 】关 联关 系。 2.2.2.2. 对象一对一(双向) Public class A{ Private B b = null; } Public class B{ Private A a = null; } 3.3.3.3. 对象一对多(双向) Public class A{// 多方 Private B b = null; } Public class B{// 一方 71 Private Collection colA = null; } 4.4.4.4. 对象多对多(双向) public class A{ private Collection colB = null; } public class B{ private Collection colA = null; } 5.5.5.5. 双向一对多是最常用的映射关系 三、三、三、三、Key Key Key Key 的配置的配置的配置的配置 1.1.1.1. 元素 元 素在 父映 射元 素定 义了 对新 表的 连接 ,并 且在 被连 接表 中定 义了 一个 外键 引 用原 表的 主键 的情 况下 经常 使用 。 columncolumncolumncolumn(可选) (可选) (可选) (可选) 外 键字 段的 名称 。也 可以 通过 嵌套 的 指 定。 72 on-deleteon-deleteon-deleteon-delete(可选,默认是 (可选,默认是 (可选,默认是 (可选,默认是 noactionnoactionnoactionnoaction)))) 表 明外 键关 联是 否打 开数 据库 级别 的级 联删 除。 property-refproperty-refproperty-refproperty-ref(可选) (可选) (可选) (可选) 表 明外 键引 用的 字段 不是 原表 的主 键( 提供 给遗 留数 据 )。 not-nullnot-nullnot-nullnot-null(可选) (可选) (可选) (可选) 表 明外 键的 字段 不可 为空 ,意 味着 无论 何时 外键 都是 主键 的一 部分 。 updateupdateupdateupdate(可选) (可选) (可选) (可选) 表 明外 键决 不应 该被 更新 ,这 意味 着无 论何 时外 键都 是主 键的 一部 分。 uniqueuniqueuniqueunique(可选) (可选) (可选) (可选) 表明外键应有唯一性约束,这意味着无论何时外键都是主键的一部分。对那些看重删除 性能的系统,推荐所有的键都应该定义为on-delete="cascade",这样Hibernate 将使用 数 据库 级的 ONCASCADEDELETE 约 束, 而不 是多 个 DELETE 语句 四、集合映射的配置四、集合映射的配置四、集合映射的配置四、集合映射的配置 用 于映 射集 合类 的 Hibernate 映 射元 素取 决于 接口 的类 型 。比如,元 素用 来映 射 Set 类 型的 属性 。 1.1.1.1. 类型属性 73 2.2.2.2. 其他映射 除了,还有, , , 映射元素。具有代表 性 ,如 下: ((((1111))))<<<>>> name="propertyName" (1) table="table_name" (2) schema="schema_name" (3) lazy="true|extra|false" (4) inverse="true|false" (5) cascade=“all|none|save-update|delete|all-delete-orphan|delete-orphan”(6) sort="unsorted|natural|comparatorClass" (7) order-by="column_name asc|desc" (8) where="arbitrary sql where condition" (9) fetch="join|select|subselect" (10) batch-size="N" (11) access="field|property|ClassName" (12) optimistic-lock="true|false" (13) mutable="true|false" (14) > (1) name 集 合属 性的 名称 (2) table( 可选 —默 认为 属性 的名 称) 这个 集合 表的 名称 (不 能在 一对 多的 关联 关系 中使 用) (3) schema (可选) 表的schema 的 名称 , 他 将覆 盖在 根元 素中 定义 的 schema 74 (4) lazy (可选--默 认为 true) 可 以用 来关 闭延 迟加 载 (false), (5) inverse (可选——默 认为 false) 标 记这 个集 合作 为双 向关 联关 系中 的方 向一 端。 (6) cascade (可选——默 认为 none) 让 操作 级联 到子 实体 (7) sort(可选)指 定集 合的 排序 顺序 (8) order-by (可选, 仅用于jdk1.4) 指 定表 的字 段 (一 个或 几个 )再加上asc 或者desc(可选), 定 义Map,Set 和Bag 的 迭代 顺序 (9) where (可选) 指定任意的SQL where 条件, 该条件将在重新载入或者删除这个集合时使 用(当 集合 中的 数据 仅仅 是所 有可 用数 据的 一个 子集 时这 个条 件非 常有 用 ) (10) fetch (可选, 默认为select) 用 于在 外连 接抓 取 、通 过后 续select 抓 取和 通过 后 续subselect 抓 取之 间选 择。 (11) batch-size (可选, 默 认为 1) 指 定通 过延 迟加 载取 得集 合实 例的 批处 理块 大小 (12) access(可选-默 认为 属性 property):Hibernate 取 得集 合属 性值 时使 用的 策略 (13) 乐观锁(可选- 默认为true): 对集合的状态的改变会是否导致其所属的实体的版本增 长。(对 一对 多关 联来 说, 关闭 这个 属性 常常 是有 理的 ) (14) mutable(可变)(可选- 默 认为 true): 若 值为 false,表 明集 合中 的元 素不 会改 变 (在 某些 情 况 下可 以进 行一 些小 的性 能优 化 )。 3.3.3.3. 集合外键 集 合实 例在 数据 库中 依靠 持有 集合 的实 体的 外键 加以 辨别 。此 外键 作为 集合 关键 字段 加 以引用。集合关键字段通过 元素映射。在外键字段上可能具有非空约束。对于大多 数 集合 来说 ,这 是隐 含的 。对 单向 一对 多关 联来 说 ,外 键字 段默 认是 可以 为空 的 ,因 此你 可 能 需要 指明 not-null=“true”。 示例 如下 : 外 键约 束可 以使 用 ONDELETECASCADE,示 例如 下 : 75 五、关系映射五、关系映射五、关系映射五、关系映射 1.1.1.1. One-to-oneOne-to-oneOne-to-oneOne-to-one 通过one-to-one 元素,可 以定 义持 久化 类的 一对 一关 联。 namenamenamename 属 性名 。 ClassClassClassClass((((可选 可选 可选 可选 )))) ( 默认 是通 过反 射得 到的 属性 类型 ) ,被 关联 的类 的名 字。 cascadecascadecascadecascade(级联 (级联 (级联 (级联 ))))(可选) (可选) (可选) (可选) 表 明操 作是 否从 父对 象级 联到 被关 联的 对象 。 constrainedconstrainedconstrainedconstrained(约束 (约束 (约束 (约束 ))))(可选) (可选) (可选) (可选) 表 明该 类对 应的 表对 应的 数据 库表 ,和 被关 联的 对象 所对 应的 数据 库表 之间 ,通 过一 个 76 外 键引 用对 主键 进行 约束 。这 个选 项影 响 save() 和delete()在 级联 执行 时的 先后 顺序 以 及 决定 该关 联能 否被 委托 (也 在 schema export tool 中 被使 用 )。 fetchfetchfetchfetch(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 selectselectselectselect)))) 在 外连 接抓 取( outer-join fetching) 和序 列选 择抓 取( sequential select fetching) 两者 中 选 择其 一。 property-refproperty-refproperty-refproperty-ref(可选) (可选) (可选) (可选) 指 定关 联类 的属 性名 ,这 个属 性将 会和 本类 的主 键相 对应 。如 果没 有指 定 ,会 使用 对方 关 联类 的主 键。 accessaccessaccessaccess(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 propertypropertypropertyproperty)))) Hibernate 用 来访 问属 性值 的策 略。 formulaformulaformulaformula (可选) (可选) (可选) (可选) 绝 大多 数一 对一 的关 联都 指向 其实 体的 主键 。在 一些 少见 的情 况中 ,你 可能 会指 向其 他 的 一个 或多 个字 段 ,或 者是 一个 表达 式 ,这 些情 况下 ,你 可以 用一 个 SQL 公 式来 表示 。 ( 可以 在 org.hibernate.test.onetooneformula 找 到例 子) lazylazylazylazy(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 proxyproxyproxyproxy)))) 默 认情 况下 ,单 点关 联是 经过 代理 的 。lazy="no-proxy"指 定此 属性 应该 在实 例变 量第 一 次 被访 问时 应该 延迟 抓取 (fetchelazily)(需 要运 行时 字节 码的 增强 )。lazy="false"指定 此关联总是被预先抓取。注意,如果constrained="false", 不可能使用代理,Hibernate 会 采取 预先 抓取 。 entity-nameentity-nameentity-nameentity-name(可选) (可选) (可选) (可选) 被 关联 的类 的实 体名 。 77 2.2.2.2. Many-to-oneMany-to-oneMany-to-oneMany-to-one 通过many-to-one 元素,可以定义一种常见的与另一个持久化类的多对一关联,这个表 的 一个 外键 引用 目标 表的 主键 字段 。 NameNameNameName 属 性名 。 columncolumncolumncolumn(可选) (可选) (可选) (可选) 外 键字 段的 名称 。也 可以 通过 嵌套 的 指 定。 78 classclassclassclass(可选) (可选) (可选) (可选) 默 认是 通过 反射 得到 的属 性类 型 ,被 关联 的类 的名 字。 cascadecascadecascadecascade(级联 (级联 (级联 (级联 ))))(可选) (可选) (可选) (可选) 表 明操 作是 否从 父对 象级 联到 被关 联的 对象 。 fetchfetchfetchfetch(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 selectselectselectselect)))) 在 外连 接抓 取和 序列 选择 抓取 两者 中选 择其 一。 update,update,update,update, insertinsertinsertinsert(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 truetruetruetrue)))) 指定对应的字段是否包含在用于UPDATE 和/或INSERT 的SQL 语句中。如果二者都 是false,则 这是 一个 纯粹 的 “外 源性 (derived)”关联,它 的值 是通 过映 射到 同一 个 (或 多个 ) 字 段的 某些 其他 属性 得到 或者 通过 trigger( 触发 器 )、 或其 他程 序生 成。 property-refproperty-refproperty-refproperty-ref(可选) (可选) (可选) (可选) 被 关联 到此 外键 的类 中的 对应 属性 的名 字。 如果 不指 定, 使用 被关 联类 的主 键 accessaccessaccessaccess(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 propertypropertypropertyproperty)))) Hibernate 用 来访 问属 性值 的策 略。 uniqueuniqueuniqueunique(可选) (可选) (可选) (可选) 使用DDL 为 外键 字段 生成 一个 唯一 约束 。此外,这 也可 以用 作 propertyref 的 目标 属性 。 这 使关 联同 时具 有一 对一 的效 果。 not-nullnot-nullnot-nullnot-null(可选) (可选) (可选) (可选) 使用DDL 为 外键 字段 生成 一个 非空 约束 。 79 optimistic-lockoptimistic-lockoptimistic-lockoptimistic-lock(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 truetruetruetrue)))) 指 定这 个属 性在 做更 新时 是否 需要 获得 乐观 锁定 。换 句话 说 ,它 决定 这个 属性 发生 脏数 据 时版 本( version) 的值 是否 增长 。 lazylazylazylazy(可选 (可选 (可选 (可选 ————默认为 默认为 默认为 默认为 proxyproxyproxyproxy)))) 默认情况下,单点关联是经过代理的。lazy="no-proxy" 指定此属性应该在实例变量第 一次被访问时应该延迟抓取(fetche lazily)(需要运行时字节码的增强)。lazy="false" 指定 此 关联 总是 被预 先抓 取。 not-foundnot-foundnot-foundnot-found(可选 (可选 (可选 (可选 ---- 默认为 默认为 默认为 默认为 exceptionexceptionexceptionexception)))) 指 定如 何处 理引 用缺 失行 的外 键: ignore 会 把缺 失的 行作 为一 个空 关联 处理 。 entity-nameentity-nameentity-nameentity-name(可选) (可选) (可选) (可选) 被 关联 的类 的实 体名 。 formulaformulaformulaformula(可选) (可选) (可选) (可选) SQL 表 达式 ,用 于定 义 computed( 计算 出的 )外 键值 。 3.3.3.3. One-to-manyOne-to-manyOne-to-manyOne-to-many 通过one-to-many 元素,可 以定 义一 种常 见的 与另 一个 持久 化类 的一 对多 关联 。 80 classclassclassclass(必需) (必需) (必需) (必需) 被 关联 类的 名称 。 not-foundnot-foundnot-foundnot-found(可选 (可选 (可选 (可选 ---- 默认为 默认为 默认为 默认为 exceptionexceptionexceptionexception)))) 指 明若 缓存 的标 示值 关联 的行 缺失 ,该 如何 处理 :ignore 会 把缺 失的 行作 为一 个空 关联 处 理。 entity-nameentity-nameentity-nameentity-name(可选) (可选) (可选) (可选) 被 关联 的类 的实 体名 ,作 为 class 的 替代 。 注 意: 元 素不 需要 定义 任何 字段 。也 不需 要指 定表 名。 4.4.4.4. 双向一对多示例 ((((1111)对象关系 )对象关系 )对象关系 )对象关系 一 对多 【双 向】 关联 : 【 一方 】 — —— —— — Group 【 多方 】 — —— —— — User 即 一个 User 只 能在 一个 Group,而 一个 Group 包 含多 个 User,也 就是 ,从User 能 导航 到Group,从Group 也 能导 航到 User public class User { private Long id; private Group group;} public class Group { private Long id; private Set users;} ((((2222)映射文件 )映射文件 )映射文件 )映射文件 81 ((((3333)测试文件 )测试文件 )测试文件 )测试文件 publicpublicpublicpublicclassclassclassclassBDOneToManyTest{ privateprivateprivateprivateSessionsession=nullnullnullnull; @Before publicpublicpublicpublicvoidvoidvoidvoidbefore(){ session=HibernateUtil.getSessionFactory( "edu/zhku/cian/bd/one2many/hibernate.cfg.xml").openSession(); session.beginTransaction(); } @After publicpublicpublicpublicvoidvoidvoidvoidafter(){ ifififif(session!=nullnullnullnull&&session.isOpen()){ session.flush(); session.getTransaction().commit(); session.clear(); session.close(); 82 session=nullnullnullnull; } } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveGroupInverseTrue(){ Useruser=newnewnewnewUser(); Groupgroup=newnewnewnewGroup(); user.setGroup(group); group.getUsers().add(user); session.save(group); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveUser(){ Useruser=newnewnewnewUser(); Groupgroup=newnewnewnewGroup(); user.setGroup(group); group.getUsers(); session.save(group); session.save(user); } /** *进行本测试之前要把 Group中的 lazy设置成 true即: lazy=true */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestGetLazyTrue(){ /** *执行完下面这个打印之前会发出两个查询语句 :1.selectuser2.selectgroup */ Useruser=(User)session.get(User.classclassclassclass,1L); System.out.println(user.getId()); /** *执行下面这条语句并不会向数据库发送查询语句,原因是,在执行了: Useruser=(User) *session.get(User.class,1L);之后, User中的 group被查询出来了,但是, Group中设置了 *lazy=true所以查询 Group时Hibernate是不会先查询 users的,所以此处并不会 向数据库发送查询语句 */ System.out.println(user.getGroup().getId()); /** *执行完下面这个之后会发出第三条查询语句 (如果 session没有被关闭的话 ,这个会被执行成功 ):select 83 userwhere *group_id=? * *为什么会发送查询了,因为在中访问了 users,由于 lazy=true,此时就会 向数据库查询 * *如果在调用 user.getGroup()之前调用了 session.close(); *或者说, session由于某些原因被关闭了,那么就会抛出异常 因为 Hibernate与数据库的当前 session链 接关闭了,失去 *了与数据库的联系,所以必然导致数据查询的失败。 */ System.out.println(user.getGroup()); /** *以下这句就不会继续发送查询语句了,因为在上面的 user.getGroup()中已经把 users查询出来了 */ System.out.println(user.getGroup().getUsers()); } /** *进行本测试之前要把 Group中的 lazy设置成 false即: lazy=false */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestGetLazyFalse(){ /** *执行完下面这个打印之前会发出三个个查询语句 :1.selectuser2.selectgroup3.selectuser *wheregroup_id=? */ Useruser=(User)session.get(User.classclassclassclass,1L); System.out.println(user.getId()); /** *即便这里关闭了也没有关系,因为在执行 Useruser=(User)session.get(User.class,1L); *的时候,由于 lazy=false所以 Hibernate已经把 User所关联的 Group查询出来, *同时 Hibernate又继续把 Group关联的 Users查询出来了 */ session.close(); /** *执行下面这条语句并不会进行查询,即不会向数据库发送查询语句 */ System.out.println(user.getGroup().getId()); /** 84 *执行完下面这个之后不会向数据库发送查询 * */ System.out.println(user.getGroup()); /** *不会进行到数据查询 ,因为所有都在前面查询出来了 */ System.out.println(user.getGroup().getUsers()); /** *看看是否是相同的对象 ,这里会有一个与 user对象是同一个 可以看到 Hibernate进行了优化 */ forforforfor(Useru:user.getGroup().getUsers()){ System.out.println(u==user); } } /** *进行本测试之前要把 Group中的 lazy设置成 true即: lazy=true */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestLoadLazyTrue(){ /** *执行完以下这句并不会向数据库中发送查询语句 load相当于进行了下面的操作: 1. *向session二级缓存中进行查询,如果没有就执行下面的如果有就直接返回缓存中的对象Useruser= newUser(); *user.setId(1L);returnuser; * */ Useruser=(User)session.load(User.classclassclassclass,1L); /** *执行下面这个语句也是不会向数据中发送查询语句的 ,因为没有 访问除了 ID之外的其他属性 ,一旦访问了出 主键 ID之外的属性 *Hibernate就会尝试到数据库中进行查询,在下面的例子中会看到 * */ System.out.println(user.getId()); /** *下面的语句会使得Hibernate向缓存中查询,如果没有的话就会到数据库中查询,在这里,由于定义了 lazy=true *默认情况下只是查询出 Group,Group不会继续查询 Users 85 * *这个语句将产生如下两条查询语句 (如果缓存中没有的话 ):selectuserwhereid=1Lselectgroup *whereid=user.getGroup().getId() * */ //session.close();session在这边被关闭掉可是会抛出异常哦,原因和 testGetLazyTrue一样 System.out.println(user.getGroup().getId()); /** *执行下面这个 Hibernate由于看到你需要获取 Users,所以会先在 session *的默认二级缓存中查找,如果没有就会到数据库中进行查找,本例会到数据库中 查找,发出下面的额语句 : selectuserwhere *group_id=user.getGroup().geId() */ System.out.println(user.getGroup().getUsers()); } /** *进行本测试之前要把 Group中的 lazy设置成 false即: lazy=false */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestLoadLazyFalse(){ /** *load的查询首先都是在 Session缓存中进行查询,默认是二级缓存 在下面的 "查询 "中含义与此一致 */ /** *执行完以下这句并不会向数据库中发送查询语句 load相当于进行了下面的操作: 1. *向session二级缓存中进行查询,如果没有就执行下面的如果有就直接返回缓存中的对象Useruser= newUser(); *user.setId(1L);returnuser; * */ Useruser=(User)session.load(User.classclassclassclass,1L); /** *执行下面这个语句也是不会向数据中发送查询语句的 ,因为没有 访问除了 ID之外的其他属性 ,一旦访问了出 主键 ID之外的属性 *Hibernate就会尝试到数据库中进行查询,在下面的例子中会看到 * */ System.out.println(user.getId()); /** 86 *和lazy=true方式不同,在执行后面这句之后, Hibernate会查询 *user中的 Group,由于 Group的lazy=false,所有 hibernate又 *会 主动 把对 应的 Group的 所有 Users查 询出 来, 本例 缓存 中没 有相 关数 据 因 而会 发出 下面 deep三 条查 询语 句: * *selectuserwhereid=?selectgroupwhereid=?selectuserwhere *group_id=? */ System.out.println(user.getGroup().getId()); } /** *进行本测试之前先把 Group中的 inverse设置成 false * */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestUpdateGroupInverseFalse(){ Groupgroup=(Group)session.get(Group.classclassclassclass,1L); System.out.println(group); System.out.println(group.getUsers()); /** *删除一个 group中的 user */ forforforfor(Useruser:group.getUsers()){ ifififif(user.getId()==13L){ group.getUsers().remove(user); breakbreakbreakbreak; } } /** *通过 group保存,注意,此时: inverse=false * *此时能够成功更新,也就是能够在数据库中删除掉 userid=13的记录 执行了两个语句: 1.update userset *group_id=nullwhereid=?andgroup_id=?2.deleteuserwhereid=? * *所以在进行数据库的表设计的时候,你还真的必须将 group_id这个外键设置成 可以为空 */ session.update(group); } /** *进行本测试之前先把 Group中的 inverse设置成 true 87 * */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestUpdateGroupInverseTrue(){ Groupgroup=(Group)session.get(Group.classclassclassclass,1L); /** *删除一个 group中的 user,其实主要作用还是 cascade由于本例中 cascade=all-delete-orphan *那么无论是否设置了 Inverse都会级联更新 */ forforforfor(Useruser:group.getUsers()){ ifififif(user.getId()==16L){ group.getUsers().remove(user); breakbreakbreakbreak; } } session.update(group); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestDeleteUser(){ Useruser=(User)session.get(User.classclassclassclass,17L); /** *由于是使用面向对象的方式,所以在删除之前应该是要基础关联关系才行 */ user.getGroup().getUsers().remove(user); session.delete(user); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestDeleteGroup(){ Groupgroup=(Group)session.get(Group.classclassclassclass,12L); /** *由于设置了级联删除,所以在删除 group的同时,也是能够将 group所属的 User都删除 * *cascade=all-delete-orphan * *最好先接触关联关系再进行删除 */ forforforfor(Useruser:group.getUsers()){ user.setGroup(nullnullnullnull); } session.delete(group); } } 88 5.5.5.5. 双向一对一外键关联示例 ((((1111)对象关系 )对象关系 )对象关系 )对象关系 一 对一 双向 【外 键 -Foreign Key】 关联 ,在 本例 中, 每个 User 对 应于 一个 Group,即一 个User 就 是一 个 Group,Group 能 够导 航到 User,User 也 可以 导航 到 Group。 域 模型 : public class Group { private Long id;} public class User { private Long id; private Group group;} ((((2222)映射文件 )映射文件 )映射文件 )映射文件 ((((3333)测试文件 )测试文件 )测试文件 )测试文件 publicpublicpublicpublicclassclassclassclassBDOneToOneFKTest{ privateprivateprivateprivateSessionsession=nullnullnullnull; @Before 89 publicpublicpublicpublicvoidvoidvoidvoidbefore(){ session = HibernateUtil.getSessionFactory("edu/zhku/cian/bd/one2one/fk/hibernate.cfg.xml").openSes sion(); session.beginTransaction(); } @After publicpublicpublicpublicvoidvoidvoidvoidafter(){ session.flush(); session.getTransaction().commit(); session.clear(); session.close(); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSave(){ /** *创建两个临时对象 (也称之为: transient状态对象 )User和Group */ Useruser=newnewnewnewUser(); Groupgroup=newnewnewnewGroup(); /** *设置关联关系,使用级联保存与更新: *在【多方】的 many-to-one:cascade="save-update" *即在 User方使用这个属性 */ user.setGroup(group); group.setUser(user); /** *保存数据包含以下几种情况 *注意,在 user表中有一个外键,关联到 group表 *(1)session.save(group); *(2)session.save(user); * *1.在Common.hbm.xml映射文件中都没有设置 cascade属性 *在执行 (1)的时候,程序可以通过,但是只保存了 Group,如果要保存 user,必须在执行 (1) *之后显示执行 (2) *在执行 (2)的时候,程序不能通过,因为此时数据库中没有相应的 group,user有外键关联而不能保存, *要想保存就必须先保存 group * *2.在Common.hbm.xml映射文件中,在 group的one-to-one中设置了 cascade级联属性,记录 90 *保存更新或删除 *但是在 user中没有设置级联属性 *在执行 (1)的时候,程序可以通过,并且同时也会对 user进行保存 *在执行 (2)的时候,如果事先没有保存 group,就不会对 user进行保存 * *3.在Common.hbm.xml映射文件中, group中不设置级联属性,在 user中设置级联属性 *在执行 (1)的时候,只会保存 group *在执行 (2)的时候,不仅会保存 user,也会保存相应的 group,实际上还是先保存了 group再对 user保存的 * *4.在Common.hbm.xml映射文件中, group和user都设置了级联属性 *无论是单独调用 (1),(2)还是混合调用,都会对 user和group进行保存,而且无论那种方式都只 会有两条插入语句(当然知识针对本案例) */ //session.save(group); session.save(user); } /** *插入一个 user,引用一个已经被其他 user对象引用 的group */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveExists(){ /** *一对一双向映射,所以这个测试必然失败 */ Groupgroup=(Group)session.get(Group.classclassclassclass,1L); Useruser=newnewnewnewUser(); user.setGroup(group); session.save(user); } } 6.6.6.6. 双向一对一主键键关联示例(不重要) ((((1111)对象关系 )对象关系 )对象关系 )对象关系 一 对一 双向 【主键-Primary Key】关联,在 本例 中 ,每个User 对 应于 一个 Group,即一 个User 就 是一 个 Group,Group 能 够导 航到 User,User 也 可以 导航 到 Group。 域 模型 : public class Group { private Long id;} 91 public class User { private Long id; private Group group;} ((((2222)映射文件 )映射文件 )映射文件 )映射文件 group ((((3333)测试文件 )测试文件 )测试文件 )测试文件 publicpublicpublicpublicclassclassclassclassBDOneToOnePKTest{ privateprivateprivateprivateSessionsession=nullnullnullnull; @Before publicpublicpublicpublicvoidvoidvoidvoidbefore(){ session = HibernateUtil.getSessionFactory("edu/zhku/cian/bd/one2one/pk/hibernate.cfg.xml").openSe ssion(); session.beginTransaction(); } @After publicpublicpublicpublicvoidvoidvoidvoidafter(){ session.flush(); session.getTransaction().commit(); session.clear(); session.close(); 92 } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSave(){ /** *创建两个临时对象 (也称之为: transient状态对象 )User和Group */ Useruser=newnewnewnewUser(); Groupgroup=newnewnewnewGroup(); /** *设置关联关系,使用级联保存与更新: *在【多方】的 many-to-one:cascade="save-update" *即在 User方使用这个属性 */ user.setGroup(group); group.setUser(user); /** *保存数据包含以下几种情况 *注意,在 user表中有一个外键,关联到 group表 *(1)session.save(group); *(2)session.save(user); * *1.在Common.hbm.xml映射文件中都没有设置 cascade属性 *在执行 (1)的时候 ,程序可以通过 ,但是只保存了 Group,如果要保存 user,必须在执行 (1)之后显示 执行 (2) *在执行 (2)的时候,程序能通过,并且会对 user和group进行保存 * *2.在Common.hbm.xml映 射文 件中 ,在 group的one-to-one中 设置 了 cascade级 联属 性, 记录 保存 更 新或删除 *但是在 user中没有设置级联属性 *在执行 (1)的时候,程序可以通过,并且同时也会对 user进行保存 *在执行 (2)的时候,程序能通过,并且会对 user和group进行保存 * *3.在Common.hbm.xml映射文件中, group中不设置级联属性,在 user中设置级联属性 *与1的情况一样 * *4.在Common.hbm.xml映射文件中, group和user都设置了级联属性 *无 论是 单独 调用 (1),(2)还 是混 合调 用, 都会 对 user和group进 行保 存, 而且 无论 那种 方式 都只 会有 两 条插入语句(当然知识针对本案例) */ session.save(group); //session.save(user); 93 } /** *插入一个 user,引用一个已经被其他 user对象引用 的group */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveExists(){ /** *一对一双向映射,所以这个测试必然失败 */ Groupgroup=(Group)session.get(Group.classclassclassclass,1L); Useruser=newnewnewnewUser(); user.setGroup(group); session.save(user); } } 7.7.7.7. 双向多对多示例 ((((1111)对象关系 )对象关系 )对象关系 )对象关系 多 对多 【双 向】 关联 关系 。 描 述: 即 一个 User 可 能属 于多 个组 ,而 一个 组只 能也 包含 多个 User,Group 可以导航到 User,User 也 可以 导航 到 Group。 类 设计 : public class User { private Long id; private Set groups; } public class Group { private Long id; private Set users; } ((((2222)映射文件 )映射文件 )映射文件 )映射文件 94 ((((3333)测试文件 )测试文件 )测试文件 )测试文件 publicpublicpublicpublicclassclassclassclassBDManyToManyTest{ privateprivateprivateprivateSessionsession=nullnullnullnull; @Before publicpublicpublicpublicvoidvoidvoidvoidbefore(){ session = HibernateUtil.getSessionFactory("edu/zhku/cian/bd/many2many/hibernate.cfg.xml").openSess ion(); session.beginTransaction(); } @After publicpublicpublicpublicvoidvoidvoidvoidafter(){ session.flush(); session.getTransaction().commit(); session.clear(); session.close(); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSchemaExport(){ HibernateUtil.schemaExport(nullnullnullnull); 95 } /** *本测试保存一个 User,不添加任何 Group *我的预测: *会保存 *结论: *Hibernate和我的预期结果一致 */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveUser(){ Useruser=newnewnewnewUser(); session.save(user); } /** *本测试保存一个 Group,不添加任何 User *我的预测: *会保存 *结论: *Hibernate和我的预期结果一致 */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveGroup(){ Groupgroup=newnewnewnewGroup(); session.save(group); } /** *保存一个 Group,Group添加一个瞬态的 User对象,但是 User对象没有把 Group添加进去 *我的猜测: *只会保存 Group,不会对 User进行保存 * *很明显,测试之后, Hibernate做出了不同的处理,他不去保存 Group也不会保存 User * *为什么 Hibernate会如此设计呢? *个人认为他从面向对象的方向去思考,由于 Group建立了和 User的关联,所以在保存 Group的时候 *也应该把 User保存,并在 t_group_user中添加关系, Hibernate的原则是: *保存 Group,同时也要保存 Group相关的关联关系 */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveGroupAndTransientUserWithoutAssociationWithGroup(){ /** *创建相应的对象并设置关联关系 96 */ Groupgroup=newnewnewnewGroup(); Useruser=newnewnewnewUser(); group.getUsers().add(user); session.save(group); } /** *保存一个数据库中没有的 Group,给它添加一个数据库中有的 User *预测结果: *由于是多对多的关联关系,所以能够保存 * *实际测试: *Hibernate就是这么做的 : *Hibernate:insertintot_groupvalues() *Hibernate:insertintot_group_user(gid,uid)values(?,?) * */ @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveGroupWithExistsUser(){ Groupgroup=newnewnewnewGroup(); Useruser=(User)session.get(User.classclassclassclass,1L); group.getUsers().add(user); session.save(group); } } 8.8.8.8. 组件映射 ((((1111)对象关系 )对象关系 )对象关系 )对象关系 主 键映 射: 描 述: 所 谓主 键映 射就 是说 ,一 个对 象是 另一 个对 象的 一部 分, 对应 于数 据库 中是 一张 表 类 设计 : 本 案例 设计 两个 类, Address 是Person 的 一部 分 public class Person { private Long id; private Address address; } 97 public class Address { private String country; private String detail; } ((((2222)映射文件 )映射文件 )映射文件 )映射文件 ((((3333)测试文件 )测试文件 )测试文件 )测试文件 publicpublicpublicpublicclassclassclassclassCompnentMappingTest{ privateprivateprivateprivateSessionsession=nullnullnullnull; @Before publicpublicpublicpublicvoidvoidvoidvoidbefore(){ session= HibernateUtil.getSessionFactory("edu/zhku/cian/compent/mapping /hibernate.cfg.xml").openSession(); session.beginTransaction(); } @After publicpublicpublicpublicvoidvoidvoidvoidafter(){ session.flush(); session.getTransaction().commit(); session.clear(); session.close(); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSchemaExport(){ HibernateUtil.schemaExport(nullnullnullnull); } @Test 98 publicpublicpublicpublicvoidvoidvoidvoidtestSaveWithNullAddress(){ Personperson=newnewnewnewPerson(); person.setAddress(nullnullnullnull); session.save(person); } @Test publicpublicpublicpublicvoidvoidvoidvoidtestSaveWithAddress(){ Personperson=newnewnewnewPerson(); Addressaddress=newnewnewnewAddress(); address.setCountry("中国 "); address.setDetail("我是中国人我爱中国 "); person.setAddress(address); session.save(person); } } 六、过滤器六、过滤器六、过滤器六、过滤器 1.1.1.1. Hibernate4 Hibernate4 Hibernate4 Hibernate4 过滤器 Hibernate3 新 增了 对某 个类 或者 集合 使用 预先 定义 的过 滤器 条件 (filter criteria)的 功能 。 过滤器条件相当于定义一个非常类似于类和各种集合上的“where”属性的约束子句,但是过 滤 器条 件可 以带 参数 ,应 用程 序可 以在 运行 时决 定是 否启 用给 定的 过滤 器 ,以 及使 用什 么样 的 参数 值 。过 滤器 的用 法很 像数 据库 视图 ,只 不过 是在 应用 程序 中确 定使 用什 么样 的参 数的 。 2.2.2.2. 定义过滤器 要使用过滤器,首先要在相应的映射节点中定义。而定义一个过滤器,要用到位于 节 点之 内的 节 点: 示 例如 下: 99 3.3.3.3. 配置过滤器 定 义好 之后 ,就 可以 在某 个类 中使 用这 个过 滤器 : ... 或 者也 可以 在某 个集 合使 用它 : 在同 一个 映射 类中 同 时可 以使 用多 个过 滤器 。 4.4.4.4. 使用过滤器 在程序中,需要使用session 接口中的:enableFilter(String filterName) , getEnabledFilter(String filterName),和disableFilter(String filterName)方法。Session 中默认不 启用过滤器,必须通过enabledFilter() 方法显式的启用。示例代码 session.enableFilter("myFilter").setParameter("myFilterParam", "some-value"); 5.5.5.5. 过滤器示例 在Parent.hbm.xml 中 定义 有如 下的 过滤 器: 100 6.6.6.6. 在定义Child Child Child Child 集合中使用 7.7.7.7. 程序中使用示例 s.enableFilter("myFilter").setParameter("myFilterParam", "%1%"); s.enableFilter("myFilter2") .setParameter("myFilterParam", 1) .setParameter("myFilterParam2", 3); Query q = s.createQuery("selectp from Parent as p "); System.out.println("p==="+p.getChildren()); 第五部分Hibernate Hibernate Hibernate Hibernate 事务和并发 一、概述一、概述一、概述一、概述 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 本身没有事务的实现 Hibernate 直 接使 用 JDBC 连 接和 JTA 资源,不 添加 任何 附加 锁定 行为 。也 就是 说你 在 Hibernate 里 面使 用的 事务 要么 是 JDBC 的 事务 ,要 么是 JTA 的 事务 。 101 2.2.2.2. Hibernate Hibernate Hibernate Hibernate 不锁定内存中的对象 你 的应 用程 序会 按照 你的 数据 库事 务的 隔离 级别 规定 的那 样运 作 ,真 正对 事务 的实 现和 支 持也 依赖 于数 据库 。 3.3.3.3. 并发处理 对 于并 发处 理 ,Hibernate 提 供了 乐观 锁和 悲观 锁来 进行 并发 处理 Hibernate 对 自动 乐观 并 发控 制提 供版 本管 理 ,针 对行 级悲 观锁 定 ,Hibernate 也 提供 了辅 助的 (较 小的 )API,它 使 用了 SELECTFORUPDATE 的SQL 语法。 二、二、二、二、Session Session Session Session 和事务范围和事务范围和事务范围和事务范围 1.1.1.1. Hibernate Hibernate Hibernate Hibernate 的Session Session Session Session 是和事务联系在一起的 可 以通 过 Session 去 获取 事务 的接 口, 从而 进行 事务 的控 制。 2.2.2.2. 数据库事务应该尽可能的短 这样能降低数据库中的锁争用。数据库长事务会阻止你的应用程序扩展到高的并发负 载。因此,假 若在 用户 思考 期间 让数 据库 事务 开着 ,直 到整 个工 作单 元完 成才 关闭 这个 事务 , 这 绝不 是一 个好 的设 计 。这 就引 出一 个问 题 :一 个操 作单 元 ,也 就是 一个 事务 单元 的范 围应 该 是多 大? 一个 操作 一个 ?一 个请 求一 个? 一个 应用 一个 ? 3.3.3.3. 反模式:session-per-operationsession-per-operationsession-per-operationsession-per-operation 在单个线程中,不要因为一次简单的数据库调用,就打开和关闭一次Session!数据库 事 务也 是如 此。 也就 是说 应该 禁止 自动 事务 提交 ( auto-commit)。 4.4.4.4. session-per-requestsession-per-requestsession-per-requestsession-per-request 最 常用 的模 式是 每个 请求 一个 会话 。在 这种 模式 下 ,来 自客 户端 的请 求被 发送 到服 务器 端,即Hibernate 持 久化 层运 行的 地方 ,一 个新 的 Hibernate Session 被 打开 ,并 且执 行这 个 102 操 作单 元中 所有 的数 据库 操作 。一 旦操 作完 成 (同 时对 客户 端的 响应 也准 备就 绪 ),session 被 同步,然 后关 闭 。会 话和 请求 之间 的关 系是 一对 一的 关系 。Hibernate 内 置了 对 “当前session (current session)”的管理,用于简化此模式。你要做的一切就是在服务器端要处理请求的 时 候, 开启 事务 ,在 响应 发送 给客 户之 前结 束事 务, 通常 使用 ServeltFilter 来 完成 。 三、数据库事务声明三、数据库事务声明三、数据库事务声明三、数据库事务声明 1.1.1.1. 非托管环境下 所 谓非 托管 ,指 的是 :应 用程 序没 有托 管到 J2EE 环 境中 ,通 常由 Hibernate 自 己来 负责 管 理数 据库 连接 池。 应用 程序 开发 人员 必须 手工 设置 事务 声明 ,换 句话 说, 就是 手工 启动 , 提 交, 或者 回滚 数据 库事 务。 2.2.2.2. 使用JTAJTAJTAJTA 又 有两 种方 式 ,一 种是 在 Hibernate 配 置里 面修 改 transaction 的factory 类,从 而在 程序 里面可以直接使用Hibernate 的事务API,也就是程序不用变化。另外一种方式就是直接通 过JNDI 去 查找 UserTransaction, 然后 直接 在程 序里 面使 用 JTA 的 接口 来控 制事 务。 四、乐观并发控制四、乐观并发控制四、乐观并发控制四、乐观并发控制 1.1.1.1. 应用程序级别的版本检查 简 单点 说 ,就 是由 应用 程序 自己 实现 版本 检查 来确 保对 话事 务的 隔离 ,从 数据 访问 的角 度 来说 是最 低效 的, 不推 荐使 用。 2.2.2.2. 扩展周期的session session session session 和自动版本化 Hibernate 使用扩展周期的Session 的方式,或者脱管对象实例的方式来提供自动版本 检查。单个Session 实例和它所关联的所有持久化对象实例都被用于整个对话,这被称为 sessionper-conversation。 Hibernate 在 同步 的时 候进 行对 象实 例的 版本 检查 ,如 果检 测到 并发 修改 则抛 出异 常。 103 由 开发 人员 来决 定是 否需 要捕 获和 处理 这个 异常 (通 常的 抉择 是给 户提 供一 个合 并更 改 ,或 者 在无 脏数 据情 况下 重新 进行 业务 对话 的机 会 )。 在 等待 用户 交互 的时 候 ,Session 断 开底 层的 JDBC 连接。这 种方 式以 数据 库访 问的 角 度 来说 是最 高效 的方 式 。应 用程 序不 需要 关心 版本 检查 或脱 管对 象实 例的 重新 关联 ,在 每个 数 据库 事务 中, 应用 程序 也不 需要 载入 读取 对象 实例 。 五、悲观锁定五、悲观锁定五、悲观锁定五、悲观锁定 1.1.1.1. 通常不需要自己去管理锁定策略 Hibernate 总 是使 用数 据库 的锁 定机 制, 从不 在内 存中 锁定 对象 。因 而用 户并 不需 要花 很多精力去担心锁定策略的问题。通常情况下,只要为JDBC 连接指定一下隔离级别,然 后 让数 据库 去搞 定一 切就 够了 。 然而,高 级用 户有 时候 希望 进行 一个 排它 的悲 观锁 定 ,或 者在 一个 新的 事务 启动 的时 候 , 重 新进 行锁 定。 2.2.2.2. 类LockModeLockModeLockModeLockMode 类LockMode 定 义了 Hibernate 所 需的 不同 的锁 定级 别 1: 当更 新或 者插 入一 行记 录的 时候 ,锁 定级 别自 动设 置为 LockMode.WRITE。 2:当 用户 显式 的使 用数 据库 支持 的 SQL 格式SELECT...FORUPDATE 发送SQL 的 时候 , 定 级别 设置 为 LockMode.UPGRADE。 3:当用户显式的使用Oracle 数据库的SQL 语句SELECT...FORUPDATENOWAIT 的时 候 ,锁 定级 别设 置 LockMode.UPGRADE_NOWAIT。 4:当Hibernate 在“可 重复 读 ”或 者是 “序 列化 ”数 据库 隔离 级别 下读 取数 据的 时候 ,锁 定模 式 自 动设 置为 LockMode.READ。 这种 模式 也可 以通 过用 户显 式指 定进 行设 置。 5:LockMode.NONE 代 表无 需锁 定 。在Transaction 结 束时 ,所 有的 对象 都切 换到 该模 式上 来 。与 session 相 关联 的对 象通 过调 用 update() 或者saveOrUpdate() 脱 离该 模式 。 104 3.3.3.3. 显示的指定锁定模式 1: 调用 Session.load() 的 时候 指定 锁定 模式 ( LockMode)。 2: 调用 Session.lock()。 3: 调用 Query.setLockMode()。 4.4.4.4. 显示指定锁定模式 (1)如果在UPGRADE 或者UPGRADE_NOWAIT 锁定模式下调用Session.load(),并 且 要读 取的 对象 尚未 被session 载 入过 ,那 么对 象通 过SELECT...FORUPDATE 这样的SQL 语句被载入。如果为一个对象调用load() 方法时,该对象已经在另一个较少限制的锁定模 式 下载 入了 ,那 么 Hibernate 就 对该 对象 调用 lock() 方 法。 (2)如果指定的锁定模式是READ ,UPGRADE 或UPGRADE_NOWAIT ,那么 Session.lock() 就 执行 版本 号检 查 。(在UPGRADE 或者UPGRADE_NOWAIT 锁 定模 式下 , 执行SELECT...FOR UPDATE 这 样的 SQL 语句。)3:如 果数 据库 不支 持用 户设 置的 锁定 模 式,Hibernate 将 使用 适当 的替 代模 式 (而 不是 扔出 异常 )。这 一点 可以 确保 应用 程序 的可 移 植 性。 第六部分性能提升和二级缓存 一、抓取策略一、抓取策略一、抓取策略一、抓取策略 1.1.1.1. 抓取策略(fetchingfetchingfetchingfetching strategystrategystrategystrategy)概念 当应用程序需要在(Hibernate 实体对象图的)关联关系间进行导航的时候,Hibernate 如 何获 取关 联对 象的 策略 。抓 取策 略可 以在 O/R 映 射的 元数 据中 声明 ,也 可以 在特 定的 HQL 或 条件 查询 ( Criteria Query) 中重 载声 明。 Hibernate4 定 义了 如下 几种 抓取 策略 : ((((1111))))连接抓取( 连接抓取( 连接抓取( 连接抓取( JoinJoinJoinJoin fetchingfetchingfetchingfetching)))) Hibernate 通过在SELECT 语句使用OUTERJOIN(外连接)来获得对象的关联实例或 105 者 关联 集合 。 ((((2222))))查询抓取( 查询抓取( 查询抓取( 查询抓取( SelectSelectSelectSelect fetchingfetchingfetchingfetching)))) 另外发送一条SELECT 语句抓取当前对象的关联实体或集合。除非你显式的指定 lazy="false"禁 止延 迟抓 取 ,否 则只 有当 真正 访问 关联 关系 的时 候 ,才 会执 行第 二条 select 语 句。 ((((3333))))子查询抓取( 子查询抓取( 子查询抓取( 子查询抓取( SubselectSubselectSubselectSubselect fetchingfetchingfetchingfetching)))) 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联 集合。除非你显式的指定lazy="false" 禁止延迟抓取否则只有当你真正访问关联关系的时 候 ,才 会执 行第 二条 select 语 句。 ((((4444))))批量抓取( 批量抓取( 批量抓取( 批量抓取( BatchBatchBatchBatch fetchingfetchingfetchingfetching)))) 对查询抓取的优化方案,通过指定一个主键或外键列表,Hibernate 使用单条SELECT 语 句获 取一 批对 象实 例或 集合 。 2.2.2.2. 映射文档中定义的抓取策略的影响 (1) 通过 get() 或load() 方 法取 得数 据。 (2) 只有 在关 联之 间进 行导 航时 ,才 会隐 式的 取得 数据 。 (3) 条件 查询 (4) 使用 了 subselect 抓 取的 HQL 查 询通 常情 况下 ,我 们并 不使 用映 射文 档进 行抓 取策 略 的 定制 。更 多的 是 ,保 持其 默认 值 ,然 后在 特定 的事 务中 ,使用HQL 的 左连 接抓 取 (left join fetch)对 其进 行重 载 。这 将通 知 Hibernate 在 第一 次查 询中 使用 外部 关联 (outer join),直接 得 到其 关联 数据 。 3.3.3.3. 条件查询APIAPIAPIAPI 条 件查 询 API 中 ,应 该调 用 setFetchMode 语句 例 如: User user= (User) session.createCriteria(User.class) 106 .setFetchMode("permissions", FetchMode.JOIN) .add( Restrictions.idEq(userId) ) .uniqueResult(); 4.4.4.4. Hibernate Hibernate Hibernate Hibernate 会区分下列各种情况 1:Immediate fetching, 立即 抓取 -当 宿主 被加 载时 ,关 联、 集合 或属 性被 立即 抓取 。 2:Lazy collection fetching,延迟集合抓取-直到应用程序对集合进行了一次操作时,集合才 被 抓取 。( 对集 合而 言这 是默 认行 为 。) 3:"Extra-lazy" collection fetching,"Extra-lazy"集 合抓 取 -对 集合 类中 的每 个元 素而 言 ,都 是直 到需要时才去访问数据库。除非绝对必要,Hibernate 不会试图去把整个集合都抓取到 内 存里 来( 适用 于非 常大 的集 合 )。 4:Proxy fetching,代 理抓 取 -对 返回 单值 的关 联而 言 ,当 其某 个方 法被 调用 ,而 非对 其关 键 字 进行 get 操 作时 才抓 取。 5:"No-proxy" fetching,非代理抓取-对返回单值的关联而言,当实例变量被访问的时候进行 抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符, 也 会导 致关 联抓 取 )但 是更 加透 明 ,因 为对 应用 程序 来说 ,不 再看 到 proxy。这 种方 法需 要 在编 译期 间进 行字 节码 增强 操作 ,因 此很 少需 要用 到。 6:Lazy attribute fetching,属 性延 迟加 载 -对 属性 或返 回单 值的 关联 而言 ,当 其实 例变 量被 访 问 的时 候进 行抓 取。 需要 编译 期字 节码 强化 ,因 此这 一方 法很 少用 到 。 二、实例化集合和代理二、实例化集合和代理二、实例化集合和代理二、实例化集合和代理 1.1.1.1. 何时实例化集合和代理 既然Hibernate 会 采取 代理 来延 迟集 合的 实例 化, 那么 何时 实例 化这 个集 合呢 ?因 为如 果session 关 闭了 ,再 访问 未初 始化 的集 合活 代理 的话 ,将 会抛 出 LazyInitializationException 异 常。 常 见的 解决 方法 ,如 下: 107 2.2.2.2. 在session session session session 打开期间去访问集合的数据 这 是一 种不 好的 做法 ,就 是没 有什 么用 的去 访问 集合 的数 据, 触发 其实 例化 。 3.3.3.3. 使用Hibernate.initialized()Hibernate.initialized()Hibernate.initialized()Hibernate.initialized()方法 静 态方 法Hibernate.initialized() 为 你的 应用 程序 提供 了一 个便 捷的 途径 来延 迟加 载集 合 或代理。只要它的Session 处于open 状态,Hibernate.initialize(cat) 将会为cat 强制对代理 实 例化 。同 样, Hibernate.initialize(cat.getKittens())对kittens 的 集合 具有 同样 的功 能。 4.4.4.4. 保持Session Session Session Session 一直处于open open open open 状态,直到集合被实例化 通 常又 有两 种方 法: 第 五和 第六 种方 法 5.5.5.5. OpenOpenOpenOpen SessionSessionSessionSession inininin View View View View 模式 在 一个 基于 Web 的 应用 中, 可以 利用 过滤 器, 在用 户请 求时 打开 Hibernate 的session、 页 面生 成结 束时 关闭 Session, 示例 如下 : publicpublicpublicpublicclassclassclassclassHibernateSessionRequestFilterimplementsimplementsimplementsimplementsFilter{ privateprivateprivateprivateSessionFactorysessionFactory=nullnullnullnull; @Override publicpublicpublicpublicvoidvoidvoidvoiddestroy(){ } @Override publicpublicpublicpublicvoidvoidvoidvoiddoFilter(ServletRequestrequest,ServletResponseresponse, FilterChainchain)throwsthrowsthrowsthrowsIOException,ServletException{ sessionFactory.getCurrentSession().beginTransaction(); chain.doFilter(request,response); sessionFactory.getCurrentSession().getTransaction().commit(); } @Override publicpublicpublicpublicvoidvoidvoidvoidinit(FilterConfigconfig)throwsthrowsthrowsthrowsServletException{ sessionFactory=HibernateUtil.getSessionFactory(); } } 108 6.6.6.6. 业务层实例化数据 在 逻辑 层为 表现 层准 备数 据的 时候 ,在 session 打 开的 情况 下, 实例 化好 所有 需要 的 数 据。 三、集合性能三、集合性能三、集合性能三、集合性能 1.1.1.1. 集合性能1111 要完全理解Hibernate 各种集合的关系结构和性能特点,必须同时考虑“用于Hibernate 更 新或 删除 集合 行数 据的 主键 的结 构 ”。 因此 得到 了如 下的 分类 : 有 序集 合类 、集 合( sets)、 包( bags) 2.2.2.2. 集合性能2222 所有的有序集合类(maps, lists, arrays)都拥有一个由组成的主键。这种 情 况下 集合 类的 更新 是非 常高 效的 ——主 键已 经被 有效 的索 引, 因此 当 Hibernate 试 图更 新 或 删除 一行 时, 可以 迅速 找到 该行 数据 3.3.3.3. 集合性能3333 集合(sets)的 主键 由 和 其他 元素 组成 。对 于有 些元 素来 说 ,这 很低 效 ,特 别是 组合 元 素, 大文 本或 二进 制数 据。 但是 对于 一对 多或 者多 对多 , set 也 可以 达到 同样 的高 效性 能 4.4.4.4. 集合性能4444 Bag 是 最差 的。 因为 bag 允 许重 复的 元素 值, 也没 有索 引字 段, 因此 不可 能定 义主 键 。 Hibernate 无法判断出重复的行。当这种集合被更改时,Hibernate 将会先完整地移除(通过 一个(in a single DELETE)) 整个 集合 ,然 后再 重新 创建 整个 集合 。因 此 Bag 是 非常 低效 的 。 5.5.5.5. 集合性能5555 Lists, maps 和sets 用 于更 新效 率最 高 109 (1)有 序集 合类 型和 大多 数 set 都 可以 在增 加、 删除 、修 改元 素中 拥有 最好 的性 能。 (2)在 多对 多中 , set 性 能不 如有 序集 合类 型 (3)Hibernate 中,set 应 该是 最通 用的 集合 类型 6.6.6.6. 集合性能6666 Bag 和list 是反向集合类(也就是指定了inverse=true 的集合类)中效率最高的在一种 情 况下 ,bag 的 性能 (包括list)要比set 高 得多 :对 于指 明了 inverse="true"的 集合 类 (比 如说 , 标准的双向的一对多关联),我们可以在未初始化(fetch)包元素的情况下直接向bag 或list 添 加新 元素 !这 是因 为 Collection.add())或者Collection.addAll() 方 法对 bag 或者List 总 是返 回true( 这点 与与 Set 不同)。 因此 对于 下面 的相 同代 码来 说, 速度 会快 得多 。 Parent p = (Parent) sess.load(Parent.class, id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); //不 用抓 取集 合 sess.flush(); 四、检测性能四、检测性能四、检测性能四、检测性能 SessionFactory 里 面带 着监 测性 能的 数据 ,就是Statistics 首 先需 要在 cfg.xml 中 打开 统计 功 能, 将 hibernate.generate_statistics 设 置为 true。 在运 行期 间, 则可 以可 以通 过 sf.getStatistics().setStatisticsEnabled(true) 或hibernateStatsBean.setStatisticsEnabled(true) 来 打开 统计 功能 所 有的 测量 值都 可以 由 Statistics 接口API 进 行访 问, 主要 分为 三类 : 1: 使用 Session 的 普通 数据 记录 ,例 如打 开的 Session 的 个数 、取 得的 JDBC 的 连接 数等 。 2: 实体 、集 合、 查询 、缓 存等 内容 的统 一数 据记 录。 3: 和具 体实 体、 集合 、查 询、 缓存 相关 的详 细数 据记 录 110 五、二级缓存五、二级缓存五、二级缓存五、二级缓存 1.1.1.1. 二级缓存基础 (1)Hibernate 的Session 在 事务 级别 进行 持久 化数 据的 缓存 操作 ,也 就是 前边 所讲 的 一 级缓 存 。 (2)当然,也可以分别为每个类(或集合),配置集群、或JVM 级别(SessionFactory 级别) 的 缓存 (二 级缓 存 )。 (3)在Hibernate4 以前的版本:通过在hibernate.cache.provider_class 属性中指定 org.hibernate.cache.CacheProvider 的某个实现的类名,你可以选择让Hibernate 使用哪个缓存 实现。注意,在3.2 版 本之 前 ,默 认使 用 EhCache 作 为缓 存实 现 ,但从3.2 起 就不 再这 样了 。 如:org.hibernate.cache.EhCacheProvider (4)在Hibernate4 的 版本 中 :通 过在 cache.region.factory_classs 指 定相 应的 缓存 区域 的实 现 , 如: org.hibernate.cache.EhCacheRegionFactory 2.2.2.2. 缓存策略提供商 111 3.3.3.3. 元素 缓 存映 射, 类或 者集 合映 射的 “元素”可 以有 下列 形式 : usage(usage(usage(usage(必须 必须 必须 必须 )))) 说 明了 缓存 的策 略 : transactional、read-write、nonstrict-read-write 或read-only。 RegionRegionRegionRegion(可选) (可选) (可选) (可选) 可选, 默 认为 类或 者集 合的 名字 ,指 定第 二级 缓存 的区 域名 。 includeincludeincludeinclude ((((可选 可选 可选 可选 )))) 默认为all) non-lazy 当属性级延迟抓取打开时, 标记为lazy="true"的实体的属性可能无 法 被缓 存 。 可 以在 hibernate.cfg.xml 中 指定 元素 4.4.4.4. 只读缓存 如 果你 的应 用程 序只 需读 取一 个持 久化 类的 实例 ,而 无需 对其 修改 ,那 么就 可以 对其 进 行 只读 缓存 。这 是最 简单 ,也 是实 用性 最好 的方 法。 甚至 在集 群中 ,它 也能 完美 地运 作 。示 例 如下 : .... 112 5.5.5.5. 读写缓存 如果应用程序需要更新数据,那么使用读/写缓存比较合适。如果应用程序要求 “serializable”的 隔离 级别 ,那 么就 决不 能使 用这 种缓 存策 略。 如果 在 JTA 环 境中 使用 缓存 , 必须指定hibernate.transaction.manager_lookup_class 属性的值,通过它,Hibernate 才能知道 该应用程序中JTA 的TransactionManager 的具体策略。在其它环境中,你必须保证在 Session.close()、或Session.disconnect()调 用前 ,整 个事 务已 经结 束。 如果 你想 在集 群环 境中 使 用此 策略 ,你 必须 保证 底层 的缓 存实 现支 持锁 定 (locking)。Hibernate 内 置的 缓存 策略 并不 支 持锁 定功 能。 .... 6.6.6.6. 非严格读写缓存Strategy:Strategy:Strategy:Strategy: nonstrictnonstrictnonstrictnonstrict read/writeread/writeread/writeread/write) 如 果应 用程 序只 偶尔 需要 更新 数据 (也 就是 说 ,两 个事 务同 时更 新同 一记 录的 情况 很不 常见),也 不需 要十 分严 格的 事务 隔离 ,那 么比 较适 合使 用非 严格 读 /写 缓存 策略 。如 果在 JTA 环境中使用该策略,你必须为其指定hibernate.transaction.manager_lookup_class 属性的值, 在 其它 环境 中 ,你 必须 保证 在 Session.close()、或Session.disconnect()调 用前 ,整 个事 务已 经 结束。 7.7.7.7. 事务缓存 Hibernate 的 事务 缓存 策略 提供 了全 事务 的缓 存支 持, 例如 对 JBoss TreeCache 的 支持 。 这 样的 缓存 只能 用 于JTA 环 境中 ,你 必须 指定 为 其hibernate.transaction.manager_lookup_class 属性。 113 8.8.8.8. 缓存管理 1:无 论何 时 ,当 你给 save()、update()或saveOrUpdate()方 法传 递一 个对 象时 ,或 使用 load()、 get()、list()、iterate() 或scroll()方 法获 得一 个对 象时 , 该 对象 都将 被加 入到 Session 的 内部 缓 存 中。 2: 当随 后 flush()方 法被 调用 时, 对象 的状 态会 和数 据库 取得 同步 。如 果你 不希 望此 同步 操 作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一 级 缓存 中去 掉这 些对 象及 其集 合, 如下 示例 : ScrollableResult cats = sess.createQuery(“from Cat as cat”).scroll(); //很 大的 结果 集 while ( cats.next() ){ Cat cat = (Cat) cats.get(0); doSomethingWithACat(cat); sess.evict(cat); } 3:Session 还 提供 了一 个 contains()方法,用 来判 断某 个实 例是 否处 于当 前 session 的 缓存 中 。 4: 如若 要把 所有 的对 象从 session 缓 存中 彻底 清除 ,则 需要 调用 Session.clear()。 5:对于二级缓存来说,在SessionFactory 中定义了许多方法,清除缓存中实例、整个类、 集 合实 例或 者整 个集 合。 sessionFactory.evict(Cat.class, catId); //evict a particular Cat sessionFactory.evict(Cat.class); //evict all Cats sessionFactory.evictCollection(“Cat.kittens”, catId); //evict a particular collection of kittens sessionFactory.evictCollection(“Cat.kittens”);//evict all kitten 6:collections CacheMode 参 数用 于控 制具 体的 Session 如 何与 二级 缓存 进行 交互 。 114 (1)CacheMode.NORMAL-从 二级 缓存 中读 、写 数据 。 (2)CacheMode.GET-从 二级 缓存 读取 数据 ,仅 在数 据更 新时 对二 级缓 存写 数据 。 (3)CacheMode.PUT-仅 向二 级缓 存写 数据 ,但 不从 二级 缓存 中读 数据 。 (4)CacheMode.REFRESH-仅向二级缓存写数据,但不从二级缓存中读数据。通过 hibernate.cache.use_minimal_puts 的设置,强制二级缓存从数据库中读取数据,刷新缓存内 容。 9.9.9.9. 查询缓存 查 询的 结果 集也 可以 被缓 存 。只 有当 经常 使用 同样 的参 数进 行查 询时 ,这 才会 有些 用处 1: 要使 用查 询缓 存, 首先 你必 须打 开 hibernate.cache.use_query_cache 为true 2:该设置将会创建两个缓存区域- 一个用于保存查询结果集 (org.hibernate.cache.StandardQueryCache);另一个则用于保存最近查询的一系列表的时间戳 (org.hibernate.cache.UpdateTimestampsCache)。 请 注意 :在 查询 缓存 中 ,它 并不 缓存 结果 集中 所包 含的 实体 的确 切状 态 ;它 只缓 存这 些 实 体的 标识 符属 性的 值、 以及 各值 类型 的结 果。 所以 查询 缓存 通常 会和 二级 缓存 一起 使用 。 3: 绝大 多数 的查 询并 不能 从查 询缓 存中 受益 ,所 以 Hibernate 默 认是 不进 行查 询缓 存的 。如 若需要进行缓存,请调用Query.setCacheable(true)方法。这个调用会让查询在执行过程中时 先 从缓 存中 查找 结果 ,并 将自 己的 结果 集放 到缓 存中 去 。 10.10.10.10.二级缓存示例 以EHCache 为 例来 说明 二级 缓存 的配 置 。 ((((1111)配置 )配置 )配置 )配置 ehcache.xmlehcache.xmlehcache.xmlehcache.xml 配置ehcache.xml, 放置 到 classpath 下 面, 配置 如下 : 配 置说 明: maxElementsInMemory:缓 存最 大数 目 eternal :缓 存是 否持 久 overflowToDisk:是 否保 存到 磁盘 ,当 系统 当机 时 timeToIdleSeconds:当 缓存 闲置 n秒 后销 毁 timeToLiveSeconds:当 缓存 存活 n秒 后销 毁 ((((2222)配置 )配置 )配置 )配置 hibernate.cfg.xmlhibernate.cfg.xmlhibernate.cfg.xmlhibernate.cfg.xml Hibernate4 以 前的 版本 : true org.hibernate.cache.EhCacheProvider false Hibernate4 的 版本 : ((((3333)配置缓存类和缓存策略 )配置缓存类和缓存策略 )配置缓存类和缓存策略 )配置缓存类和缓存策略 在hibernate.cfg.xml 中 设置 需要 缓存 的类 ,还 有缓 存策 略 false true org.hibernate.cache.EhCacheRegionFactory 116 ((((4444)编写测试文件 )编写测试文件 )编写测试文件 )编写测试文件 s = sf.openSession(); t = s.beginTransaction(); UserModel um1 = (UserModel)s.load(UserModel.class, "1"); System.out.println("um1=="+um1); t.commit(); s = sf.openSession(); t = s.beginTransaction(); UserModel um2 = (UserModel)s.load(UserModel.class, "1"); System.out.println("um2=="+um2); t.commit(); s = sf.openSession(); t = s.beginTransaction(); UserModel um3 = (UserModel)s.load(UserModel.class, "1"); System.out.println("um3=="+um3); t.commit(); ((((5555)运行测试 )运行测试 )运行测试 )运行测试 运 行测 试文 件 ,看 看输 出的 sql 语句,一 共有 多少 条? 一条 查询 语句 才是 正确 的 ,因为 缓 存起 了作 用。 尝试 去掉 缓存 的配 置, 测试 文件 不动 ,再 次运 行, 看看 输出 的 sql 语句,一 共 有多 少条 ?应 该是 三条 了。 ((((6666)说明 )说明 )说明 )说明 如 果不 设置 “查 询缓 存 ”,那么hibernate 只 会缓 存使 用load()方 法获 得的 单个 持久 化对 象 , 如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据 结 果集 的话 ,就 需要 设置 hibernate.cache.use_query_cachetrue 才 行。 117 11.11.11.11.查询缓存示例 ((((1111)配置 )配置 )配置 )配置 ehcache.xmlehcache.xmlehcache.xmlehcache.xml 和 前面 那个 二级 缓存 示例 相同 。 ((((2222))))配置 配置 配置 配置 hibernate.cfg.xmlhibernate.cfg.xmlhibernate.cfg.xmlhibernate.cfg.xml 在 前面 配置 的基 础上 ,添 加: 配置 是否 使用 查询 缓存 为 true true ((((3333)编写程序 )编写程序 )编写程序 )编写程序 在 写程 序的 时候 ,还 要设 置 Query 的setCacheable(true); ((((4444)编写测试文件 )编写测试文件 )编写测试文件 )编写测试文件 s = sf.openSession(); t = s.beginTransaction(); Query query1 =s.createQuery("select Object(o) from UserModel o"); query1.setCacheable(true); List list1 = query1.list(); System.out.println("list1=="+list1); t.commit(); s = sf.openSession(); t = s.beginTransaction(); Query query2 =s.createQuery("select Object(o) from UserModel o"); query2.setCacheable(true); List list2 = query2.list(); System.out.println("list2=="+list2); t.commit(); s = sf.openSession(); 118 t = s.beginTransaction(); Query query3 =s.createQuery("select Object(o) from UserModel o"); query3.setCacheable(true); List list3 = query3.list(); System.out.println("list3=="+list3); t.commit(); 第七部分Hibernate Hibernate Hibernate Hibernate 基本原理 一、一、一、一、Hibernate Hibernate Hibernate Hibernate 整体运行流程整体运行流程整体运行流程整体运行流程 1.1.1.1. 整体流程 ((((1111))))通过 通过 通过 通过 configuration configuration configuration configuration 来读 来读 来读 来读 cfg.xml cfg.xml cfg.xml cfg.xml 文件 文件 文件 文件 ((((2222))))得到 得到 得到 得到 SessionFactory SessionFactory SessionFactory SessionFactory 工厂 工厂 工厂 工厂 ((((3333))))通过 通过 通过 通过 SessionFactory SessionFactory SessionFactory SessionFactory 工厂来创建 工厂来创建 工厂来创建 工厂来创建 Session Session Session Session 实例 实例 实例 实例 ((((4444))))通过 通过 通过 通过 Session Session Session Session 打开事务 打开事务 打开事务 打开事务 ((((5555))))通过 通过 通过 通过 session session session session 的的的的api api api api 操作数据库 操作数据库 操作数据库 操作数据库 ((((6666))))事务提交 事务提交 事务提交 事务提交 ((((7777))))关闭连接 关闭连接 关闭连接 关闭连接 2.2.2.2. 说明 以 下分 方法 描述 的实 现流 程并 不是 Hibernate 的 完整 实现 流程 ,也 不是 Hibernate 的 完整 实 现顺 序, 只描 述了 Hibernate 实 现这 些方 法的 主干 和基 本方 式, 主要 是用 来理 解这 些方 法 背 后都 发生 了些 什么 ,如 果需 要详 细完 整的 实现 流程 ,请 查阅 Hibernate 相 应文 档和 源代 码 。 119 3.3.3.3. Hibernate Hibernate Hibernate Hibernate 运行流程图 二、二、二、二、Save Save Save Save 方法基本流程方法基本流程方法基本流程方法基本流程 1.1.1.1. 调用了session.save(UserModel)session.save(UserModel)session.save(UserModel)session.save(UserModel) 1:TO--->PO:Hibernate 先 在缓 存中 查找 ,如 果发 现在 内部 缓存 中已 经存 在相 同 id 的PO, 就 认为 这个 数据 已经 保存 了 ,抛 出例 外 。如 果缓 存中 没有 ,Hibernate 会 把传 入的 这个 TO 对 象 放到 session 控 制的 实例 池去 ,也 就是 把一 个瞬 时对 象变 成了 一个 持久 化对 象。 如果 需 要 Hibernate 生 成主 键值 , Hibernate 就 会去 生成 id 并 设置 到 PO 上 2: 客户 端提 交事 务或 者刷 新内 存 3: 根据 model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 4: 根据 hbm.xml 文 件和 model 来 动态 的拼 sql 120 如 下: insert into 表名(来自hbm.xml) (字 段名 列表 (来自hbm.xml)) values(对 应的 值的 列表 (根 据hbm.xml 从 传入 的 model 中 获取 值 )) 5: 真正 用 JDBC 执行sql, 把值 添加 到数 据库 6: 返回 这个 PO 的id。 2.2.2.2. Save Save Save Save 运行流程图1111 121 3.3.3.3. Save Save Save Save 运行流程图2222 三、三、三、三、Update(DO)Update(DO)Update(DO)Update(DO)执行流程执行流程执行流程执行流程 DO——Detached Object, 更新 一个 托管 状态 下的 对象 。 1.1.1.1. 调用session.update(user)session.update(user)session.update(user)session.update(user) 1:DO--->PO:首先根据model 的主键在hibernate 的实例池中查找该对象,找到就抛出错 误。如果没有就DO--->PO,Hibernate 会把传入的这个DO对象放到session 控制的实例池 去 ,也 就是 把一 个瞬 时对 象变 成了 一个 持久 化对 象 2:客 户端 提交 事务 或者 刷新 内存 3:根据model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 4:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 不进 行脏 数据 检查 ,如 下: update 表名(来自hbm.xml) set 字 段名 (来自hbm.xml)=值(根据hbm.xml 从 传入 的model 中 获取 值 ) where 条件。 122 5:真 正用 JDBC 执行sql, 把值 修改 到数 据库 2.2.2.2. Update(DO)Update(DO)Update(DO)Update(DO)运行流程图1111 3.3.3.3. Update(DO)Update(DO)Update(DO)Update(DO)运行流程图2222 123 四、四、四、四、Update(PO)Update(PO)Update(PO)Update(PO)执行流程执行流程执行流程执行流程 PO——Persistent Object 持 久化 对象 1.1.1.1. 调用session.update(user)session.update(user)session.update(user)session.update(user)流程 1:首 先根 据 model 的 主键 在 hibernate 的 实例 池中 查找 该对 象, 找到 就使 用该 PO 对 象 (用 来检 查脏 数据 )。 2:客 户端 提交 事务 或者 刷新 内存 3:Hibernate 会 进行 脏数 据检 查, 如果 没有 数据 被修 改, 就不 执行 下面 的步 骤了 。 4:根据model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 5:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 进行 脏数 据检 查( 如果 开启 了 dynamic-update 的话), 如下 : update 表名(来自hbm.xml) set 字 段名 (来 自 hbm.xml)=值(根据hbm.xml 从 传入 的model 中 获取 值 ) where 条件 6:真 正用 JDBC 执行sql, 把值 修改 到数 据库 2.2.2.2. Update(PO)Update(PO)Update(PO)Update(PO)流程图1111 124 3.3.3.3. Update(PO)Update(PO)Update(PO)Update(PO)流程图2222 五、五、五、五、Delete(DO)Delete(DO)Delete(DO)Delete(DO)执行流程执行流程执行流程执行流程 DO——Detached Object 托 管状 态对 象 1.1.1.1. IDIDIDID生成方式为assigned assigned assigned assigned 情况 ((((1111)调用 )调用 )调用 )调用 session.delete(user)session.delete(user)session.delete(user)session.delete(user)流程 流程 流程 流程 1:根据model 的 主键 在数 据库 里面 查找 数据 ,来 保证 对象 的存 在 ,然 后把 找到 的对 象放 到内 存里 面, 如果 此时 在 hibernate 的 实例 池中 已经 存在 对应 的实 体对 象 (注 意: 代理 对象 不算 实 体 对象 ), 就抛 出例 外。 2:如 果此 时在 hibernate 的 实例 池中 不存 在对 应的 实体 对象 ,那 么就 把对 象放 到内 存里 面 , 但 会标 识成 待删 除的 对象 ,就 不可 以被 load 等 使用 了。 3:如 果对 象还 是不 存在 ,那 么就 直接 返回 了( 注意 ,这 个时 候是 不抛 出例 外的 )。 也就 是 说,delete 之 前会 执行 一个 查询 语句 。 125 4:客 户端 提交 事务 或者 刷新 内存 5:判 断待 删除 的 PO 是 否存 在, 存在 才需 要删 除, 否则 不需 要删 除 6:如果要删除,才执行以下的步骤。先根据model 类型和cfg.xml 中映射文件的注册来找 到 相应 的 hbm.xml 文件 7:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 如下 : delete from 表名(来自hbm.xml) where 主键=值(来自model) 8:真 正用 JDBC 执行sql, 把数 据从 数据 库中 删除 ((((2222))))Delete(DO)Delete(DO)Delete(DO)Delete(DO)流程图 流程图 流程图 流程图 1111 126 ((((3333))))Delete(DO)Delete(DO)Delete(DO)Delete(DO)流程图 流程图 流程图 流程图 2222 2.2.2.2. IDIDIDID生成方式不为assigned assigned assigned assigned 情况 ((((1111)调用 )调用 )调用 )调用 session.delete(user)session.delete(user)session.delete(user)session.delete(user)流程 流程 流程 流程 1:根据model 的 主键 在 hibernate 的 实例 池中 查找 对应 的实 体对 象 (注 意: 代理 对象 不 算实 体对 象 ), 找到 就抛 出例 外。 2:如 果内 存中 没有 对应 的实 体对 象, 就什 么都 不做 。 3:客 户端 提交 事务 或者 刷新 内存 4:先 根据 model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 5:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 如下 : delete from 表名(来自hbm.xml) where 主键=值(来自model) 6:真 正用 JDBC 执行sql, 把数 据从 数据 库中 删除 ,如 果数 据不 存在 ,就 抛出 例外 127 ((((2222))))Delete(DO)Delete(DO)Delete(DO)Delete(DO)流程图 流程图 流程图 流程图 1111 ((((3333))))Delete(DO)Delete(DO)Delete(DO)Delete(DO)流程图 流程图 流程图 流程图 2222 128 六、六、六、六、Delete(PO)Delete(PO)Delete(PO)Delete(PO)方法流程方法流程方法流程方法流程 PO——Persistent Object 持 久化 对象 1.1.1.1. 调用session.delete(user)session.delete(user)session.delete(user)session.delete(user)过程 1:根据model 的 主键 在 hibernate 的 实例 池中 查找 对应 的实 体对 象 (注意:代 理对 象不 算实 体 对象), 找到 就使 用该 对象 。 2:如 果内 存中 没有 对应 的实 体对 象, 就到 数据 库中 查找 来保 证对 象的 存在 ,把 找到 的对 象 放到内存里面,而且不会标识成待删除的对象,可以继续被load 等使用。代理对象也需要 去 数据 库中 查找 数据 。 3:如 果对 象还 是不 存在 ,就 抛出 例外 。也 就是 说, delete 之 前可 能会 执行 一个 查询 语句 。 4:客 户端 提交 事务 或者 刷新 内存 5:根据model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 6:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 如下 : delete from 表名(来自hbm.xml) where 主键=值(来自model) 7:真 正用 JDBC 执行sql, 把数 据从 数据 库中 删除 2.2.2.2. Delete(PO)Delete(PO)Delete(PO)Delete(PO)流程图1111 129 3.3.3.3. Delete(PO)Delete(PO)Delete(PO)Delete(PO)流程图2222 七、七、七、七、Load Load Load Load 方法执行流程方法执行流程方法执行流程方法执行流程 1.1.1.1. 调用session.load(User.class,session.load(User.class,session.load(User.class,session.load(User.class, key)key)key)key) 1:根据model 类 型和 主键 值在 一级 缓存 中查 找对 象, 找到 就返 回该 对象 2:如 果没 有找 到, 判断 是否 lazy=true, 如果 是, 那就 生成 一个 代理 对象 并返 回; 否则 就先 查 找二 级缓 存 ,二 级缓 存没 有 ,就 查找 数据 库。 如果 是返 回代 理对 象的 ,在 第一 次访 问非 主键 属 性的 时候 ,先 查找 二级 缓存 ,二 级缓 存中 没有 才真 正查 找数 据库 。 3:如果需要查找数据库的话,会根据model 类型和cfg.xml 中映射文件的注册来找到相应 的hbm.xml 文件 4:根据hbm.xml 文 件和 model 来 动态 的拼 sql, 如下 : select 字 段列 表 (来自hbm.xml) from 表名(来自hbm.xml) where 主键=值 5:真 正用 JDBC 执行sql, 把数 据从 数据 库中 查询 出来 到 rs里 面。 如果 找不 到就 报错 6:从 结果 集 ---〉Model, 然后 返回 model 注 意: load 方 法开 不开 事务 都可 以执 行查 询语 句。 130 2.2.2.2. Load Load Load Load 执行过程图1111 3.3.3.3. Load Load Load Load 执行过程图2222 131 八、八、八、八、Get Get Get Get 方法执行过程方法执行过程方法执行过程方法执行过程 1.1.1.1. 调用session.get(User.class,session.get(User.class,session.get(User.class,session.get(User.class, key)key)key)key) 1:先 根据 model 类 型和 主键 值查 找缓 存, 如果 存在 具体 的实 体对 象, 就返 回; 如果 存在 实 体 的代 理对 象( 比如 前面 load 这 条数 据, 但是 还没 有使 用, 那么 load 生 成的 是一 个只 有主 键 值的 代理 对象 ), 那么 查找 数据 库, 把具 体的 数据 填充 到这 个代 理对 象里 面, 然后 返回 这 个 代理 对象 ,当 然这 个代 理对 象此 时已 经完 全装 载好 数据 了, 跟实 体对 象没 有什 么区 别了 。 2:如要查找数据库,先根据model 类型和cfg.xml 映射文件的注册来找相应的hbm.xml3: 根据hbm.xml 文 件和 model 来 动态 的拼 sql, 如下 : select 字 段列 表 (来自hbm.xml) from 表名(来自hbm.xml) where 主键=值 4:真 正用 JDBC 执行sql, 把数 据从 数据 库中 查询 出来 到 rs里 面, 没有 值就 返回 null 5:从 结果 集 ---〉Model, 然后 返回 model 注 意: get 方 法开 不开 事务 都可 以执 行查 询语 句。 2.2.2.2. Get Get Get Get 执行流程图1111 132 3.3.3.3. Get Get Get Get 执行流程图2222 九、九、九、九、Query Query Query Query 执行过程执行过程执行过程执行过程 1.1.1.1. 调用query.list()query.list()query.list()query.list()过程 1:对HQL 进 行语 义分 析, 分析 出 model 来 2:根据model 类 型和 cfg.xml 中 映射 文件 的注 册来 找到 相应 的 hbm.xml 文件 3:根据hbm.xml 文件和model,来解析HQL,从而实现动态的把HQL 转换成对应的sql, (从hql---〉sql 这 个过 程是 非常 复杂 的 ,不 但区 分不 同的 数据 库 ,还 包括 了对 sql 进 行自 动 的 优化 ), 这里 只能 简单 的示 例如 下: select 字 段列 表 (来自hbm.xml) from 表名(来自hbm.xml) where 条件 4:真 正用 JDBC 执行sql, 把数 据从 数据 库中 查询 出来 到 rs里面 5:从 结果 集 ---〉Model 集 合( 或对 象数 组 ), 然后 返回 model 集 合( 或对 象数 组) 注 意: list()方 法开 不开 事务 都可 以执 行查 询语 句。 133 2.2.2.2. Query Query Query Query 的执行流程图 第八部分Hibernate Hibernate Hibernate Hibernate 最佳实践 一、一、一、一、设计细颗粒度的持久类并且使用设计细颗粒度的持久类并且使用设计细颗粒度的持久类并且使用设计细颗粒度的持久类并且使用来实现来实现来实现来实现 映射。映射。映射。映射。 例 如使 用一 个 Address 持 久类 来封 装 street, suburb, state, postcode. 这 将有 利于 代码 重用 和 简化 代码 重构 (refactoring)的 工作 。 134 二、二、二、二、对持久类声明标识符属性对持久类声明标识符属性对持久类声明标识符属性对持久类声明标识符属性(((( identifieridentifieridentifieridentifier properties)properties)properties)properties)。。。。 Hibernate 中标识符属性是可选的,不过有很多原因来说明你应该使用标识符属性。我 们 建议 标识 符应 该是 “人造”的(自 动生 成, 不涉 及业 务含 义 )。 三、三、三、三、使用自然键使用自然键使用自然键使用自然键(natural(natural(natural(natural keys)keys)keys)keys)标识标识标识标识 对所有的实体都标识出自然键,用进行映射。实现equals()和hashCode(), 在 其中 用组 成自 然键 的属 性进 行比 较。 四、四、四、四、为每个持久类写一个映射文件为每个持久类写一个映射文件为每个持久类写一个映射文件为每个持久类写一个映射文件 不要把所有的持久类映射都写到一个大文件中。把com.eg.Foo 映射到 com/eg/Foo.hbm.xml 中 ,在 团队 开发 环境 中, 这一 点显 得特 别有 意义 。 五、五、五、五、把映射文件作为资源加载把映射文件作为资源加载把映射文件作为资源加载把映射文件作为资源加载 把 映射 文件 和他 们的 映射 类放 在一 起进 行部 署。 真正 高质 量培 训签 订就 业协 议 六、六、六、六、考虑把查询字符串放在程序外面考虑把查询字符串放在程序外面考虑把查询字符串放在程序外面考虑把查询字符串放在程序外面 如果你的查询中调用了非ANSI 标准的SQL 函数,那么这条实践经验对你适用。把查 询 字符 串放 在映 射文 件中 可以 让程 序具 有更 好的 可移 植性 。 七、七、七、七、使用绑定变量使用绑定变量使用绑定变量使用绑定变量 就像在JDBC 编程中一样,应该总是用占位符"?"来替换非常量值,不要在查询中用字 符 串值 来构 造非 常量 值! 更好 的办 法是 在查 询中 使用 命名 参数 。 八、八、八、八、不要自己来管理不要自己来管理不要自己来管理不要自己来管理JDBCJDBCJDBCJDBC connectionsconnectionsconnectionsconnections Hibernate 允 许应 用程 序自 己来 管理 JDBC connections,但 是应 该作 为最 后没 有办 法的 办 法。如果你不能使用Hibernate 内建的connections providers,那么考虑实现自己来实现 135 org.hibernate.connection.ConnectionProvider 九、九、九、九、考虑使用用户自定义类型考虑使用用户自定义类型考虑使用用户自定义类型考虑使用用户自定义类型(custom(custom(custom(custom type)type)type)type) 假设你有一个Java 类型,来自某些类库,需要被持久化,但是该类没有提供映射操作 需 要的 存取 方法 。那 么你 应该 考虑 实现 org.hibernate.UserType 接口。这 种办 法使 程序 代码 写 起 来更 加自 如, 不再 需要 考虑 类与 Hibernate type 之 间的 相互 转换 。 十、十、十、十、在性能瓶颈的地方使用硬编码的在性能瓶颈的地方使用硬编码的在性能瓶颈的地方使用硬编码的在性能瓶颈的地方使用硬编码的JDBCJDBCJDBCJDBC 在 系统 中对 性能 要求 很严 格的 一些 部分 ,某 些操 作也 许直 接使 用 JDBC 会 更好 。但 是请 先 确认 这的 确是 一个 瓶颈 ,并 且不 要想 当然 认为 JDBC 一 定会 更快 。如 果确 实需 要直 接使 用 JDBC,那 么最 好打 开一 个 Hibernate Session 然 后从 Session 获得connection,按 照这 种办 法 你 仍然 可以 使用 同样 的 transaction 策 略和 底层 的 connection provider。 十一、十一、十一、十一、理解理解理解理解Session Session Session Session 清洗(清洗(清洗(清洗(flushingflushingflushingflushing)))) Session 会不时的向数据库同步持久化状态,如果这种操作进行的过于频繁,性能会受 到一定的影响。有时候你可以通过禁止自动flushing,尽量最小化非必要的flushing 操作, 或 者更 进一 步, 在一 个特 定的 transaction 中 改变 查询 和其 它操 作的 顺序 。 十二十二十二十二、、、、在三层结构中在三层结构中在三层结构中在三层结构中,,,,考虑使用托管对象考虑使用托管对象考虑使用托管对象考虑使用托管对象((((detacheddetacheddetacheddetached objectobjectobjectobject)))) 当使用一个servlet/ session bean 类型的架构的时候, 你可以把已加载的持久对象在 session bean 层和servlet/ JSP 层之间来回传递。使用新的session 来为每个请求服务,使用 Session.merge() 或者Session.saveOrUpdate()来 与数 据库 同步 。 十三、十三、十三、十三、在两层结构中,考虑使用长持久上下文在两层结构中,考虑使用长持久上下文在两层结构中,考虑使用长持久上下文在两层结构中,考虑使用长持久上下文(long(long(long(long persistencepersistencepersistencepersistence contexts).contexts).contexts).contexts). 为了得到最佳的可伸缩性,数据库事务(Database Transaction)应该尽可能的短。但是, 程序常常需要实现长时间运行的“应用程序事务(Application Transaction)”,包含一个从用户 136 的 观点 来看 的原 子操 作。 这个 应用 程序 事务 可能 跨越 多次 从用 户请 求到 得到 反馈 的循 环 。用 脱 管对 象 (与session 脱 离的 对象 )来 实现 应用 程序 事务 是常 见的 。或者,尤 其在 两层 结构 中 , 把Hibernate Session 从JDBC 连 接中 脱离 开, 下次 需要 用的 时候 再连 接上 。绝 不要 把一 个 Session 用 在多 个应 用程 序事 务 (Application Transaction)中 ,否 则你 的数 据可 能会 过期 失效 。 十四、十四、十四、十四、不要把异常看成可恢复的不要把异常看成可恢复的不要把异常看成可恢复的不要把异常看成可恢复的 这 一点 甚至 比 “最 佳实 践 ”还 要重 要 ,这是“必 备常 识 ”。当 异常 发生 的时 候 ,必 须要 回 滚 Transaction ,关闭Session。如 果你 不这 样做 的话 ,Hibernate 无 法保 证内 存状 态精 确的 反应 持 久状 态 。尤 其不 要使 用 Session.load()来 判断 一个 给定 标识 符的 对象 实例 在数 据库 中是 否存 在 ,应 该使 用 Session.get()或 者进 行一 次查 询 。 十五、十五、十五、十五、对于关联优先考虑对于关联优先考虑对于关联优先考虑对于关联优先考虑lazylazylazylazy fetchingfetchingfetchingfetching 谨慎的使用主动抓取(eager fetching)。对于关联来说,若其目标是无法在第二级缓存中 完全缓存所有实例的类,应该使用代理(proxies)与/或具有延迟加载属性的集合(lazy collections)。若目标是可以被缓存的,尤其是缓存的命中率非常高的情况下,应该使用 lazy="false",明 确的 禁止 掉 eager fetching。如 果那 些特 殊的 确实 适合 使用 join fetch 的 场合 , 请 在查 询中 使用 left join fetch。 十六、十六、十六、十六、使用使用使用使用openopenopenopen sessionsessionsessionsession inininin view view view view 模式,或者执行严格的模式,或者执行严格的模式,或者执行严格的模式,或者执行严格的 装配期装配期装配期装配期(assembly(assembly(assembly(assembly phase)phase)phase)phase)策略来避免再次抓取数据带来的策略来避免再次抓取数据带来的策略来避免再次抓取数据带来的策略来避免再次抓取数据带来的 问题问题问题问题 Hibernate 让 开发 者们 摆脱 了繁 琐的 Data Transfer Objects (DTO)。在 传统 的 EJB 结 构中 , DTO 有双重作用:首先,他们解决了entity bean 无法序列化的问题;其次,他们隐含地定 义 了一 个装 配期 ,在 此期 间 ,所 有在 view 层 需要 用到 的数 据 ,都 被抓 取 、集 中到 了 DTO 中, 然后控制才被装到表示层。Hibernate 终结了第一个作用。然而,除非你做好了在整个渲染 过 程中 都维 护一 个打 开的 持久 化上 下文 (session)的 准备 ,你 仍然 需要 一个 装配 期 (想 象一 下 , 你的业务方法与你的表示层有严格的契约,数据总是被放置到托管对象中)。这并非是 Hibernate 的 限制 !这 是实 现安 全的 事务 化数 据访 问的 基本 需求 。 137 十七、十七、十七、十七、考虑把考虑把考虑把考虑把Hibernate Hibernate Hibernate Hibernate 代码从业务逻辑代码中抽象出来代码从业务逻辑代码中抽象出来代码从业务逻辑代码中抽象出来代码从业务逻辑代码中抽象出来 把Hibernate 的数据存取代码隐藏到接口(interface)的后面,组合使用DAO 和Thread Local Session 模式。通过Hibernate 的UserType,你甚至可以用硬编码的JDBC 来持久化那 些 本该 被 Hibernate 持 久化 的类 。 (该 建议 更适 用于 规模 足够 大应 用软 件中 ,对 于那 些只 有 5 张 表的 应用 程序 并不 适合 。 ) 十八、十八、十八、十八、不要用怪异的连接映射不要用怪异的连接映射不要用怪异的连接映射不要用怪异的连接映射 多 对多 连接 用得 好的 例子 实际 上相 当少 见 。大 多数 时候 你在 “连 接表 ”中 需要 保存 额外 的 信息。这 种情 况下 ,用 两个 指向 中介 类的 一对 多的 连接 比较 好 。实 际上 ,我 们认 为绝 大多 数 的 连接 是一 对多 和多 对一 的 ,你 应该 谨慎 使用 其它 连接 风格 ,用 之前 问自 己一 句 ,是 否真 的 必 须这 么做 。 十九、十九、十九、十九、偏爱双向关联偏爱双向关联偏爱双向关联偏爱双向关联 单 向关 联更 加难 于查 询。 在大 型应 用中 ,几 乎所 有的 关联 必须 在查 询中 可以 双向 导航 。
还剩136页未读

继续阅读

pdf贡献者

cianfree

贡献于2012-11-30

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!