1. Java对象持久化概述
1.1. 应用程序的分层体系结构
1.1.1. 基于B/S的典型三层架构
说明:
1, 展现层:提供与用户交互的界面。
2, 业务逻辑层:实现各种业务逻辑。
3, 数据访问层:负责存放和管理应用程序的持久化业务数据。
1.1.2. 数据访问层与Hibernate在Java应用程序中的角色
数据访问层(持久化层)封装了数据访问的细节,为业务逻辑层提供了面向对象的API。完善的持久化层应该达到的目标:
1, 代码重用性高,可完成所有的数据访问操作。
2, 如果需要的话,能够支持多种数据库平台。
3, 具有相对独立性,当持久化层变化时,不会影响上层实现。
在数据访问层(持久化层)中可以使用Hibernate框架以实现要求,如下图所示:
1.2. 软件模型
1.2.1. 各种模型的说明
概念模型:
模拟问题域中的真实实体。描述每个实体的概念和属性及实体间关系。不描述实体行为。实体间的关系有一对一、一对多和多对多。。
关系数据模型:
在概念模型的基础上建立起来的,用于描述这些关系数据的静态结构。有以下内容组成:
1, 若干表
2, 表的所有索引
3, 视图
4, 触发器
5, 表与表之间的参照完整性
域模型:
在软件的分析阶段创建概念模型,在软件设计阶段创建域模型。
组成部分:
1, 具有状态和行为的域对象。
2, 域对象之间的关联。
域对象(domain object):
构成域模型的基本元素就是域对象。对真实世界的实体的软件抽象,也叫做业务对象(Business Object,BO)。域对象可代表业务领域中的人、地点、事物或概念。
域对象分为以下几种:
1, 实体域对象:通常是指业务领域中的名词。(plain old java object,简单Java对象)。
2, 过程域对象:应用中的业务逻辑或流程。依赖于实体域对象,业务领域中的动词。如发出订单、登陆等。
3, 事件域对象:应用中的一些事件(警告、异常)。
1.2.2. 域对象间的关系
关联:
类间的引用关系。以属性定义的方式表现。
依赖:
类之间访问关系。无需定义成属性。在A中访问B中的方法或属性,或者A负责实例化B。
聚集(Aggregation):
整体与部分的关系。例人与手的关系。部分类的对象不能单独存在,他的生命周期依赖于整体类的对象的生命周期,整体消失时,部分也随之消失。
一般化(Generalization):
类之间继承关系。
1.2.3. 域对象(Domain)的持久化概念
实体域对象在内存中创建后,不能永久存在。将实体域对象永久保存起来,就是持久化的过程。通常只有实体域对象需要持久化,过程域对象和事件域对象一般不需要持久化。广义持久化指增、删、改、查。
1.3. ORM与ORM框架
1.3.1. ORM(Object/Relation Mapping)
对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。
ORM 主要解决对象-关系的映射
面向对象概念 | 面向关系概念 |
类 | 表 |
对象 | 表的行(记录) |
属性 | 表的列(字段) |
ORM的实现思想:
将关系数据库中表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。因此ORM的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
ORM 采用元数据来描述对象-关系映射细节
元数据通常采用 XML 格式,并且存放在专门的对象-关系映射文件中。
ORM中间件的使用方法
采用元数据来描述对象-关系映射细节,元数据通常采用xml格式,并存放在专门的对象-关系映射文件中。只要配置了持久化类与表的映射关系,orm中间件在运行时就能够参照映射文件的信息,把域对象持久化到数据库中。例(Hibernate):
1.3.2. 流行的ORM框架
Hibernate:
非常优秀、成熟的 ORM 框架。提供强大的对象和关系数据库映射以及查询功能。Hibernate是面向对象的程序设计语言和关系型数据库之间的桥梁,允许开发者采用面向对象的方式来操作关系数据库。
Hibernate 的目标是释放开发者通常的与数据库持久化相关的编程任务的 95%。消除那些针对特定数据库厂商的SQL代码。
Ibatis:
相比 Hibernate 灵活高,运行速度快。开发速度慢,不支持纯粹的面向对象操作,需熟悉sql语句,并且熟练使用sql语句优化功能。
TopLink
OJB
1.3.3. Hibernate 与 Jdbc 代码对比
1.4. 小结
2. Hibernate入门(HelloWorld)
2.1. Hibernate介绍
Hibernate是一个基于jdbc的开源的持久化框架,是一个优秀的ORM实现,它很大程度的简化了dao层编码工作。Hibernate对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
在分层结构中处于持久化层,封装对数据库的访问细节,使业务逻辑层更专注于实现业务逻辑。
Hibernate的主页为:http://www.hibernate.org/
Hibernate的下载地址:http://sourceforge.net/projects/hibernate/files/hibernate3/
HibernateTools的下载地址:http://sourceforge.net/projects/jboss/files/JBossTools/JBossTools3.1.x/HibernateTools-3.3.1.v201006011046R-H111-GA.zip/download
我们所学的版本是3.6.0
2.2. Hibernate的体系结构与开发步骤
Hibernate的体系结构
Hibernate开发步骤
2.3. 第一个Hibernate程序(HelloWorld)
2.3.1. 创建Eclipse工程并引入相关的jar包
新建Java工程,并添加如下jar包:
1,{hibernate_home}/ hibernate3.jar
2,{hibernate_home}/lib/required/*.jar
3,{hibernate_home}/lib/jpa/hibernate-jpa-2.0-api-1.0.0.Final.jar
4, 数据库对应的JDBC驱动(例如mysql-connector-java-5.1.5-bin.jar)
还可以加入日志相关的jar包(不加也可以):
1, log4j-1.2.15.jar
2, slf4j-log4j12-1.6.1.jar
2.3.2. 创建持久化对象:User
2.3.3. 创建对象-关系映射文件:User.hbm.xml
Customer类属性 | Java类型 | Hibernate类型 | Cutomers字段名 | Sql类型 |
Name | java.lang.String | string | NAME | Varchar(12) |
age | int | int | age | INT |
Sex | char | character | SEX | CHAR(1) |
Married | boolean | boolean | married | bit |
Des | java.lang.String | text | des | CLOB |
Pic | byte[] | binary | pic | BLOB |
Birthday | java.sql.Date | date | BIRTHDAY | DATE |
registeredTime | java.sql.TimeStamp | timestamp | REGISTERED_TIME | TIMESTAMP |
2.3.4. 创建 Hibernate 配置文件:hibernate.cfg.xml
创建表、JavaBean、写映射文件
2.3.5. 通过 Hibernate API 编写访问数据库的代码
编程步骤
1, 获取 Configuration 对象。
2, 获取 SessionFactory 对象。
3, 获取 Session,打开事务。
4, 用面向对象的方式操作数据库。
5, 关闭事务,关闭 Session。
增删改查功能的实现代码
3. Hibernate API简介
3.1. Configuration
Configuration 类负责管理 Hibernate 的配置信息
包括如下内容:1,Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等(对应 hibernate.cfg.xml 文件)。2,持久化类与数据表的映射关系(*.hbm.xml 文件)。
•
创建 Configuration 的两种方式
1, 属性文件(hibernate.properties)
Configuration cfg = new Configuration();
2, Xml文件(hibernate.cfg.xml)
a) 加载默认名称的配置文件(hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
b) 或加载指定名称的配置文件:
Configuration cfg = new Configuration()
.configure(“myhibernate.cfg.xml”);
3.2. SessionFactory
Configuration对象根据当前的配置信息生成 SessionFactory 对象。SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息(SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。同时,SessionFactory还负责维护Hibernate的二级缓存)。 相关代码如下:
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
1, SessionFactory是线程安全的。
2, 构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
3, SessionFactory是生成Session的工厂:
Session session = sessionFactory.openSession();
3.3. Session
Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,所有持久化对象必须在 session 的管理下才可以进行持久化操作。此对象的生命周期很短。Session 中有一个缓存,显式执行flush()方法之前,所有的持久层操作的数据都缓存在 session 对象处。(相当于 JDBC 中的 Connection)。一般一个业务操作使用一个Session。
l 持久化类与 Session 关联起来后就具有了持久化的能力。
l Session是线程不安全的,一般是一次业务操作使用一个Session。
l Session 类的一些方法:
取得持久化对象的方法: get() load()
持久化对象都得保存,更新和删除:save(),update(),saveOrUpdate(),delete()
开启事务: beginTransaction().
管理 Session 的方法:isOpen(),flush(), clear(), evict(), close()等
3.4. Transaction
代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
l 代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
Transaction tx = session.beginTransaction();
l 常用方法:
commit():提交相关联的session实例
rollback():撤销事务操作
wasCommitted():检查事务是否提交
3.5. Query和Criteria接口
都是查询接口,Query实例包装了HQL查询语句,hql是面向对象的,他引用类名及类的属性名,而不是表名和字段名。Criteria接口完全封装了基于字符串形式的查询语句,比Query接口更面向对象,他擅长执行动态查询。
3.6. Hibernate的运行过程
Hibernate的运行过程如下:
1、应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,
2、并用这些信息生成一个SessionFactory对象,
3、然后从SessionFactory对象生成一个Session对象,
4、并用Session对象生成Transaction对象;
A、可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载、保存、更新、删除、等操作;
B、在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;如果没有异常,Transaction对象将提交这些操作到数据库中。
4. 对象/关系数据库映射(一)基础
4.1. 持久化对象与OID
4.1.1. 对持久化对象的要求
1. 提供一个无参的构造器。使Hibernate可以使用Constructor.newInstance() 来实例化持久化类。
2. 提供一个标识属性(identifier property)。通常映射为数据库表的主键字段。如果没有该属性,一些功能将不起作用,如:Session.saveOrUpdate()。
3. 为持久化类的字段声明访问方法(get/set)。Hibernate对JavaBeans风格的属性实行持久化。
4. 使用非final类。在运行时生成代理是Hibernate的一个重要的功能。如果持久化类没有实现任何接口,Hibnernate 使用 CGLIB 生成代理。如果使用的是 final 类,则无法生成CGLIB代理。
5. 重写eqauls()和hashCode()方法。如果需要把持久化类的实例放到Set中(当需要进行关联映射时),则应该重写这两个方法。
4.1.2. OID
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier-OID)。
4.2. 对象/关系数据库映射文件(hbm.xml)
Hibernate 采用 XML 格式的文件来指定对象和关系数据之间的映射。在运行时 Hibernate 将根据这个映射文件来生成各种SQL语句
映射文件的后缀为“.hbm.xml”。
映射文件示例:
4.3. 持久化类的属性及属性映射(普通属性)
映射配置示例
<property name=”username” type=”string” length=”64”/>
4.3.1. 持久化类的属性及访问方法
Hibernate中持久化类的访问者有两个:
1, Java应用程序
2, hibernate(何时调用get、set方法?如下图所示)
4.3.2. 使用基本数据类型和包装类型的区别
基本数据类型和包装类型对应的hibernate映射类型相同(映射是一样的),例:
4.3.3. Hibernate访问持久化类属性的策略
propertype (默认值):
表明hibernate通过getXXX和setXXX来访问类属性。推荐使用。提高域模型透明性。
field
hibernate通过java反射机制直接访问类属性。对于没有get与set方法的属性可设置该访问策略。
noop
它映射Java持久化类中不存在的属性,即主要用于HQL(用query接口测试,使用hql语句)中,当数据库中有某列,而实体中不存在的情况。
示例:
在持久化类的方法中加入程序逻辑
在Customer.hbm.xml文件中无需映射firstname和lastname属性,而是映射name属性
4.3.4. 设置派生属性(formula)
利用<property>元素的formula属性,用来设置一个sql表达式,hibernate将根据它来计算出派生属性的值。
如果指定了formula 属性,则就会insert=”false” update=”false”
4.3.5. 控制insert、update语句
映射属性 | 作用 |
<property>的 insert属性 | 若为false,在insert语句中不包含该字段,该字段永远不能被插入。默认值true。 |
<property>的 update属性 | 若为false,update语句不包含该字段,该字段永远不能被更新。默认值为true。 |
<class>的 mutable属性 | 若为false,等价于所有的<property>元素的update属性为false,整个实例不能被更新。默认为true。 |
<class>的 dynamic-insert属性 | 若为true,等价于所有的<property>元素的insert为true,保存一个对象时,动态生成insert语句,语句中仅包含取值不为null的字段。默认false。 |
<class>的 dynamic-update属性 | 若为true,等价于所有的<property>元素的update为true,更新一个对象时,动态生成update语句,语句中仅包含取值不为null的字段。默认false。 |
4.3.6. 处理sql引用表示符
在SQL语法中,表示符是指用于为数据库表、视图、字段或索引等名字的字符串,常规表示符不包括空格,也不包含特殊字符,因此无需使用引用符号。如果数据库表名或列名包含特殊字符,可以使用引用表示符(键盘~下面的字符)。
4.3.7. 设置类的包名
如果在一个映射文件中包含多个类,并且这些类位于同一个包中,可以设置<hibernate-mapping>元素的package属性,以避免为每个类提供完整的类名。
指定类的全限定名
在<hibernate-mapping>元素的指定package,就只需指定简单类名了
4.4. 映射对象标识符(OID,对应数据库主键)
4.4.1. OID,唯一性的标志
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier-OID)。
关系数据库用主键区分是否是同一条记录。
Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系。对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate来为OID赋值。
主键必备条件:
1,不能为null。
2,唯一,不能重复。
3,永远不会改变。
4.4.2. 自然主键和代理主键
业务主键(自然主键):
在数据库表中把具有业务逻辑含义的字段作为主键,称为“自然主键(Natural Key)”。
逻辑主键(代理主键):
在数据库表中采用一个与当前表中逻辑信息无关的字段作为其主键,称为“代理主键”。
复合主键(联合主键):
通过两个或者多个字段的组合作为主键。
自然主键:把具有业务含义的字段作为主键叫做自然主键。
代理主键:不具备业务含义的字段,该字段一般取名为“id”。(推荐)
4.4.3. 数据库中的主键介绍
关系数据库按主键区分不同记录
把主键定义为自动增长类型
在my SQL中,把字段设为auto_increment类型,数据库会自动为主键赋值。
在ms SQL server中,把字段设为identity类型,数据库会自动为主键赋值。
oracle从序列(sequence)中获取自动增长的描述符
create sequence seq_customer increment by 2 start with 1
insert into customers values(seq_customer.curval,’..’)
4.4.4. java与Hibernate如何区分对象
Java语言按内存地址(==)或equals()方法区分不同的对象
Hibernate中用对象表示符(OID)来区分对象
OID是关系数据库中的主键在java对象模型中的等价物。在运行时,hibernate根据OID来维持java对象和数据库记录的对应关系。
Hibernate使用OID来区分对象,不是equals()方法!所以不重写持久化类的hashCode()与equals()方法Hibernate也可以正确运行(但要放到HashSet等集合中时要注意需要重写这两个方法)。
4.4.5. ID和 generator元素配置说明
配置示例:
<id name=“id” type=“long” column=“ID”>
<generator class=“increment” />
</id>
<id>元素说明:
设定持久化类的 OID 和表的主键的映射,可以有以下属性:
¨ name: 标识持久化类 OID 的属性名
¨ column: 设置标识属性所映射的数据列的列名(主键字段的名字).
¨ unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象,在Hibernate3中几乎不再需要.
¨ type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型
¨ Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型. 基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型
<generator>元素说明
设定持久化类设定标识符生成器,可以有一个class属性:
¨ class: 指定使用的标识符生成器全限定类名或其缩写名。
<generator>元素的class属性可以指定的值说明(主键生成策略)
主键生成器 | 描述 |
increment | 适用于代理主键。由hibernate自动以递增的方式生成标识符,每次增量为1。 |
identity | 适用于代理主键。由底层数据库生成表识符。条件是数据库支持自动增长数据类型。 |
sequence | 适用于代理主键。Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。 |
hilo | 适用于代理主键。Hibernate根据hign/low算法生成标识符。Hibernate把特定表的字段作为“hign”值。默认情况下,采用hibernate_unique_key表的next_hi字段。 |
native | 适用于代理主键。根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo |
uuid | 适用于代理主键。Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。 |
assigned | 适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为private的。尽量避免使用自然主键。 |
4.4.5.1. increment
l increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
l Hibernate 会先读取表中的主键的最大值,向表中插入记录时, 就在 max(id) 的基础上递增,增量为1。
l 适用范围:
1, 由于 increment生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
2, 适用于只有单个 Hibernate 应用进程访问同一个数据库的场合,在多线程情况下会有问题。
3, OID必须为long、int或 short 类型,如果把OID定义为byte类型,在运行时会抛出异常
4.4.5.2. identity
l identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型。
l 适用范围:
1, 由于 identity 生成标识符的机制依赖于底层数据库系统,因此,要求底层数据库系统必须支持自动增长字段类型。支持自动增长字段类型的数据库包括:DB2、 Mysql、MSSQLServer、Sybase等。
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。
4.4.5.3. sequence
l sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
l Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
l 适用范围:
1, 由于 sequence 生成标识符的机制依赖于底层数据库系统的序列,因此,要求底层数据库系统必须支持序列。支持序列的数据库包括:DB2 Oracle 等。
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。
4.4.5.4. hilo
l hilo 标识符生成器由 Hibernate 按照一种 high/low 算法*生成标识符, 它从数据库的特定表的字段中获取 high 值.
l Hibernate 在持久化一个 News 对象时, 由 Hibernate 负责生成主键值. hilo 标识符生成器在生成标识符时, 需要读取并修改 HI_TABLE 表中的 NEXT_VALUE 值.
l 适用范围:
1, 由于 hilo 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
4.4.5.5. native
l native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
l 适用范围:
1, 由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
4.4.5.6. uuid
l 适用范围:
1, 由于 hilo 生成标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
2, OID 必须为String类型
4.4.5.7. assigned
assigned表示手工指定主键的值。
4.4.5.8. 映射复合主键
4.4.5.8.1. 方法一
4.4.5.8.2. 方法二
4.5. Hibernate映射类型
4.5.1. 内置映射类型
内置映射类型
Hibernate | java | sql | 取值范围 |
int或integer | int 或java.lang.Integer | INTEGER | 4 |
long | Long或java.lang.Long | BIGINT | 8 |
short | short Short | SMALLINT | 2 |
byte | byte Byte | TINYINT | 1 |
float | float Float | FLOAT | 4 |
double | double Double | DOUBLE | 8 |
big_decimal | java.math.BigDecinimal | NUMERIC | 8位含2位小数部分 |
character | char Character String | CHAR(1) | 定长字符 |
string | java.lang.String | VARCHAR | 变长串 |
boolean | boolean或Boolean | BIT | 布尔 |
yes_no | boolean或Boolean | CHAR(1) | 布尔 |
true_false | boolean或Boolean | CHAR(1) | 布尔 |
4.5.2. java时间和日期类性
Hibernate | java | sql | 取值范围 |
date | util.Date sql.Date | DATE | YYYY-MM-DD |
time | util.Date sql.Time | TIME | HH:mm:ss |
timestamp | util.Date sql.timestamp | TIMESTAMP | YYYYMMDDHHMMSS |
calendar | java.util.Calendar | TIMESTAMP | YYYYMMDDHHMMSS |
calendar_date | java.util.Calendar | DATE | YYYY-MM-DD |
4.5.3. 大对象类型的映射
Hibernate | java | sql |
binary | byte[] | VARBINARY(BLOB) |
text | String | CLOB |
serializable | 实现类 | BARBINARY(BLOB) |
clob | sql.Clob | CLOB |
blob | sql.Blob | BLOB |
不允许以上类型来定义OID
(Hibernate Reference3.2.2中有)JDBC 类 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blob 和 clob 对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)
4.5.4. JDK自带的个别java类的映射类型
Hibernate | java | sql |
class | java.lang.Class | VARCHAR |
locale | java.util.Locale | VARCHAR |
timezone | java.util.TimeZone | VARCHAR |
currency | java.util.Currency | VARCHAR |
5. 对象/关系数据库映射(二)
5.1. 集合映射
集合属性大致有两种:
¨ 单纯的集合属性,如像List、Set或数组等集合属性
¨ Map结构的集合属性,每个属性值都有对应的Key映射
集合映射的元素大致有如下几种:
¨ list: 用于映射List集合属性
¨ set: 用于映射Set集合属性
¨ map: 用于映射Map集合性
¨ array: 用于映射数组集合属性
¨ bag: 用于映射无序集合
¨ idbag: 用于映射无序集合,但为集合增加逻辑次序
5.1.0.1. Set
但Set是无序,不可重复的集合。
测试程序
生成的表及插入的数据
注:映射 Set 集合属性时,如果 element 元素包括 not-null = “true” 属性,则集合属性表以关联持久化类的外键和元素列作为联合主键,否则该表没有主键。但 List 集合属性不会,List 集合属性总是以外键列和元素此序列作为联合主键。
5.1.0.2. List
¨ List是有序集合,因此持久化到数据库时必须增加一列来表示集合元素的次序。
¨ 集合属性只能以接口声明(当持久化某个实例时, Hibernate 会自动把程序中的集合实现类替换成 Hibernate 自己的集合实现类),因此下面代码中,schools的类型能是List,不能是ArrayList,
映射说明
¨ list元素要求用list-index的子元素来映射有序集合的次序列。
¨ 集合的属性的值会存放有另外的表中,须以外键关联,用 Key 元素来映射外键列。
¨ 当集合元素是基本数据类型及其包装类, 字符串或日期类型时使用 element 来映射集合属性
测试程序:
生成的表及插入的数据
5.1.0.3. 数组
¨ 数组属性的映射和 List 的处理方式基本一致
¨ 数组使用 <array> 元素完成完成映射
5.1.0.4. Bag
Bag的特性是:允许重复的元素,但无序。在Java的标准API中并没有提供Bag容器,Hibernate提供自己的Bag实现,允许您将List映射为Bag。
¨ bag元素既可以为List集合属性映射,也可以为Collection集合属性映射。
¨ 不管是哪种集合属性,使用bag元素都将被映射成无序集合,而集合属性对应的表没有主键。
¨ Bag 元素只需要 key 元素来映射外键列,使用 element 元素来映射集合属性的每个元素。
5.1.0.5. Map
¨ Map不仅需要映射属性值,还需要映射属性Key。
¨ Hibnernate 将以外键列和Key列作为联合主键。
¨ Map集合属性使用map元素映射,该元素需要key和map-key两个子元素
• key子元素用于映射外键列,
• map-key子元素则用于映射Map集合的Key。
• map-key和element元素都必须确定type属性
5.2. 组成关系(Component)映射
组件属性的意思是持久化类的属性既不是基本数据类型,也不是 String 字符串,而是某个组件变量,该组件属性的类型可以是自定义类。
¨ 显然无法直接用 property 映射 name 属性。为了映射组件属性, Hibernate 提供了 component 元素。
¨ 每个 component 元素映射一个组件属性,组件属性必须指定该属性的类型,component 元素中的 class 属性用于确定组件的类型。
示例:
生成的表及插入的数据:
集合除了存放 String 字符串以外,还可以存放组件类型。实际上,更多情况下,集合组件存放的都是组件类型。
¨ 对于有集合属性持久化类, 需要使用 set, list, bag 等集合元素来映射集合属性。
¨ 如果集合里的元素是普通字符串,则使用 element 映射集合元素即可。
¨ 如果集合元素也是定义类,则需使用 composite-element 子元素来映射集合元素。
¨ composite-element 元素映射一个组件类型,因此需要 class 元素确定元素的类型,该元素还支持 property 的子元素来定义组件类型的子属性
5.3. 一对多关联关系映射
1, 单项关联:仅仅建立从Order到Customer的多对一关联,即仅仅在Order类中定义customer属性。或者仅仅建立从Customer到Order的一对多关联,即仅仅在Customer类中定义orders集合。
2, 双项关联:既建立从Order到Customer的多对一关联,又建立从Customer到Order的一对多关联
5.3.1. 单向多对一
单向 n-1 关联只需从 n 的一端可以访问 1 的一端。
5.3.1.1. 域模型
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
5.3.1.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.1.3. 代码
代码(持久化类):
代码(映射文件):
Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
测试代码:
1, 保存新数据,并有关联关系。
2, 不能关联Transient状态对象。
3, 获取数据,可以从一端获取到另一端,但从另一端获取不到这一端。
4, 解除关联关系。
5.3.1.4. 级联(cascade)
当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,而是会抛出TransientObjectException.如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
级联指的是当主控方执行操作时,关联对象(被动方)是否同步执行同一操作。
级联风格
每个 Hibernate session 的基本操作 — 包括 persist(), merge(), saveOrUpdate(), delete(),lock(), refresh(), evict(), replicate() — 都有对应的级联风格(cascade style)。这些级联风格(cascade style)风格分别命名为persist, merge, save-update, delete, lock, refresh, evict, replicate。
级联风格 | Session中的方法 |
persist | persist() |
merge | merge() |
save-update | save()、update()、saveOrUpdate() |
delete | delete() |
lock | lock() |
refresh | refresh() |
evict | evict() |
replicate | replicate() |
如果你希望一个操作被顺着关联关系级联传播,你必须在映射文件中指出这一点。
指定级联风格:
<one-to-one name="person" cascade="persist"/>
级联风格(cascade style)是可组合的:
<one-to-one name="person" cascade="persist,delete"/>
你可以使用 cascade="all" 来指定全部操作都顺着关联关系级联(cascaded)。默认值是cascade="none",即任何操作都不会被级联(cascaded)。
delete-orphan
A special cascade style, delete-orphan, applies only to one-to-many associations, and indicates that the delete() operation should be applied to any child object that is removed from the association.
在对象–关系映射文件中, 用于映射持久化类之间关联关系的元素, 如<set>, <many-to-one> 和 <one-to-one> 都有一个 cascade 属性。
5.3.2. 单向一对多
单向 1–n 关联只需从 1 的一端可以访问 n 的一端。
5.3.2.1. 域模型:
5.3.2.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.2.3. 代码
代码(持久化类):
代码(映射文件):
Hibernate使用set元素来映射一对多关联关系
测试代码:
1, 保存新数据,并有关联关系。
2, 设置cascade并测试。
3, 获取数据,可以从一端获取到另一端,但从另一端获取不到这一端。
4, 解除关联关系。
5.3.2.4. inverse属性
¨ 在hibernate中通过对 inverse 属性的值决定是由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系
¨ 在没有设置 inverse=true 的情况下,父子两边都维护父子关系
¨ 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
¨ 在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句。
¨ 只有集合标记(set/map/list/array/bag)才有inverse属性
¨ 在hibernate中通过对 inverse 属性的设置来决定是由谁来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
¨ 在没有设置 inverse=true 的情况下,父子两边都维护父子关系
¨ 在 1-N 关系中,将 many 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
¨ 在 1-N 关系中,若将 1 方设为主控方会额外多出 update 语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
5.3.2.5. order-by属性
¨ <set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序。(注意,指定的是SQL语句的OrderBy子句,所以要写列名)
¨ order-by 属性中还可以加入 SQL 函数
5.3.3. 一对多/多对一双向关联
¨ 双向 1-n 与 双向 n-1 是完全相同的两种情形
¨ 双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
5.3.3.1. 域模型
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
5.3.3.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.3.3. 代码
持久化类:双方都有相应的属性。
映射文件:双方都做相应的映射。
5.4. 父子关系
所谓父子关系:
是指父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联
解除关联关系 ---父子关系
5.5. 多对多关联关系映射
多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系型数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。
5.5.1. 双向多对多
¨ 双向 n-n 关联需要两端都使用集合属性
¨ 双向n-n关联必须使用中间表
¨ 集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类
¨ 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。
¨ set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,
¨ 其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,
¨ 因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.
¨ 对于双向 n-n 关联, 须把其中一端的 inverse 设置为 true, 否则可能会造成主键冲突.
映射Student端
映射Course端
测试代码
• 关联并保存对象
• 解除关联关系
维护关联关系是指在中间表中插入或删除数据。
5.5.2. 单向多对多
去掉双向多对多关联中的任一方,即是单向多对多。
5.6. 一对一关联关系映射
¨ 一对一关联指两个表之间的记录是一一对应的关系。分为两种:外键关联和主键关联。
¨ 比如一家公司(Company)和它所在的地址(Address)。在业务逻辑中要求一家公司只有唯一的地址,一个地址也只有一家公司。
5.6.1. 基于外键的双向1-1关联
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加 many-to-one 元素。为 many-to-one元素增加 unique=“true” 属性来表示为1-1关联,并用name属性来指定关联属性的属性名
另一端需要使用one-to-one元素,该元素使用 property-ref(可以不加) 属性指定使用被关联实体主键以外的字段作为关联字段
有外键方(Company)配置
无外键方(Address)配置
测试代码:
1, 关联并保存对象
2, 测试唯一性
3, 取消关联关系
5.6.2. 基于主键的双向1-1关联
一对一的另一种解决方式就是主键关联,在这种关联关系中,要求两个对象的主键必须保持一致,通过两个表的主键建立关联关系须外键参与。
基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的哪个属性作为 “对方”
采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其 one-to-one 属性还应增加 constrained=“true” 属性;另一端(company)增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
无外键方(Company)配置
有外键方(Address)配置
5.6.3. 单向一对一映射
5.6.3.1. 基于外键的单向 1-1
¨ 单向 1-1,POJO 与 N-1 没有丝毫区别。
¨ 基于外键的单向 1-1 映射文件:只需要在原有的 many-to-one 元素添加 unique=“true”,用以表示 N 的一端必须唯一即可,N的一端增加了唯一约束, 即成为单向 1-1.
5.7. 映射继承关系
Hibernate支持三种继承映射策略:
¨ 每个具体类一张表(table per concrete class) 将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
¨ 每个类分层结构一张表(table per class hierarchy) 对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
¨ 每个子类一张表(table per subclass) 域模型中的每个类映射到一个表,通过关系数据模型中的外键来描述表之间的继承关系。这也就相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。
5.7.1. 采用 subclass 元素的继承映射
父类和子类使用同一张表
¨ 采用 subclass 的继承映射可以实现对于继承关系中父类和子类使用同一张表
¨ 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是哪个类的实例----这个列被称为辨别者列(discriminator).
¨ 在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
¨ 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中
5.7.2. 采用 joined-subclass 元素的继承映射
关系树的每个类对应一个表
¨ 采用 joined-subclass 元素的继承映射可以实现每个子类一张表
¨ 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
¨ 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键,该主键必须与父类标识属性的列名相同。但如果继承树的深度很深,可能查询一个子类实例时,需要跨越多个表,因为子类的数据一次保存在多个父类中。
¨ 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中
5.7.3. 采用 union-subclass 元素的继承映射
关系树的每个类存放在各自独立的表中,没有主表和子表的概念,每个 表都有全部的自身信息,所有表和在一起后,记录仍然是唯一的。
¨ 采用 union-subclass 元素可以实现将每一个实体对象映射到一个独立的表中。
¨ 子类增加的属性可以有非空约束 --- 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
¨ 子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录
¨ 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和
¨ 在这种映射策略下,既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键.
¨ 使用 union-subclass 映射策略是不可使用 identity 的主键生成策略, 因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的. 受此影响, 也不该使用 native 主键生成策略, 因为 native 会根据数据库来选择使用 identity 或 sequence.
5.7.4. 三种继承映射方式的比较
6. Hibernate配置文件(.cfg.xml)的常用属性
6.1. JDBC 连接属性
¨ connection.url:数据库URL
¨ connection.username:数据库用户名
¨ connection.password:数据库用户密码
¨ connection.driver_class:数据库JDBC驱动
¨ dialect:配置数据库的方言,根据底层的数据库不同产生不同的sql语句,Hibernate 会针对数据库的特性在访问时进行优化
6.2. C3P0 数据库连接池属性
¨ hibernate.connection.provider_class: 该类用来向 Hibernate 提供JDBC连接
¨ hibernate.c3p0.max_size: 数据库连接池的最大连接数
¨ hibernate.c3p0.min_size: 数据库连接池的最小连接数
¨ hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
¨ hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
¨ hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
¨ hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
¨ hibernate.c3p0.validate: 是否每次连接都验证连接是否可用
6.3. 其他
¨ show_sql:是否将运行期生成的SQL输出到日志以供调试。取值 true | false
¨ format_sql:是否将 SQL 转化为格式良好的 SQL . 取值 true | false
¨ use_sql_comments:是否在 Hibernate 生成的 SQL 语句中添加有助于调试的注释
¨ hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式。取值 create | update | create-drop | validate
¨ jdbc.fetch_size
¨ hibernate.jdbc.batch_size
6.4. jdbc.fetch_size 和 jdbc.batch_size说明
jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会 1 次性把1万条取出来的,而只会取出Fetch Size条数,当纪录集遍历完了这些记录以后,再去数据库取Fetch Size条数据。因此大大节省了无谓的内存消耗。Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。Oracle数据库的JDBC驱动默认的Fetch Size=10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当Fetch Size=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持。MySQL就像上面那种最坏的情况,总是一下就把1万条记录完全取出来,内存消耗会非常惊人!
hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。Batch Size越大,批量操作的向数据库发送sql的次数越少,速度就越快。测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!Oracle数据库 Batch Size = 30 的时候比较合适。
7. 操纵持久化对象(Session)
7.1. 在hibernate中java对象的状态
Hibernate 把对象分为 4 种状态:
¨ 临时状态,
¨ 持久化状态,
¨ 游离状态,
¨ 删除状态.
Session 的特定方法能使对象从一个状态转换到另一个状态
7.1.1. 临时对象(transient)
¨ 在使用代理主键的情况下, OID 通常为 null
¨ 不处于 Session 的缓存中
¨ 在数据库中没有对应的记录
7.1.2. 持久化对象(也叫”托管”)(Persist)
¨ OID 不为 null
¨ 位于 Session 缓存中
¨ 持久化对象和数据库中的相关记录对应
¨ Session 在清理缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
¨ 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
有主键值,数据库会做相应改变。
7.1.3. 游离对象(也叫”脱管”)(Detached)
¨ OID 不为 null
¨ 不再处于 Session 的缓存中
¨ 一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
7.1.4. 删除对象(Removed)
¨ OID 不为 null
¨ 从一个 Session实例的缓存中删除
¨ Session 已经计划将其从数据库删除, Session 在清理缓存时, 会执行 SQL delete 语句, 删除数据库中的对应记录
¨ 一般情况下, 应用程序不该再使用被删除的对象
7.1.5. 对象的状态转换说明(图)
对象的状态转换图
测试hibernate中java对象的状态
程序代码 | 生命周期 | 状态 |
tx = session.beginTransaction(); Customer c = new Customer); | 开始生命周期 | 临时状态 |
Session.save(c) | 处于生命周期中 | 转变为持久化状态 |
Long id=c.getId(); c = null; Customer c2 = (Customer)session.load(Customer.class,id); tx.commit(); | 处于生命周期中 | 处于持久化状态 |
session.close(); | 处于生命周期中 | 转变为游离态 |
c2.getName(); | 处于生命周期中 | 处于游离态 |
c2 = null; | 结束生命周期 | 结束生命周期 |
7.1.6. 对象的状态总结
| Session缓存存在对应的记录 | 数据中存在对应的记录 |
临时态 | no | no |
持久态 | yes | 可能有也可能没有,但最终会有 |
游离态 | no | 可能有(数据没有删除)也可能没有 |
7.2. session的缓存
7.2.1. Session缓存的说明
Session 接口是 Hibernate向应用程序提供的操纵对数据库的最主要的接口,它提供了基本的保存,更新,删除和加载Java 对象的方法。
在 Session 接口的实现中包含一系列的 Java 集合,这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期(因为还有这些集合引用对象)。
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,有则返回,没有再查询数据库。
7.2.2. 清理session的缓存的相关方法
Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为清理缓存(flush)。
默认情况下 Session 在以下时间点清理缓存:
¨ 当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),然后在向数据库提交事务(tx.commit())
¨ 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,以保证查询结果能够反映持久化对象的最新状态
¨ 显式调用 Session 的 flush() 方法.
Session.flush()
进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步执行一些列sql语句,但不提交事务。
Transaction.commit()
先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
Session.reresh()
刷新缓存,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态。
Session.clear()
清空缓存(等价于list.removeAll())
7.2.3. 设置缓存的清理模式
清理缓存的模式 | Session的查询方法 | tx的commit() | Session的flush() |
FlushMode.AUTO(默认) | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FLushMode. MANUAL | 不清理 | 不清理 | 清理 |
7.2.4. 大批量操作(如插入10万记录)
错误代码:
这段程序会失败并抛出内存溢出异常(OutOfMemoryException)。这是因为 Hibernate 把所有新插入的客户(Customer)实例在 session 级别的缓存区进行了缓存的缘故。
正确做法:
7.3. 操纵持久化对象的方法(Session中)
7.3.1. save()
Session 的 save() 方法使一个临时对象转变为持久化对象。
¨ Session 的 save() 方法完成以下操作:
• 把 News 对象加入到 Session 缓存中, 使它进入持久化状态
• 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
• 计划执行一条 insert 语句,把Customer对象当前的属性值组装到insert语句中
¨ Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
7.3.2. update()
Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句。
若希望 Session 仅当修改了 News 对象的属性时, 才执行 update() 语句, 可以把映射文件中 <class> 元素的 select-before-update(更新之前先查询) 设为 true. 该属性的默认值为 false
当 update() 方法关联一个游离对象时, 如果在 Session 的缓存中已经存在相同 OID 的持久化对象, 会抛出异常
原因是:两个不同的对象拥有相同的OID
当 update() 方法关联一个游离对象时, 如果在数据库中不存在相应的记录, 也会抛出异常
7.3.3. saveOrUpdate()
该方法同时包含save和update方法,如果参数是临时对象就用save方法,如果是游离对象就用update方法,如果是持久化对象就直接返回。
判定对象为临时对象的标准
¨ Java 对象的 OID 为 null
¨ 映射文件中为 <id> 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配
如果参数是临时对象就会用save方法
如果是游离对象就用update方法
如果是持久化对象就直接返回,不执行操作
判定对象为临时对象的标准
¨ Java 对象的 OID 为 null
¨ 映射文件中为 <id> 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配,执行插入操作
根据以上判断临时对象的标准id=null是临时对象。但可以定义属性为int id
* 此时id默认值是0而不是null,应该执行更新操作
* 但实际我们要执行的插入操作。这时,可以在id中设置unsaved-value=0(默认值)
7.3.4. get()、load()
都可以根据给定的 OID 从数据库中加载一个持久化对象
区别:
¨ 当数据库中不存在与 OID 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null
¨ 两者采用不同的延迟检索策略
7.3.5. delete()
Session 的 delete() 方法既可以删除一个游离对象, 也可以删除一个持久化对象。
¨ 如果参数是持久化对象,就执行一个delete语句,若为游离对象,先使游离对象被session关联,使他变为持久化对象
¨ 计划执行一条 delete 语句
¨ 把对象从 Session 缓存中删除, 该对象进入删除状态.
7.3.6. 与触发器协同工作
Session.save(c);
Session.flush();
Session.refresh(c);
------------------------------------------------------
触发器的行为导致缓存与数据库中的数据不一致。解决办法是执行完
操作后,立即调用session的flush方法和refresh方法,迫使缓存与
数据库同步。
Session的update操作方法盲目的激活触发器
如果游离状态的对象的属性和数据库一致,则更新操作是多余的。
为避免这种情况:
<class name=“” table=“” select-before-update=“true”>
……
</class>
1. Java对象持久化概述
1.1. 应用程序的分层体系结构
1.1.1. 基于B/S的典型三层架构
说明:
1, 展现层:提供与用户交互的界面。
2, 业务逻辑层:实现各种业务逻辑。
3, 数据访问层:负责存放和管理应用程序的持久化业务数据。
1.1.2. 数据访问层与Hibernate在Java应用程序中的角色
数据访问层(持久化层)封装了数据访问的细节,为业务逻辑层提供了面向对象的API。完善的持久化层应该达到的目标:
1, 代码重用性高,可完成所有的数据访问操作。
2, 如果需要的话,能够支持多种数据库平台。
3, 具有相对独立性,当持久化层变化时,不会影响上层实现。
在数据访问层(持久化层)中可以使用Hibernate框架以实现要求,如下图所示:
1.2. 软件模型
1.2.1. 各种模型的说明
概念模型:
模拟问题域中的真实实体。描述每个实体的概念和属性及实体间关系。不描述实体行为。实体间的关系有一对一、一对多和多对多。。
关系数据模型:
在概念模型的基础上建立起来的,用于描述这些关系数据的静态结构。有以下内容组成:
1, 若干表
2, 表的所有索引
3, 视图
4, 触发器
5, 表与表之间的参照完整性
域模型:
在软件的分析阶段创建概念模型,在软件设计阶段创建域模型。
组成部分:
1, 具有状态和行为的域对象。
2, 域对象之间的关联。
域对象(domain object):
构成域模型的基本元素就是域对象。对真实世界的实体的软件抽象,也叫做业务对象(Business Object,BO)。域对象可代表业务领域中的人、地点、事物或概念。
域对象分为以下几种:
1, 实体域对象:通常是指业务领域中的名词。(plain old java object,简单Java对象)。
2, 过程域对象:应用中的业务逻辑或流程。依赖于实体域对象,业务领域中的动词。如发出订单、登陆等。
3, 事件域对象:应用中的一些事件(警告、异常)。
1.2.2. 域对象间的关系
关联:
类间的引用关系。以属性定义的方式表现。
依赖:
类之间访问关系。无需定义成属性。在A中访问B中的方法或属性,或者A负责实例化B。
聚集(Aggregation):
整体与部分的关系。例人与手的关系。部分类的对象不能单独存在,他的生命周期依赖于整体类的对象的生命周期,整体消失时,部分也随之消失。
一般化(Generalization):
类之间继承关系。
1.2.3. 域对象(Domain)的持久化概念
实体域对象在内存中创建后,不能永久存在。将实体域对象永久保存起来,就是持久化的过程。通常只有实体域对象需要持久化,过程域对象和事件域对象一般不需要持久化。广义持久化指增、删、改、查。
1.3. ORM与ORM框架
1.3.1. ORM(Object/Relation Mapping)
对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。
ORM 主要解决对象-关系的映射
面向对象概念 | 面向关系概念 |
类 | 表 |
对象 | 表的行(记录) |
属性 | 表的列(字段) |
ORM的实现思想:
将关系数据库中表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。因此ORM的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
ORM 采用元数据来描述对象-关系映射细节
元数据通常采用 XML 格式,并且存放在专门的对象-关系映射文件中。
ORM中间件的使用方法
采用元数据来描述对象-关系映射细节,元数据通常采用xml格式,并存放在专门的对象-关系映射文件中。只要配置了持久化类与表的映射关系,orm中间件在运行时就能够参照映射文件的信息,把域对象持久化到数据库中。例(Hibernate):
1.3.2. 流行的ORM框架
Hibernate:
非常优秀、成熟的 ORM 框架。提供强大的对象和关系数据库映射以及查询功能。Hibernate是面向对象的程序设计语言和关系型数据库之间的桥梁,允许开发者采用面向对象的方式来操作关系数据库。
Hibernate 的目标是释放开发者通常的与数据库持久化相关的编程任务的 95%。消除那些针对特定数据库厂商的SQL代码。
Ibatis:
相比 Hibernate 灵活高,运行速度快。开发速度慢,不支持纯粹的面向对象操作,需熟悉sql语句,并且熟练使用sql语句优化功能。
TopLink
OJB
1.3.3. Hibernate 与 Jdbc 代码对比
1.4. 小结
2. Hibernate入门(HelloWorld)
2.1. Hibernate介绍
Hibernate是一个基于jdbc的开源的持久化框架,是一个优秀的ORM实现,它很大程度的简化了dao层编码工作。Hibernate对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
在分层结构中处于持久化层,封装对数据库的访问细节,使业务逻辑层更专注于实现业务逻辑。
Hibernate的主页为:http://www.hibernate.org/
Hibernate的下载地址:http://sourceforge.net/projects/hibernate/files/hibernate3/
HibernateTools的下载地址:http://sourceforge.net/projects/jboss/files/JBossTools/JBossTools3.1.x/HibernateTools-3.3.1.v201006011046R-H111-GA.zip/download
我们所学的版本是3.6.0
2.2. Hibernate的体系结构与开发步骤
Hibernate的体系结构
Hibernate开发步骤
2.3. 第一个Hibernate程序(HelloWorld)
2.3.1. 创建Eclipse工程并引入相关的jar包
新建Java工程,并添加如下jar包:
1,{hibernate_home}/ hibernate3.jar
2,{hibernate_home}/lib/required/*.jar
3,{hibernate_home}/lib/jpa/hibernate-jpa-2.0-api-1.0.0.Final.jar
4, 数据库对应的JDBC驱动(例如mysql-connector-java-5.1.5-bin.jar)
还可以加入日志相关的jar包(不加也可以):
1, log4j-1.2.15.jar
2, slf4j-log4j12-1.6.1.jar
2.3.2. 创建持久化对象:User
2.3.3. 创建对象-关系映射文件:User.hbm.xml
Customer类属性 | Java类型 | Hibernate类型 | Cutomers字段名 | Sql类型 |
Name | java.lang.String | string | NAME | Varchar(12) |
age | int | int | age | INT |
Sex | char | character | SEX | CHAR(1) |
Married | boolean | boolean | married | bit |
Des | java.lang.String | text | des | CLOB |
Pic | byte[] | binary | pic | BLOB |
Birthday | java.sql.Date | date | BIRTHDAY | DATE |
registeredTime | java.sql.TimeStamp | timestamp | REGISTERED_TIME | TIMESTAMP |
2.3.4. 创建 Hibernate 配置文件:hibernate.cfg.xml
创建表、JavaBean、写映射文件
2.3.5. 通过 Hibernate API 编写访问数据库的代码
编程步骤
1, 获取 Configuration 对象。
2, 获取 SessionFactory 对象。
3, 获取 Session,打开事务。
4, 用面向对象的方式操作数据库。
5, 关闭事务,关闭 Session。
增删改查功能的实现代码
3. Hibernate API简介
3.1. Configuration
Configuration 类负责管理 Hibernate 的配置信息
包括如下内容:1,Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等(对应 hibernate.cfg.xml 文件)。2,持久化类与数据表的映射关系(*.hbm.xml 文件)。
•
创建 Configuration 的两种方式
1, 属性文件(hibernate.properties)
Configuration cfg = new Configuration();
2, Xml文件(hibernate.cfg.xml)
a) 加载默认名称的配置文件(hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
b) 或加载指定名称的配置文件:
Configuration cfg = new Configuration()
.configure(“myhibernate.cfg.xml”);
3.2. SessionFactory
Configuration对象根据当前的配置信息生成 SessionFactory 对象。SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息(SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。同时,SessionFactory还负责维护Hibernate的二级缓存)。 相关代码如下:
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
1, SessionFactory是线程安全的。
2, 构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
3, SessionFactory是生成Session的工厂:
Session session = sessionFactory.openSession();
3.3. Session
Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,所有持久化对象必须在 session 的管理下才可以进行持久化操作。此对象的生命周期很短。Session 中有一个缓存,显式执行flush()方法之前,所有的持久层操作的数据都缓存在 session 对象处。(相当于 JDBC 中的 Connection)。一般一个业务操作使用一个Session。
l 持久化类与 Session 关联起来后就具有了持久化的能力。
l Session是线程不安全的,一般是一次业务操作使用一个Session。
l Session 类的一些方法:
取得持久化对象的方法: get() load()
持久化对象都得保存,更新和删除:save(),update(),saveOrUpdate(),delete()
开启事务: beginTransaction().
管理 Session 的方法:isOpen(),flush(), clear(), evict(), close()等
3.4. Transaction
代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
l 代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
Transaction tx = session.beginTransaction();
l 常用方法:
commit():提交相关联的session实例
rollback():撤销事务操作
wasCommitted():检查事务是否提交
3.5. Query和Criteria接口
都是查询接口,Query实例包装了HQL查询语句,hql是面向对象的,他引用类名及类的属性名,而不是表名和字段名。Criteria接口完全封装了基于字符串形式的查询语句,比Query接口更面向对象,他擅长执行动态查询。
3.6. Hibernate的运行过程
Hibernate的运行过程如下:
1、应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,
2、并用这些信息生成一个SessionFactory对象,
3、然后从SessionFactory对象生成一个Session对象,
4、并用Session对象生成Transaction对象;
A、可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载、保存、更新、删除、等操作;
B、在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;如果没有异常,Transaction对象将提交这些操作到数据库中。
4. 对象/关系数据库映射(一)基础
4.1. 持久化对象与OID
4.1.1. 对持久化对象的要求
1. 提供一个无参的构造器。使Hibernate可以使用Constructor.newInstance() 来实例化持久化类。
2. 提供一个标识属性(identifier property)。通常映射为数据库表的主键字段。如果没有该属性,一些功能将不起作用,如:Session.saveOrUpdate()。
3. 为持久化类的字段声明访问方法(get/set)。Hibernate对JavaBeans风格的属性实行持久化。
4. 使用非final类。在运行时生成代理是Hibernate的一个重要的功能。如果持久化类没有实现任何接口,Hibnernate 使用 CGLIB 生成代理。如果使用的是 final 类,则无法生成CGLIB代理。
5. 重写eqauls()和hashCode()方法。如果需要把持久化类的实例放到Set中(当需要进行关联映射时),则应该重写这两个方法。
4.1.2. OID
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier-OID)。
4.2. 对象/关系数据库映射文件(hbm.xml)
Hibernate 采用 XML 格式的文件来指定对象和关系数据之间的映射。在运行时 Hibernate 将根据这个映射文件来生成各种SQL语句
映射文件的后缀为“.hbm.xml”。
映射文件示例:
4.3. 持久化类的属性及属性映射(普通属性)
映射配置示例
<property name=”username” type=”string” length=”64”/>
4.3.1. 持久化类的属性及访问方法
Hibernate中持久化类的访问者有两个:
1, Java应用程序
2, hibernate(何时调用get、set方法?如下图所示)
4.3.2. 使用基本数据类型和包装类型的区别
基本数据类型和包装类型对应的hibernate映射类型相同(映射是一样的),例:
4.3.3. Hibernate访问持久化类属性的策略
propertype (默认值):
表明hibernate通过getXXX和setXXX来访问类属性。推荐使用。提高域模型透明性。
field
hibernate通过java反射机制直接访问类属性。对于没有get与set方法的属性可设置该访问策略。
noop
它映射Java持久化类中不存在的属性,即主要用于HQL(用query接口测试,使用hql语句)中,当数据库中有某列,而实体中不存在的情况。
示例:
在持久化类的方法中加入程序逻辑
在Customer.hbm.xml文件中无需映射firstname和lastname属性,而是映射name属性
4.3.4. 设置派生属性(formula)
利用<property>元素的formula属性,用来设置一个sql表达式,hibernate将根据它来计算出派生属性的值。
如果指定了formula 属性,则就会insert=”false” update=”false”
4.3.5. 控制insert、update语句
映射属性 | 作用 |
<property>的 insert属性 | 若为false,在insert语句中不包含该字段,该字段永远不能被插入。默认值true。 |
<property>的 update属性 | 若为false,update语句不包含该字段,该字段永远不能被更新。默认值为true。 |
<class>的 mutable属性 | 若为false,等价于所有的<property>元素的update属性为false,整个实例不能被更新。默认为true。 |
<class>的 dynamic-insert属性 | 若为true,等价于所有的<property>元素的insert为true,保存一个对象时,动态生成insert语句,语句中仅包含取值不为null的字段。默认false。 |
<class>的 dynamic-update属性 | 若为true,等价于所有的<property>元素的update为true,更新一个对象时,动态生成update语句,语句中仅包含取值不为null的字段。默认false。 |
4.3.6. 处理sql引用表示符
在SQL语法中,表示符是指用于为数据库表、视图、字段或索引等名字的字符串,常规表示符不包括空格,也不包含特殊字符,因此无需使用引用符号。如果数据库表名或列名包含特殊字符,可以使用引用表示符(键盘~下面的字符)。
4.3.7. 设置类的包名
如果在一个映射文件中包含多个类,并且这些类位于同一个包中,可以设置<hibernate-mapping>元素的package属性,以避免为每个类提供完整的类名。
指定类的全限定名
在<hibernate-mapping>元素的指定package,就只需指定简单类名了
4.4. 映射对象标识符(OID,对应数据库主键)
4.4.1. OID,唯一性的标志
为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier-OID)。
关系数据库用主键区分是否是同一条记录。
Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系。对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate来为OID赋值。
主键必备条件:
1,不能为null。
2,唯一,不能重复。
3,永远不会改变。
4.4.2. 自然主键和代理主键
业务主键(自然主键):
在数据库表中把具有业务逻辑含义的字段作为主键,称为“自然主键(Natural Key)”。
逻辑主键(代理主键):
在数据库表中采用一个与当前表中逻辑信息无关的字段作为其主键,称为“代理主键”。
复合主键(联合主键):
通过两个或者多个字段的组合作为主键。
自然主键:把具有业务含义的字段作为主键叫做自然主键。
代理主键:不具备业务含义的字段,该字段一般取名为“id”。(推荐)
4.4.3. 数据库中的主键介绍
关系数据库按主键区分不同记录
把主键定义为自动增长类型
在my SQL中,把字段设为auto_increment类型,数据库会自动为主键赋值。
在ms SQL server中,把字段设为identity类型,数据库会自动为主键赋值。
oracle从序列(sequence)中获取自动增长的描述符
create sequence seq_customer increment by 2 start with 1
insert into customers values(seq_customer.curval,’..’)
4.4.4. java与Hibernate如何区分对象
Java语言按内存地址(==)或equals()方法区分不同的对象
Hibernate中用对象表示符(OID)来区分对象
OID是关系数据库中的主键在java对象模型中的等价物。在运行时,hibernate根据OID来维持java对象和数据库记录的对应关系。
Hibernate使用OID来区分对象,不是equals()方法!所以不重写持久化类的hashCode()与equals()方法Hibernate也可以正确运行(但要放到HashSet等集合中时要注意需要重写这两个方法)。
4.4.5. ID和 generator元素配置说明
配置示例:
<id name=“id” type=“long” column=“ID”>
<generator class=“increment” />
</id>
<id>元素说明:
设定持久化类的 OID 和表的主键的映射,可以有以下属性:
¨ name: 标识持久化类 OID 的属性名
¨ column: 设置标识属性所映射的数据列的列名(主键字段的名字).
¨ unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象,在Hibernate3中几乎不再需要.
¨ type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型
¨ Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型. 基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型
<generator>元素说明
设定持久化类设定标识符生成器,可以有一个class属性:
¨ class: 指定使用的标识符生成器全限定类名或其缩写名。
<generator>元素的class属性可以指定的值说明(主键生成策略)
主键生成器 | 描述 |
increment | 适用于代理主键。由hibernate自动以递增的方式生成标识符,每次增量为1。 |
identity | 适用于代理主键。由底层数据库生成表识符。条件是数据库支持自动增长数据类型。 |
sequence | 适用于代理主键。Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。 |
hilo | 适用于代理主键。Hibernate根据hign/low算法生成标识符。Hibernate把特定表的字段作为“hign”值。默认情况下,采用hibernate_unique_key表的next_hi字段。 |
native | 适用于代理主键。根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo |
uuid | 适用于代理主键。Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。 |
assigned | 适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为private的。尽量避免使用自然主键。 |
4.4.5.1. increment
l increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
l Hibernate 会先读取表中的主键的最大值,向表中插入记录时, 就在 max(id) 的基础上递增,增量为1。
l 适用范围:
1, 由于 increment生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
2, 适用于只有单个 Hibernate 应用进程访问同一个数据库的场合,在多线程情况下会有问题。
3, OID必须为long、int或 short 类型,如果把OID定义为byte类型,在运行时会抛出异常
4.4.5.2. identity
l identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型。
l 适用范围:
1, 由于 identity 生成标识符的机制依赖于底层数据库系统,因此,要求底层数据库系统必须支持自动增长字段类型。支持自动增长字段类型的数据库包括:DB2、 Mysql、MSSQLServer、Sybase等。
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。
4.4.5.3. sequence
l sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
l Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
l 适用范围:
1, 由于 sequence 生成标识符的机制依赖于底层数据库系统的序列,因此,要求底层数据库系统必须支持序列。支持序列的数据库包括:DB2 Oracle 等。
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。
4.4.5.4. hilo
l hilo 标识符生成器由 Hibernate 按照一种 high/low 算法*生成标识符, 它从数据库的特定表的字段中获取 high 值.
l Hibernate 在持久化一个 News 对象时, 由 Hibernate 负责生成主键值. hilo 标识符生成器在生成标识符时, 需要读取并修改 HI_TABLE 表中的 NEXT_VALUE 值.
l 适用范围:
1, 由于 hilo 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
4.4.5.5. native
l native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
l 适用范围:
1, 由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
2, OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
4.4.5.6. uuid
l 适用范围:
1, 由于 hilo 生成标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
2, OID 必须为String类型
4.4.5.7. assigned
assigned表示手工指定主键的值。
4.4.5.8. 映射复合主键
4.4.5.8.1. 方法一
4.4.5.8.2. 方法二
4.5. Hibernate映射类型
4.5.1. 内置映射类型
内置映射类型
Hibernate | java | sql | 取值范围 |
int或integer | int 或java.lang.Integer | INTEGER | 4 |
long | Long或java.lang.Long | BIGINT | 8 |
short | short Short | SMALLINT | 2 |
byte | byte Byte | TINYINT | 1 |
float | float Float | FLOAT | 4 |
double | double Double | DOUBLE | 8 |
big_decimal | java.math.BigDecinimal | NUMERIC | 8位含2位小数部分 |
character | char Character String | CHAR(1) | 定长字符 |
string | java.lang.String | VARCHAR | 变长串 |
boolean | boolean或Boolean | BIT | 布尔 |
yes_no | boolean或Boolean | CHAR(1) | 布尔 |
true_false | boolean或Boolean | CHAR(1) | 布尔 |
4.5.2. java时间和日期类性
Hibernate | java | sql | 取值范围 |
date | util.Date sql.Date | DATE | YYYY-MM-DD |
time | util.Date sql.Time | TIME | HH:mm:ss |
timestamp | util.Date sql.timestamp | TIMESTAMP | YYYYMMDDHHMMSS |
calendar | java.util.Calendar | TIMESTAMP | YYYYMMDDHHMMSS |
calendar_date | java.util.Calendar | DATE | YYYY-MM-DD |
4.5.3. 大对象类型的映射
Hibernate | java | sql |
binary | byte[] | VARBINARY(BLOB) |
text | String | CLOB |
serializable | 实现类 | BARBINARY(BLOB) |
clob | sql.Clob | CLOB |
blob | sql.Blob | BLOB |
不允许以上类型来定义OID
(Hibernate Reference3.2.2中有)JDBC 类 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blob 和 clob 对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)
4.5.4. JDK自带的个别java类的映射类型
Hibernate | java | sql |
class | java.lang.Class | VARCHAR |
locale | java.util.Locale | VARCHAR |
timezone | java.util.TimeZone | VARCHAR |
currency | java.util.Currency | VARCHAR |
5. 对象/关系数据库映射(二)
5.1. 集合映射
集合属性大致有两种:
¨ 单纯的集合属性,如像List、Set或数组等集合属性
¨ Map结构的集合属性,每个属性值都有对应的Key映射
集合映射的元素大致有如下几种:
¨ list: 用于映射List集合属性
¨ set: 用于映射Set集合属性
¨ map: 用于映射Map集合性
¨ array: 用于映射数组集合属性
¨ bag: 用于映射无序集合
¨ idbag: 用于映射无序集合,但为集合增加逻辑次序
5.1.0.1. Set
但Set是无序,不可重复的集合。
测试程序
生成的表及插入的数据
注:映射 Set 集合属性时,如果 element 元素包括 not-null = “true” 属性,则集合属性表以关联持久化类的外键和元素列作为联合主键,否则该表没有主键。但 List 集合属性不会,List 集合属性总是以外键列和元素此序列作为联合主键。
5.1.0.2. List
¨ List是有序集合,因此持久化到数据库时必须增加一列来表示集合元素的次序。
¨ 集合属性只能以接口声明(当持久化某个实例时, Hibernate 会自动把程序中的集合实现类替换成 Hibernate 自己的集合实现类),因此下面代码中,schools的类型能是List,不能是ArrayList,
映射说明
¨ list元素要求用list-index的子元素来映射有序集合的次序列。
¨ 集合的属性的值会存放有另外的表中,须以外键关联,用 Key 元素来映射外键列。
¨ 当集合元素是基本数据类型及其包装类, 字符串或日期类型时使用 element 来映射集合属性
测试程序:
生成的表及插入的数据
5.1.0.3. 数组
¨ 数组属性的映射和 List 的处理方式基本一致
¨ 数组使用 <array> 元素完成完成映射
5.1.0.4. Bag
Bag的特性是:允许重复的元素,但无序。在Java的标准API中并没有提供Bag容器,Hibernate提供自己的Bag实现,允许您将List映射为Bag。
¨ bag元素既可以为List集合属性映射,也可以为Collection集合属性映射。
¨ 不管是哪种集合属性,使用bag元素都将被映射成无序集合,而集合属性对应的表没有主键。
¨ Bag 元素只需要 key 元素来映射外键列,使用 element 元素来映射集合属性的每个元素。
5.1.0.5. Map
¨ Map不仅需要映射属性值,还需要映射属性Key。
¨ Hibnernate 将以外键列和Key列作为联合主键。
¨ Map集合属性使用map元素映射,该元素需要key和map-key两个子元素
• key子元素用于映射外键列,
• map-key子元素则用于映射Map集合的Key。
• map-key和element元素都必须确定type属性
5.2. 组成关系(Component)映射
组件属性的意思是持久化类的属性既不是基本数据类型,也不是 String 字符串,而是某个组件变量,该组件属性的类型可以是自定义类。
¨ 显然无法直接用 property 映射 name 属性。为了映射组件属性, Hibernate 提供了 component 元素。
¨ 每个 component 元素映射一个组件属性,组件属性必须指定该属性的类型,component 元素中的 class 属性用于确定组件的类型。
示例:
生成的表及插入的数据:
集合除了存放 String 字符串以外,还可以存放组件类型。实际上,更多情况下,集合组件存放的都是组件类型。
¨ 对于有集合属性持久化类, 需要使用 set, list, bag 等集合元素来映射集合属性。
¨ 如果集合里的元素是普通字符串,则使用 element 映射集合元素即可。
¨ 如果集合元素也是定义类,则需使用 composite-element 子元素来映射集合元素。
¨ composite-element 元素映射一个组件类型,因此需要 class 元素确定元素的类型,该元素还支持 property 的子元素来定义组件类型的子属性
5.3. 一对多关联关系映射
1, 单项关联:仅仅建立从Order到Customer的多对一关联,即仅仅在Order类中定义customer属性。或者仅仅建立从Customer到Order的一对多关联,即仅仅在Customer类中定义orders集合。
2, 双项关联:既建立从Order到Customer的多对一关联,又建立从Customer到Order的一对多关联
5.3.1. 单向多对一
单向 n-1 关联只需从 n 的一端可以访问 1 的一端。
5.3.1.1. 域模型
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
5.3.1.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.1.3. 代码
代码(持久化类):
代码(映射文件):
Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
测试代码:
1, 保存新数据,并有关联关系。
2, 不能关联Transient状态对象。
3, 获取数据,可以从一端获取到另一端,但从另一端获取不到这一端。
4, 解除关联关系。
5.3.1.4. 级联(cascade)
当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,而是会抛出TransientObjectException.如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
级联指的是当主控方执行操作时,关联对象(被动方)是否同步执行同一操作。
级联风格
每个 Hibernate session 的基本操作 — 包括 persist(), merge(), saveOrUpdate(), delete(),lock(), refresh(), evict(), replicate() — 都有对应的级联风格(cascade style)。这些级联风格(cascade style)风格分别命名为persist, merge, save-update, delete, lock, refresh, evict, replicate。
级联风格 | Session中的方法 |
persist | persist() |
merge | merge() |
save-update | save()、update()、saveOrUpdate() |
delete | delete() |
lock | lock() |
refresh | refresh() |
evict | evict() |
replicate | replicate() |
如果你希望一个操作被顺着关联关系级联传播,你必须在映射文件中指出这一点。
指定级联风格:
<one-to-one name="person" cascade="persist"/>
级联风格(cascade style)是可组合的:
<one-to-one name="person" cascade="persist,delete"/>
你可以使用 cascade="all" 来指定全部操作都顺着关联关系级联(cascaded)。默认值是cascade="none",即任何操作都不会被级联(cascaded)。
delete-orphan
A special cascade style, delete-orphan, applies only to one-to-many associations, and indicates that the delete() operation should be applied to any child object that is removed from the association.
在对象–关系映射文件中, 用于映射持久化类之间关联关系的元素, 如<set>, <many-to-one> 和 <one-to-one> 都有一个 cascade 属性。
5.3.2. 单向一对多
单向 1–n 关联只需从 1 的一端可以访问 n 的一端。
5.3.2.1. 域模型:
5.3.2.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.2.3. 代码
代码(持久化类):
代码(映射文件):
Hibernate使用set元素来映射一对多关联关系
测试代码:
1, 保存新数据,并有关联关系。
2, 设置cascade并测试。
3, 获取数据,可以从一端获取到另一端,但从另一端获取不到这一端。
4, 解除关联关系。
5.3.2.4. inverse属性
¨ 在hibernate中通过对 inverse 属性的值决定是由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系
¨ 在没有设置 inverse=true 的情况下,父子两边都维护父子关系
¨ 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
¨ 在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句。
¨ 只有集合标记(set/map/list/array/bag)才有inverse属性
¨ 在hibernate中通过对 inverse 属性的设置来决定是由谁来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
¨ 在没有设置 inverse=true 的情况下,父子两边都维护父子关系
¨ 在 1-N 关系中,将 many 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
¨ 在 1-N 关系中,若将 1 方设为主控方会额外多出 update 语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
5.3.2.5. order-by属性
¨ <set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序。(注意,指定的是SQL语句的OrderBy子句,所以要写列名)
¨ order-by 属性中还可以加入 SQL 函数
5.3.3. 一对多/多对一双向关联
¨ 双向 1-n 与 双向 n-1 是完全相同的两种情形
¨ 双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
5.3.3.1. 域模型
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
5.3.3.2. 关系数据模型
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
5.3.3.3. 代码
持久化类:双方都有相应的属性。
映射文件:双方都做相应的映射。
5.4. 父子关系
所谓父子关系:
是指父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联
解除关联关系 ---父子关系
5.5. 多对多关联关系映射
多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系型数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。
5.5.1. 双向多对多
¨ 双向 n-n 关联需要两端都使用集合属性
¨ 双向n-n关联必须使用中间表
¨ 集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类
¨ 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。
¨ set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,
¨ 其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,
¨ 因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.
¨ 对于双向 n-n 关联, 须把其中一端的 inverse 设置为 true, 否则可能会造成主键冲突.
映射Student端
映射Course端
测试代码
• 关联并保存对象
• 解除关联关系
维护关联关系是指在中间表中插入或删除数据。
5.5.2. 单向多对多
去掉双向多对多关联中的任一方,即是单向多对多。
5.6. 一对一关联关系映射
¨ 一对一关联指两个表之间的记录是一一对应的关系。分为两种:外键关联和主键关联。
¨ 比如一家公司(Company)和它所在的地址(Address)。在业务逻辑中要求一家公司只有唯一的地址,一个地址也只有一家公司。
5.6.1. 基于外键的双向1-1关联
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加 many-to-one 元素。为 many-to-one元素增加 unique=“true” 属性来表示为1-1关联,并用name属性来指定关联属性的属性名
另一端需要使用one-to-one元素,该元素使用 property-ref(可以不加) 属性指定使用被关联实体主键以外的字段作为关联字段
有外键方(Company)配置
无外键方(Address)配置
测试代码:
1, 关联并保存对象
2, 测试唯一性
3, 取消关联关系
5.6.2. 基于主键的双向1-1关联
一对一的另一种解决方式就是主键关联,在这种关联关系中,要求两个对象的主键必须保持一致,通过两个表的主键建立关联关系须外键参与。
基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的哪个属性作为 “对方”
采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其 one-to-one 属性还应增加 constrained=“true” 属性;另一端(company)增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
无外键方(Company)配置
有外键方(Address)配置
5.6.3. 单向一对一映射
5.6.3.1. 基于外键的单向 1-1
¨ 单向 1-1,POJO 与 N-1 没有丝毫区别。
¨ 基于外键的单向 1-1 映射文件:只需要在原有的 many-to-one 元素添加 unique=“true”,用以表示 N 的一端必须唯一即可,N的一端增加了唯一约束, 即成为单向 1-1.
5.7. 映射继承关系
Hibernate支持三种继承映射策略:
¨ 每个具体类一张表(table per concrete class) 将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
¨ 每个类分层结构一张表(table per class hierarchy) 对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
¨ 每个子类一张表(table per subclass) 域模型中的每个类映射到一个表,通过关系数据模型中的外键来描述表之间的继承关系。这也就相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。
5.7.1. 采用 subclass 元素的继承映射
父类和子类使用同一张表
¨ 采用 subclass 的继承映射可以实现对于继承关系中父类和子类使用同一张表
¨ 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是哪个类的实例----这个列被称为辨别者列(discriminator).
¨ 在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
¨ 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中
5.7.2. 采用 joined-subclass 元素的继承映射
关系树的每个类对应一个表
¨ 采用 joined-subclass 元素的继承映射可以实现每个子类一张表
¨ 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
¨ 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键,该主键必须与父类标识属性的列名相同。但如果继承树的深度很深,可能查询一个子类实例时,需要跨越多个表,因为子类的数据一次保存在多个父类中。
¨ 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中
5.7.3. 采用 union-subclass 元素的继承映射
关系树的每个类存放在各自独立的表中,没有主表和子表的概念,每个 表都有全部的自身信息,所有表和在一起后,记录仍然是唯一的。
¨ 采用 union-subclass 元素可以实现将每一个实体对象映射到一个独立的表中。
¨ 子类增加的属性可以有非空约束 --- 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
¨ 子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录
¨ 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和
¨ 在这种映射策略下,既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键.
¨ 使用 union-subclass 映射策略是不可使用 identity 的主键生成策略, 因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的. 受此影响, 也不该使用 native 主键生成策略, 因为 native 会根据数据库来选择使用 identity 或 sequence.
5.7.4. 三种继承映射方式的比较
6. Hibernate配置文件(.cfg.xml)的常用属性
6.1. JDBC 连接属性
¨ connection.url:数据库URL
¨ connection.username:数据库用户名
¨ connection.password:数据库用户密码
¨ connection.driver_class:数据库JDBC驱动
¨ dialect:配置数据库的方言,根据底层的数据库不同产生不同的sql语句,Hibernate 会针对数据库的特性在访问时进行优化
6.2. C3P0 数据库连接池属性
¨ hibernate.connection.provider_class: 该类用来向 Hibernate 提供JDBC连接
¨ hibernate.c3p0.max_size: 数据库连接池的最大连接数
¨ hibernate.c3p0.min_size: 数据库连接池的最小连接数
¨ hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
¨ hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
¨ hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
¨ hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
¨ hibernate.c3p0.validate: 是否每次连接都验证连接是否可用
6.3. 其他
¨ show_sql:是否将运行期生成的SQL输出到日志以供调试。取值 true | false
¨ format_sql:是否将 SQL 转化为格式良好的 SQL . 取值 true | false
¨ use_sql_comments:是否在 Hibernate 生成的 SQL 语句中添加有助于调试的注释
¨ hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式。取值 create | update | create-drop | validate
¨ jdbc.fetch_size
¨ hibernate.jdbc.batch_size
6.4. jdbc.fetch_size 和 jdbc.batch_size说明
jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会 1 次性把1万条取出来的,而只会取出Fetch Size条数,当纪录集遍历完了这些记录以后,再去数据库取Fetch Size条数据。因此大大节省了无谓的内存消耗。Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。Oracle数据库的JDBC驱动默认的Fetch Size=10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当Fetch Size=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持。MySQL就像上面那种最坏的情况,总是一下就把1万条记录完全取出来,内存消耗会非常惊人!
hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。Batch Size越大,批量操作的向数据库发送sql的次数越少,速度就越快。测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!Oracle数据库 Batch Size = 30 的时候比较合适。
7. 操纵持久化对象(Session)
7.1. 在hibernate中java对象的状态
Hibernate 把对象分为 4 种状态:
¨ 临时状态,
¨ 持久化状态,
¨ 游离状态,
¨ 删除状态.
Session 的特定方法能使对象从一个状态转换到另一个状态
7.1.1. 临时对象(transient)
¨ 在使用代理主键的情况下, OID 通常为 null
¨ 不处于 Session 的缓存中
¨ 在数据库中没有对应的记录
7.1.2. 持久化对象(也叫”托管”)(Persist)
¨ OID 不为 null
¨ 位于 Session 缓存中
¨ 持久化对象和数据库中的相关记录对应
¨ Session 在清理缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
¨ 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
有主键值,数据库会做相应改变。
7.1.3. 游离对象(也叫”脱管”)(Detached)
¨ OID 不为 null
¨ 不再处于 Session 的缓存中
¨ 一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
7.1.4. 删除对象(Removed)
¨ OID 不为 null
¨ 从一个 Session实例的缓存中删除
¨ Session 已经计划将其从数据库删除, Session 在清理缓存时, 会执行 SQL delete 语句, 删除数据库中的对应记录
¨ 一般情况下, 应用程序不该再使用被删除的对象
7.1.5. 对象的状态转换说明(图)
对象的状态转换图
测试hibernate中java对象的状态
程序代码 | 生命周期 | 状态 |
tx = session.beginTransaction(); Customer c = new Customer); | 开始生命周期 | 临时状态 |
Session.save(c) | 处于生命周期中 | 转变为持久化状态 |
Long id=c.getId(); c = null; Customer c2 = (Customer)session.load(Customer.class,id); tx.commit(); | 处于生命周期中 | 处于持久化状态 |
session.close(); | 处于生命周期中 | 转变为游离态 |
c2.getName(); | 处于生命周期中 | 处于游离态 |
c2 = null; | 结束生命周期 | 结束生命周期 |
7.1.6. 对象的状态总结
| Session缓存存在对应的记录 | 数据中存在对应的记录 |
临时态 | no | no |
持久态 | yes | 可能有也可能没有,但最终会有 |
游离态 | no | 可能有(数据没有删除)也可能没有 |
7.2. session的缓存
7.2.1. Session缓存的说明
Session 接口是 Hibernate向应用程序提供的操纵对数据库的最主要的接口,它提供了基本的保存,更新,删除和加载Java 对象的方法。
在 Session 接口的实现中包含一系列的 Java 集合,这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期(因为还有这些集合引用对象)。
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,有则返回,没有再查询数据库。
7.2.2. 清理session的缓存的相关方法
Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为清理缓存(flush)。
默认情况下 Session 在以下时间点清理缓存:
¨ 当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),然后在向数据库提交事务(tx.commit())
¨ 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,以保证查询结果能够反映持久化对象的最新状态
¨ 显式调用 Session 的 flush() 方法.
Session.flush()
进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步执行一些列sql语句,但不提交事务。
Transaction.commit()
先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
Session.reresh()
刷新缓存,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态。
Session.clear()
清空缓存(等价于list.removeAll())
7.2.3. 设置缓存的清理模式
清理缓存的模式 | Session的查询方法 | tx的commit() | Session的flush() |
FlushMode.AUTO(默认) | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FLushMode. MANUAL | 不清理 | 不清理 | 清理 |
7.2.4. 大批量操作(如插入10万记录)
错误代码:
这段程序会失败并抛出内存溢出异常(OutOfMemoryException)。这是因为 Hibernate 把所有新插入的客户(Customer)实例在 session 级别的缓存区进行了缓存的缘故。
正确做法:
7.3. 操纵持久化对象的方法(Session中)
7.3.1. save()
Session 的 save() 方法使一个临时对象转变为持久化对象。
¨ Session 的 save() 方法完成以下操作:
• 把 News 对象加入到 Session 缓存中, 使它进入持久化状态
• 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
• 计划执行一条 insert 语句,把Customer对象当前的属性值组装到insert语句中
¨ Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
7.3.2. update()
Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句。
若希望 Session 仅当修改了 News 对象的属性时, 才执行 update() 语句, 可以把映射文件中 <class> 元素的 select-before-update(更新之前先查询) 设为 true. 该属性的默认值为 false
当 update() 方法关联一个游离对象时, 如果在 Session 的缓存中已经存在相同 OID 的持久化对象, 会抛出异常
原因是:两个不同的对象拥有相同的OID
当 update() 方法关联一个游离对象时, 如果在数据库中不存在相应的记录, 也会抛出异常
7.3.3. saveOrUpdate()
该方法同时包含save和update方法,如果参数是临时对象就用save方法,如果是游离对象就用update方法,如果是持久化对象就直接返回。
判定对象为临时对象的标准
¨ Java 对象的 OID 为 null
¨ 映射文件中为 <id> 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配
如果参数是临时对象就会用save方法
如果是游离对象就用update方法
如果是持久化对象就直接返回,不执行操作
判定对象为临时对象的标准
¨ Java 对象的 OID 为 null
¨ 映射文件中为 <id> 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配,执行插入操作
根据以上判断临时对象的标准id=null是临时对象。但可以定义属性为int id
* 此时id默认值是0而不是null,应该执行更新操作
* 但实际我们要执行的插入操作。这时,可以在id中设置unsaved-value=0(默认值)
7.3.4. get()、load()
都可以根据给定的 OID 从数据库中加载一个持久化对象
区别:
¨ 当数据库中不存在与 OID 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null
¨ 两者采用不同的延迟检索策略
7.3.5. delete()
Session 的 delete() 方法既可以删除一个游离对象, 也可以删除一个持久化对象。
¨ 如果参数是持久化对象,就执行一个delete语句,若为游离对象,先使游离对象被session关联,使他变为持久化对象
¨ 计划执行一条 delete 语句
¨ 把对象从 Session 缓存中删除, 该对象进入删除状态.
7.3.6. 与触发器协同工作
Session.save(c);
Session.flush();
Session.refresh(c);
------------------------------------------------------
触发器的行为导致缓存与数据库中的数据不一致。解决办法是执行完
操作后,立即调用session的flush方法和refresh方法,迫使缓存与
数据库同步。
Session的update操作方法盲目的激活触发器
如果游离状态的对象的属性和数据库一致,则更新操作是多余的。
为避免这种情况:
<class name=“” table=“” select-before-update=“true”>
……
</class>