Hibernate总结
一、应用程序的分层结构
(1)应用软件的三层结构:
1)表述层:提供与用户交互的界面。GUI(图形用户界面)和Web页面时表述层的两个典型的例子。
2)业务逻辑层:实现各种业务逻辑。例如当用户发出生成订单的请求时,业务逻辑层负责计算订单的价格、验证订单的信息,以及把订单信息保存到数据库中。
3)数据库层:负责存放和管理应用的持久性业务数据。例如对于电子商务网站应用,在数据库中保存了客户、订单和商品等业务数据。
(2)软件分层:
1)物理分层:
即每一层都运行在单独的机器上。整个系统物理上分两层。
2)逻辑分层:
指的是在单个软件模块中完成特定的功能。即上述的三层结构。
逻辑分层的的特征:
a)每一层由一组相关的类或组件构成,共同完成特定的功能。
b)层与层之间存在自上而下的依赖关系,即上层组件会访问下层组件的API,而下层组件不应该依赖上层组件。例如:表述层依赖于业务逻辑层,而业务曾依赖数据库层。
c)每个层对上层公开API,但具体的实现细节对外透明。当某一层的实现发生变化,只要他的API不变,不会影响其他层的实现。
eg.
购物网站:按照业务功能划分为客户管理模块、订单模块和库存模块。
这几个模块之间为并列关系,不存在自上而下的依赖关系,因此不算分层的结构。但每个模块都可划分为表述层、业务逻辑层和数据库层。
管理
(3)持久层(import):
1)原因:三层结构中业务逻辑层不仅负责业务逻辑,而且直接访问数据库,提供对业务数据的保存、更新、删除和查询操作。为了使数据访问细节和业务逻辑分开,可以把数据访问作为单独的持久层。
2)持久层封装了数据访问细节,为逻辑层提供了面向对象的API
3)中间件:是在应用程序和系统之间的连接管道。
1)中间件的特点:
a) 中间件有很高的可重用性,对于各种应用领域都适用。
b) 透明性。
2)Hibernate可以看成是连接Java应用和关系数据库的管道。
Hibernate作为中间件,可以为任何一个需要访问关系数据库的java应用服务。
二、hibernate数据持久化组件
1.基本概念:
(1)对象持久化:把数据保存在永久的存储介质中(数据库)。
(2)ORM(object relation mapping)对象关系映射。
(3)POJO(PlainOldJavaObject)类,就是标准的Java Bean。
(4)Hibernate就是一个可以自动的根据xml完成对象关系映射,并持久化到数据库的开源组件,底层也是由JDBC实现的。通过xml文件的配置,对数据库的底层的方言,以及数据库连接所需的信息,以及连接数据库的驱动。
(5)hibernate的系统配置文件的名字一般叫做hibernate.cfg.xml,一般是映射类名.hbm.xml。一般将映射类的xml文件和实体类放在一起。
a)对象映射文件
hibernate的映射类的XXXX.hbm.xml的写法(XXX一般跟所对应的POJO类的类名相同)
<?xml version="1.0" encoding="gbk"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--package:映射类所在的包,注意:包中间用"."隔开-->
<hibernate-mapping package="XXX.XXX.XXX">
<!--将类和数据库的表联系起来-->
<class name="Xxxxxx" table="Xxxxx">
<!--主键生成策略-->
<id name="studentId" column="studentId">
<!--指定主键生成策略为用户指定-->
<generator class="assigned"/>
</id>
<!--类中的属性和字段之间建立联系-->
<property name="XXX" column="XXXX" type="string"/>
<property name="homeAddress" column="homeAddress"/>
<property name="schoolAddress" column="schoolAddress"/>
<!--在hibernate中其他类型可以自动识别只有Date类型必须指名-->
<property name="brithday" column="brithday" type="date"/>
</class>
</hibernate-mapping>
b)hibernate.cfg.xml中会设置数据库的连接信息,以及引用的其他文件的文件名,和一些其他的设置。这个文件一般放在项目的根目录下。
在hibernate.cfg.xml的写法
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--显示sql语句-->
<property name="show_sql">true</property>
<!--使显示的sql语句格式化-->
<property name="format_sql">true</property>
<!--使用的数据库方言信息-->
<property name="dialect">....</property>
<!--使用的数据库的驱动-->
<property name="connection.driver_class">....</property>
<!--连接数据库使用的url-->
<property name="connection.url">....</property>
<!--连接数据库的用户名-->
<property name="connection.username">...</property>
<!--连接数据库的密码-->
<property name="connection.password">...</property>
<!--引入的映射对象的xml文件的全路径及文件名,注意:包名之间用"/"隔开-->
<mapping resource="xxx/xxxx/xxxxx.hbm.xml"/>
</session-factory>
</hibernate-configuration>
2.使用hibernate编程步骤
1)配置环境,加载hibernate的jar文件,以及连接数据库连接使用的jar文件,并配置CLASSPATH环境变量。
2)写POJO类(普通的java类)
3)写hibernate所需的配置文件,hibernate.cfg.xml ,Xxxxx.hbm.xml
4)调用hibernate API。
a)使用Configuration对象的buildSessionFactory()方法创建SessionFactory对象。
b)使用SessionFactory对象openSession()方法创建Session对象。
c)使用Session的相应方法来操作数据库,将对象信息持久化到数据库。
3.Hibernate的5个核心类或接口:
(1)Configuration:用于解析hibernate.cfg.xml文件和XXXXX.hbm.xml文件,并创建SessionFactory对象。Configuration对象用于配置并且启动Hibernate。Hibernate应用通过Configuration实例来指定对象--关系映射文件的位置或者动态配置Hibernate的属性,然后创建SessionFactory实例。
(2)SessionFactory:初始化Hibernate,充当数据存储源的代理,创建Session对象。一个SessinFactory实例对应一个数据存储源,应用从SessionFactory中获得Session实例。如果应用同时访问多个DB,怎需要为每个数据库创建一个单独的SessionFactory实例。
(3)Session:也被称为持久化管理器,对象级数据库操作。
特点:
1)不是线程安全的,因此在设计软件架构时,应该避免多个线程共享同一个Session实例。
2)Session实例是轻量级的,所谓轻量级,是指它的创建和销毁不需要消耗太多的资源。这意味着在程序中可以经常创建或销毁Session对象,例如为每个客户请求分配单独的Session实例,或者为每个工作单位分配单独的Session实例。
3)通常将每一个Session实例和一个DB事务邦定,也就是说,每执行一个DB事务,都应该先创建一个新的Session实例,不论事务执行成功与否,最后都应该调用Session的close()方法,从而释放Session实例占用的资源。
注:每个Session实例都有自己的缓存,这个Session实例的缓存只能被当前的工作单元访问。
(4)Query:执行数据库查询操作。要使用HQL(HibernateQueryLanguage)查询语句,HQL查询语句是面向对象的,它引用类名及类的属性名。
(5)Transaction:用于管理操作事务。它对底层的事务接口做了封装,底层事务接口包括:JDBC API、JTA(JavaTransactionAPI)、CORBA(CommonObjectRequestBroker Architecture)API。
三、持久化类:
1.持久化类:是指其实例需要被Hibernate持久化到数据库中的类。持久化类符合JavaBean的规范,包含一些属性,以及与之对应的getXXX()和setXXX()方法。
注:get/set方法必须符合特定的命名规则,get和set后面紧跟属性的名字,并且属性名的首字母为大写。
name属性的get方法为getName(),如果写成getname()或getNAME()会导致Hibernate在运行时抛出以下异常:
net.sf.hibernate.PropertyNotFoundException:Could not find a getter for porperty name in class mypack XXX
2.如果持久化类的属性为boolean类型,那么他的get方法名即可以用get作为前缀,也可以用is作为前缀。
eg .
Account类的opened属性为boolean类型,因此以下两种get方法是等价的。
public boolean isOpened(){
return opened;
}
<=>
public boolean getOpened(){
return opened;
}
3.持久化类有一个id属性,用来唯一标识Account类的每一个对象。这个id属性被称为对象标示符(OID,Object Identifier),通常它都用整数表示。
4.Hibernate要求持久化类必须提供一个不带参的默认构造方法,在程序运行时,Hibernate运用Java反射机制,调用java.Lang.raflect.Constructor.newInstance()方法来构造持久化类的实例。
四、把持久化类映射到表
1.Account.hbm.xml文件用于映射Account类,如果需要映射多个持久化类,有两种方法
(1)在同一个映射文件中映射所有类
(2)为每一个类创建单独的映射文件,映射文件最好和类同名,扩展名为hbm.xml(推 荐使用,这有利于在团队开发中维护和管理映射文件)
2.映射文件的分析:
(1)<hibernate-mapping package="XXX.XXX.XXX">
package:映射类所在的包,注意:包中间用"."隔开
(2)<class name="Xxxxxx" table="Xxxxx">
<class>元素指定类和表的映射
name:设定类名 table:设定表名
注:如果没写tableHibernate直接以类名作为表名。
(3)<id name="studentId" column="studentId">
<generator class="native"/>
</id>
1)<id>子元素设定持久化类的OID和表的主键的映射,<class>元素只有一个<id>子元素。
2)<generator>子元素指定对象标识符生成器,他负责为OID生成唯一标识符。
3)Hibernate的内置标示符生成器(id生成器):
a)increment:由Hibernate以递增的方式为代理主键赋值。Hibernate在初始化阶段读取表中最大主键值,然后再最大值的基础上递增,增量为1。
eg .
<id name="oid" column="oid" type="integer">
<generator class="increment"/>
</id>
适用范围:
1>increament不依赖于底层数据库系统,所以适于所有的DBS。
2>适用于只有单个Hibernate应用程序访问同一个DB的场合。
3>OID必须为long、int或short类型,定义为byte类型,在运行时会抛异常。
b)identity:由底层DB来负责生成,要求底层DB把主键定义为自增类型,例如在SQL Server中,应该把主键定义为identity类型,MySQL中,定义为auto_increment类型。
eg .
create table account(
oid integer auto_increment not null,
actNo varchar(15) not null,
bla double
);
<id name="oid" column="oid">
<generator class="identity"/>
</id>
适用范围:
1>由于identity生成机制依赖于底层DB系统,要求底层DB必须支持自增字段类型。
2>OID必须为long、int或short类型,定义为byte类型,在运行时会抛异常。
c)sequence:利用底层DB提供的序列生成。
eg.
<id name="id" column="id">
<generator class="sequence">
<param name="sequence">tt_oid_seq</param><
</generator>
</id>
注:tt_oid_seq 是我们在Oracle中建立的,create sequence tt_oid_seq;
适用范围:
1>sequence依赖于底层DB,要求底层DB系统必须支持序列,eg.Oracle、DB2等
2>OID必须为long、int或short类型,定义为byte类型,在运行时会抛异常。
d)hilo(高低位算法):由Hibernate按照一种high/low算法来生成标识符,他从DB的特定表的字段中获取high值。
eg .
<id name="id" column="id">
<generator class="hilo">
<!--设置高位值取值的表-->
<param name="table">tt_hilo</param>
<!--设置高位值取值的字段-->
<param name="column">hi</param>
<!--指定低位最大值,当取道最大值时会再取一个高位值再运算-->
<param name="max_lo">50</param>
</generator>
</id>
适用范围:
1〉hilo不依赖于底层DB系统,适用于所有的DBS。
2〉OID必须为long、int或short类型,定义为byte类型,在运行时会抛异常。
3〉hign/low算法生成的标识符只能在一个DB中保证唯一。
4〉当用户为Hibernate自行提供DB连接,或者Hibernate通过JTA,从应用服务器的数据源获得数据库连接时无法使用hilo,因为这不能保证hilo在新的DB连接的事务中访问hi值所在的表,这时如果DBS支持Sequence,可以使用seqhilo来产生OID,seqhilo从名为hi_sequence的序列里获取high值。
e)native:依据底层数据库自动生成标识符的能力,来选择使用identity、sequence或hilo标识符生成器。它能自动判断底层DB提供的生成标识符的机制。
eg.
<id name="oid" column="oid">
<generator class="native"/>
</id>
注:针对Oracle数据库的生成方式是sequence,只不过需要一个特定名字的sequence,"hibernate_sequence"。MySQL和SQLServer选择 identity。
适用范围:
1〉适合跨DB平台开发,即同一个Hibernate应用需要连接多种DBS的场合。
2〉OID必须为long、int或short类型,定义为byte类型,在运行时会抛异常。
f)assign
g)foreign
h)uuid.hex
主键必须是字符串.32位
分布式数据库
(3)<property name="XXX" column="XXXX" type="string" not-null="true"/>
1)<class>元素包含多个<property>子元素。
2)<property>子元素设定类的属性和表的字段的映射。
a)name:指定持久化类的属性名。
b)type:指定Hibernate映射类型,Hibernate映射类型是Java类型与SQL类型的桥梁。
c)not-null:为true,表明不允许为空,默认为false。
d)column:指定与类属性映射的表的字段名,没设置Hibernate直接以类名作为字段名。
4.Hibernate采用XML文件来配置对象-关系映射,有以下优点:
(1)Hibernate既不会渗透到上层域模型中,也不会渗透到下层数据模型中。
(2)软件开发人员可以独立设计域模型,不必强迫遵守任何规范。
(3)数据库设计人员可以独立设计数据模型。
(4)对象-关系不依赖于任何程序代码,如果需要修改对象-关系映射,只需修改XML文件,不需要修改任何程序,提高了软件的灵活性,并且使维护更加方便。
五、持久化对象(PersistentObject)
1.Session的缓存
(1)Session具有一个缓存,位于缓存中的对象处于持久化状态,他和DB中的相关记录对应,Session能够在某些时间点,按照缓存中持久化对象的属性变化同步更新DB,这一过程称为清理缓存。Session的特定方法能使对象从一种状态转到另一个状态。
(2)Session的缓存有两大作用:
1)减少访问数据库的频率。
2)保证缓存中对象与DB中相关记录保持同步。
(3)commit()和flush()的区别:
1)flush()进行清理缓存的操作,执行一系列的SQL语句,但不会提交事务。
2)commit()会先调用flush()方法,然后提交事务。提交事务意味着对DB所做的更新被永久保存下来。
(4)flush()方法适用的场合:
1)插入、删除或更新某个持久化对象会引发DB的触发器。
2)在应用程序中混合使用HibernateAPI和JDBCAPI
3)JDBC驱动程序不健壮,导致Hibernate在自动清理缓存的模式下无法正常工作。
2.对于需要持久化的Java对象,在它生命周期中的三种状态:(Student stu ,Session s)(内部:内存中,外部:数据库中)
(1)暂态(自由态/临时态):刚刚用new语句创建,还没有被持久化,不处于Session的缓存中,只有内部状态,DB中无Data,与Session无关。
eg.
stu = new Student();
(2)持久态:已经被持久化加入到Session的缓存中内外部状态都有,由Session自动管理内、外部状态的同步。
eg.
s.save(stu);
(3)游离态/托管态:已经被持久化,但不再处于Session的缓存中。有内外部状态,由手工管理内外部状态同步
原因:Session被关闭
eg.
s.close();
3.持久化对象:处于持久化态的Java对象成为持久化对象。就是在DB中存有其相对应数据的对象,并且在内存中也有对象,这个对象在Session管理范围内,也就是调用过save()方法同步到DB中。
(1)持久化类与持久化对象的区别:
持久化类的实例可以处于暂态、持久态和游离态,其中处于持久态的实例被称为持久化对象。
(2)持久化对象的特征:
1)位于一个Session实例的缓存中,即持久化对象总是被一个Session实例关联。
2)持久化对象和DB中的相关记录对应。
3)Session在清理缓存时,会根据持久化对象的属性变化来同步更新DB。
4.临时对象:处于临时态的Java对象称为临时对象。
临时对象的特征:
1)不处于Session的缓存中,即不被任何一个Session实例关联。
2)在DB中没有对应的纪录。
5.游离对象:处于游离态的Java对象被称为游离对象。
(1)游离对象的特征:
1)不再位于Session的缓存中,即不被Session关联。
2) 在数据库中有和该对象向对应的纪录,并且在内存中的也存在该对象,但是不在Session的管理范围之内,也就是在Session关闭之后,就成了游离对象,就不会再将其改变同步到数据库中。
(2)游离对象与临时对象:
1)相同:两者都不被Session 关联,因此Hibernate不会保证他们的属性变化与DB保持同步。
2)区别:游离对象由持久化对象转变过来的,因此在DB中还存在对应记录。
临时对象在DB中没有 对应的纪录。
6.三种状态对象的转变:
状态转变如图所示:
new
garbage
save ()
对象直接从DB中恢复出来只在DB中有记录,但在内存中没对象.不是脱管态,因为托管态在内存中有对象
|
saveOrUpdate()
delete()
不能是暂态(会出错),对持久态对象有用,使其又成为持久态对象,与DB同步
|
get () 读一个对象
load ()
dead
不同于delete(),不删DB中记录,只将对象从Session中删除
|
update() 带参
garbage evict()除掉,带参
close() saveOrUpdate()带参,与Update作用一样
clear() lock()带参s.lock(stu)不更新DB
(1)到暂态的转变:
1)通过new语句刚创建的Java对象,处于暂态。
2)Session的delete()能使一个持久化对象/游离对象-->临时对象:
对于持久对象,delete()从DB中删除与他对应的纪录,并且把它从Session的缓存中删除;对于游离对象,delete()从DB中删除与它对应的记录。
(2)进入持久化状态:
1)Session的save()把临时对象-->持久化对象。
2)Session的load()/get()返回的对象总是处于持久化状态。
3)Session的find()返回的List集合中存放的都是持久化状态。
4)Session的update(),saveOrUpdate()和lock()使游离对象-->持久化对象。
5)当持久化对象关联一个临时对象,在允许级联保存的情况下,Session在清 理 缓存时会把这个临时对象-->持久化对象。
(3)转变为游离对象:
1)Session的close()清空
2)Session的缓存,缓存中的所有持久化对象-->游离对象。
3)Seesion的evict()能够从缓存中删除持久化对象,使它变为游离状态。
注:
(1)Session.load()/get()方法均可以根据指定的实体类和id从数据库读取记录,并返回与
之对应的实体对象。其区别在于:
1)如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。
Load方法可返回实体的代理类实例,而get方法永远直接返回实体类。
2)load方法可以充分利用内部缓存和二级缓存中的现有数据,
get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存, 直接调用SQL完成数据读取。
(2)save()、update()和saveOrUpdate()的区别
1)Session.save()方法的执行步骤:
·在Session内部缓存中寻找待保存对象。内部缓存命中,则认为此数据已经保存(执行
过insert操作),实体对象已经处于Persistent状态,直接返回。
·如果实体类实现了lifecycle接口,则调用待保存对象的onSave方法。
·如果实体类实现了validatable接口,则调用其validate()方法。
·调用对应拦截器的Interceptor.onSave方法(如果有的话)。
·构造Insert SQL,并加以执行。
·记录插入成功,user.id属性被设定为insert操作返回的新记录id值。
·将user对象放入内部缓存。
·最后,如果存在级联关系,对级联关系进行递归处理。
2)Session.update()方法的执行步骤:
·根据待更新实体对象的Key,在当前session的内部缓存中进行查找,如果发现,则认
为当前实体对象已经处于Persistent状态,返回。
·初始化实体对象的状态信息(作为之后脏数据检查的依据),并将其纳入内部缓存。
注意:
这里Session.update()方法本身并没有发送Update SQL完成数据更新操作,Update SQL将在之后的Session.flush方法中执行(Transaction.commit在真正提交数据库事务之前会调用Session.flush)。
3)Session.saveOrUpdate()方法的执行步骤:
·首先在Session内部缓存中进行查找,如果发现则直接返回。
·执行实体类对应的Interceptor.isUnsaved方法(如果有的话),判断对象是否为未保
存状态。
·根据unsaved-value判断对象是否处于未保存状态。
·如果对象未保存(Transient状态),则调用save方法保存对象。
·如果对象为已保存(Detached状态),调用update方法将对象与Session重新关联。
四、 映射:
(1)单一实体映射(OID--<id>)
(2)实体关系的映射:
1)基数关系映射(基于数量的关联关系)
a)1:1 b)1:m c)n:m
2)继承关系(多态)
3)组件关系映射(关联关系的强度)
4)集合关系映射(辅助基数关系和组件关系)
五、基数关系映射
(1)关联关系(双方)
1) 一般 use
2) 聚合 has
3) 组合 own
(2)一对一关系建表策略
1)共享主键,也就是一方引用另一方的主键,作为自身的主键,也是外键。
2)外键引用,也就是一方引用另一方的主键,作为外键,并且对引用的外键加唯一约束。
六、Hibernate中实体关系的处理
1、One-to-One
(1)在数据库中的体现:
一个表的外键对应另一个表的主键,外键要加上Unique约束(外键关联),或者两个表共享一个主键,表现为子表中的pk同时应用了父表的pk作外键而存在(主键关联,子表中的pk和fk为一个字段)。
(2)在JavaBean中:
在JavaBean中增加一个属性,即另一个对象的引用可以单向也可以双向。
(3)在Hibernate中:
1)主键关联:
即两张关联表通过主键形成一对一映射关系。
都是<one-to-one>,要用foreign生成策略。
eg.以人Person和护照Passport为例:
类图:
每个人有一个护照
Person和Passport之间是一对一关系,所以需要在这两个实体映射文件中
都进行相应的配置。这样,才能完整的表示这两个对象之间的关系。
表关系:
Person:
Passport:
映射文件:
①在Person对象的配置文件(Person.hbm.xml)中定义到Passport对象的关联:
<one-to-one name="passport"
class="one2one.entity.Passport" cascade="all" />
注:
·cascade:操作是的级联关系,即当主控方执行操作时,关联对象(被动方)是 否同步执行同一操作。
这个属性的可选值有:all(所有的操作均进行级联操作)、 none(所有 操作均不进行级联操作)、save-update(在执行save和update 时进行级 联操作)、delete(在执行delete是进行级联操作)。
cascade="all":表示增删改查Person时都会级联增加、删除和修改 Passport 对象。级联一定是在主对象的映射文件中。
②在Passport对象的配置文件(Passport.hbm.xml)中定义到Person对象的关联:
<one-to-one name="person"
class="one2one.entity.Person" constrained="true" />
这样才定义了Person和Passport对象之间的一对一的关联关系
注:
·constrained:表明当前类对应的表与被关联的表之间是否存在这外键约束。这将影响级联操作的顺序
constrained="true",告知Hibernate当前表主键上存在一个约束:Passport表引用了Person表的主键作为外键。
③为了确保两个对象的主键值是相等的,在定义主键的时候可以使用名称
foreign的Hibernate标识符生成策略来实现这一目的。Passport对象主键
字段的
<id name="oid" column="oid" >
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
注:<generator class="foreign">与constrained="true"是成对出现 的!!!
2)外键映射:
采用唯一外键关联的方式建立的关联关系实际上是一个表的某个外键字段关联到另一个表的主键上,这其实是只是多对一的一种特例。
表关系:
Person:
Passport:
映射文件:
主表中用one-to-one,通过property-ref
附表中用many-to-one
①在Person对象的配置文件(Person.hbm.xml)中定义到Passport对象的关联:
<one-to-one name="passport" class="one2one.entity.Passport"
property-ref="person" cascade="save-update"/>
注:
·property-ref:指定关联类的属性名,这个属性将会和本类的主键对应。
name="passport" property-ref="person"表示Passport表引用了Person表的主键作为它的外键。
②用户(Person)与护照(Passport)之间是一对一的关联关系,所使用的是 在护照对象中包含一个p_id字段,这个字段与Person对象的oid字段相关联
所以,首先需要在Passport对象的映射文件中增加配置信息:
<many-to-one name="person" class="one2one.entity.Person"
column="personid" unique="true"/>
注:
·unique:设置该字段的值是否唯一。在产生DDL语句或者创建数据库对象 的时候会用到。使用unique表明两者之间的一对一关系。
unique="true" column="personid"表示为Passport表中的外键personid加上唯一约束,使之一对多关系强制转化为一对一关系。
上面是一种双向的一对一关系,如果要实现对象之间的单向的一对一关联,在User的映射文件中进行相应的配置:
<one-to-one name="passport"
class="one2one.entity.Passport" />
单向关联在操作的时候两个对象必须要都插入,而且要注意插入顺序
s.save(pp);
s.save(p);
2、One-to-Many
(1)数据库中:一个表的主键对应另一个表的外键。
(2)JavaBean中:一个对象包含另一个对象的一个引用,另一个对象包含这个对象的一组(集合,用set)引用。
(3)在Hibernate中:用set.key指定具体列。
eg.以订单Order(一方)和订单项Item(多方)为例
类图:
表关系:
order_tt:(注意表名不要用order,否则报错,因为order为关键字)
item_tt:
映射文件:
①从一的一方开始,通过<one-to-many>元素来实现。由于一对多的关联关系需要在“一”这一方持有多个另一方的对象。而这种持有方式是通过Set来实现的。
Order.hbm.xml
<set name="items" cascade="all-delete-orphan" inverse="true">
<key column="orderid"></key>
<one-to-many class="Item"/>
</set>
注:
·cascade="all-delete-orphan"
如果设为all则:我们调用session.delete(order)时,会删除order下所有的item,但是如果调用order.getItems().remove(item),然后调用session.update(order)则不会删除item,只会把item_tt表中该条item记录的orderid设置为null。因此,如果想通过order.getItems.remove(item)删除对象,需要在已有级联的基础上加上“delete-orphan”。
·inverse为了使多的一方变为主控方所以在一的一方增加inverse="true"。
在对象之间的关联关系中,inverse="false"的一方为主控方,由它来负责维护对象之间的关联关系。
inverse与cascade的区别:
a.inverse所描述的是对象之间关联关系的控制方向,也就是有哪一个对象来维 护他们之间的关联关系。
b.cascade则描述的是层级之间的连锁操作方式,也就是一个对象的改变是否也 要同步对其管理对象进行相应的操作
②一对多的单向:
Item没有Order属性
Item.hbm.xml
没有<many-to-one>标签
一对多的双向:
这样可以避免需要在多的一方的关联字段中先插入空值的情况。
Item.hbm.xml
<many-to-one name="order" class="Order"
column="orderid" cascade="save-update"></many-to-one>
3、Many-to-Many
一般多对多关联会拆分成两个一对多的关系来实现多对多关系。
Hibernate的实现方式是通过中间表间接的实现了多对多关系,实际上也是将多对多拆分成两个双向的一对多关系。
eg.以班级Class和课程Course为例。
类图:
表关系:
Student:
Course
student_course
映射文件:
①Sudent.hbm.xml
<set name="courses" table="student_course" cascade="all">
<key column="studentid"></key>
<many-to-many class="Course" column="courseid"/>
</set>
注:
·<many-to-many>:元素是需要作为set等集合元素的子元素来使用的,它所描述的是这个集合的构成。还有一点需要注意,在多对多的关系中,JavaBean和DB表之间并不是一对一的关系。因为在Java中,是通过集合对象来保持被关联对象的多个实例,而在DB中则需要通过一个中间表来将多对多的关系转换为一个对象到中间表的多对一关系,以及中间表到另一个对象的一对多关系来实现的。
class="Course":集合中的元素对应的类
·table="class_course":设置中间表名称e
·<key column="studentid"></key>:在中间表中的字段
②Course.hbm.xml
<set name="students" table="student_course" inverse="true">
<key column="courseid"></key>
<many-to-many class="Student" column="studentid"/>
</set>
4、类继承关系映射
(1)DB表之间不存在继承关系,要把JavaBean中的继承关系映射到关系数据库中的有三种映射方式:
·每个类建一张表
·所有类建一张表
·只为具体类建表
eg. 以公司Company(一方)和员工Employee(多方),Employee有两个子:类小时工HourlyEmployee和正式员工SalariedEmployee
类图:
1)每个类建一张表
可以有效减少数据冗余,减少字段,查询效率不很高。
表结构:
company:
employee:
hourly:
salaried:
配置文件:
Company.hbm.xml
<class name="Company" table="company">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name" type="string"/>
<!-- Company与Employee是多态关联,
但是由于DB没有描述Employee类和它的两个子类的继承关系,
因此无法映射Company类的employees集合,
所以该文件仅仅映射了Company的id和name属性 -->
</class>
<一>:需要针对每个类写映射配置文件,就和普通的单表映射的xml文件相同
Employee.hbm.xml
<class name="Employee" table="employee">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name"/>
</class>
HourlyEmployee.hbm.xml
<class name="HourlyEmployee" table="hourly">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name"/>
<property name="rate"></property>
<many-to-one name="company" class="Company"
column="companyid" cascade="save-update"></many-to-one>
</class>
SalaryEmployee.hbm.xml
<class name="SalariedEmployee" table="salaried">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name"/>
<property name="salary"></property>
<many-to-one name="company" class="Company"
column="companyid" cascade="save-update"></many-to-one>
</class>
采用这种独立映射方式的配置方法,在配置文件中没有定义这些类之间的任何关系,也就是说,三个类都是独立存在的。使用这种映射方式解决了相同属性必须使用相同字段名的限制,但是从父类继承的属性需要在每个子类中都进行相应的定义,造成属性配置的重复。
<二>也可以使用一个xml文件来进行映射,要使用<union-subclass>标签!!!
注意:这里不能使用id生成策略中的native,而是要指定特定的生成策略。
Employee2.hbm.xml:
<class name="Employee" table="employee">
<id name="oid" column="oid" >
<generator class="hilo">
<param name="table">tt_hi</param>
<param name="column">hi</param>
</generator>
</id>
<property name="name"/>
<union-subclass name="HourlyEmployee" table="hourly" >
<property name="rate"></property>
<many-to-one name="Company"
column="companyid" cascade="save-update">
</many-to-one>
</union-subclass>
<union-subclass name="SalariedEmployee"
table="salaried" >
<property name="salary"></property>
<many-to-one name="Company" column="companyid"
cascade="save-update">
</many-to-one>
</union-subclass>
</class>
使用这种方式除了每个子类对应一个表外,其定义方式与java对象的继承非常相似,即子类可以继承父类中公共的属性定义,解决了属性配置的重复,但是,造成了相同属性必须使用相同字段名的限制。
2)所有类建一张表
查寻效率比较高,但是会产生很多空间浪费,当子类中的非空约束,就不大适用了,这是对于子类要使用<subclass>标签表示。
表结构:
company2:
employee2:
配置文件:
Company2.hbm.xml:
<class name="Company" table="company">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name" type="string"/>
<set name="employees"
cascade="all-delete-orphan" inverse="true">
<key column="companyid"></key>
<one-to-many class="Employee"/>
</set>
</class>
Employee3.hbm.xml:
<class name="Employee" table="employee2">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name" />
<discriminator column="employee_type" type="string"/>
<subclass name="HourlyEmployee"
discriminator-value="hourly">
<property name="rate"></property>
<many-to-one name="Company"
column="companyid" cascade="all">
</many-to-one>
</subclass>
<subclass name="SalariedEmployee"
discriminator-value="salaried">
<property name="salary"></property>
<many-to-one name="Company"
column="companyid" cascade="save-update">
</many-to-one>
</subclass>
</class>
使用这种映射方式需要注意的是它通过<discriminator>标签(<discriminator column="employee_type" type="string"/>)增加一个字段(这里是employee_type字段)来标示某个记录是属于哪个实体对象的。通过<subclass>标记中的discriminator-value属性来定义哪个值代表哪个子类的持久化对象。
3)只为具体类建表
·适用于不使用多态的情况下
·跟每个类建一张表的区别:
① 每个类一张表的映射策略所建立的表示独立的,每个表都包括子类所自定义 的属性和由父类锁继承的属性的映射字段。
② 只为具体类建表,子类所对应的表只包括子类所定义的属性,而子类所对应的 表与父类所对应的表是通过外键来进行关联的,即当持久化一个子类时,需要在父 类的表和子类的表各增加一条记录,这两个记录通过外键来关联。
·好处:父类所定义的属性就在父类的表中进行映射,而子类所定义的属性就在子 类的表中进行映射。避免了子类所定义的表中仍然需要定义父类属性的映射字段。
·映射文件中的子类可以使用<join-subclass>标签来表示,并且引用父类的主 键作为共享主键,就是不需要指定id生成策略
表结构:
company3
employee3
hourly2
salaried2
配置文件:
Company3.hbm.xml:
<class name="Company" table="company3">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name" type="string"/>
<set name="employees" cascade="all-delete-orphan"
inverse="true">
<key column="companyid"></key>
<one-to-many class="Employee"/>
</set>
</class>
Employee4.hbm.xml:
<class name="Employee" table="employee3">
<id name="oid" column="oid" >
<generator class="native">
</generator>
</id>
<property name="name" />
<joined-subclass name="HourlyEmployee" table="hourly2">
<key column="oid"></key>
<property name="rate"></property>
<many-to-one name="Company" column="companyid"
cascade="save-update">
</many-to-one>
</joined-subclass>
<joined-subclass name="SalariedEmployee" table="salaried2">
<key column="oid"></key>
<property name="salary"></property>
<many-to-one name="Company" column="companyid"
cascade="save-update">
</many-to-one>
</joined-subclass>
</class>
5、集合映射
(1)set映射
关联对象的属性除了外键之外,只有1、2个属性,那么就可以使用set映射
使用了set标签的element元素,不用创建关联对象就可以实现单向一对多的关联关系
public class Room implements Serializable{
private int id;
private String roomNumber;
private Set<String> equipments = new HashSet<String>();
private Set<Image> images = new HashSet<Image>();
}
<set name="equipments" table="equipment_set">
<key column="roomid" foreign-key="fk_equip_room_set"/>
<element column="description" type="string"
length="128" not-null="true"/>
</set>
<set name="images" table="image_set">
<key column="roomid" foreign-key="fk_img_room_set"/>
<composite-element class="Image">
<property name="path" column="path" type="string"
length="50" not-null="true"/>
<property name="width" column="width" type="integer" />
<property name="height" column="height" type="integer" />
</composite-element>
</set>
(2)map映射
非常有用的一种集合映射
public class Room implements Serializable{
private int id;
private String roomNumber;
private Map<String, String> equipments
= new HashMap<String, String>();
private Map<String, Image> images
= new HashMap<String, Image>();
}
<map name="equipments" table="equipment_map">
<key column="roomid" foreign-key="fk_equip_room_map"/>
<map-key column="name" type="string" length="15" />
<element column="description" type="string"
length="128" not-null="true"/>
</map>
<map name="images" table="image_map">
<key column="roomid" foreign-key="fk_img_room_map"/>
<map-key column="name" type="string" length="15" />
<composite-element class="Image">
<property name="path" column="path" type="string"
length="50" not-null="true"/>
<property name="width" column="width" type="integer" />
<property name="height" column="height" type="integer" />
</composite-element>
</map>
七、Hibernate关键字总结
1、inverse="true/false":如果一对多关联中的<key>字段定义成NOT NULL,那么当创建和更新关联关系时Hibernate 可能引起约束违例。为了预防这个问题,你必须使用双向关联,并且在“多”这一端(Set 或者是bag)指明inverse="true"。
2、cascade属性是设置级联操作的也就是在操作一端的数据如果影响到多端数据时会进行级联操作,cascade属性的值常用的设置为以下六项:
1)none就是不使用级联操作,默认级联是none。
2)save-update也就是只有对象保存操作(持久化操作)或者是持久化对象的更新操作,才会级联操作关联对象(子对象)。
3)persist就只是将级联对象也持久化到数据库。
4)delete对持久化对象的删除操作时会进行级联操作关联对象(子对象)。
5)all对持久化对象的所有操作都会级联操作关联对象(子对象)。
6)all-delete-orphan,在多端进行删除操作时,会再多端表中留下null空纪录,设置了级联操作为delete之会将表中表示关联的外键id置成null,不会将这条纪录也删除掉,而把级联设置成delete-orphan就不会留有空纪录,而是级联的把相关纪录删除掉。
建议:
one-to-one :用all
many-to-one :用all或save-update
many-to-many:用save-update
3、batch-size这个属性只能够写在set标签中,这个属性带表批量加载,也就是在加载一端的集合属性时会一次加载指定的数量的对象,而不是默认的一个一个的加载,会提高效率,批量加载只能用于延迟加载和立即加载策略,也就是(lazy="true"或者lazy="false")。
4、 Hiberniate的加载策略:
·延迟加载:就是对一端的集合属性的加载策略,就是在仅仅当应用程序需要访问时,才载入他们的值。
·立即加载
·预先抓取:当需要直接初始化成员属性时使用。他不保证查询返回的数据不重复,需要我们自己过滤。
(1)lazy="true" 延迟加载,这也就是会出现LazyInitializationException异常,也就是没有初始化这个代理的集合对象,在事先查询到了集合中的对象就会初始化这个对象,如果Session没有关闭就会在查询加载集合中的对象信息,如果提前关闭了Session,当使用集合中的对象信息时就会有这个异常。
(2)fetch="join",这就是使用了预先抓取策略,也就是针对关联的对象的加载策略,在使用到关联对象的信息时会再发送sql语句,如果不使用fetch="join",就会不使用表连接而是先查出一端的关联id再一条一条的发送sql语句查询到关联对象信息,使用了fetch="join"就会使用表连接将关联对象信息直接查寻出来的。fetch="lazy"这个是默认的设置。
注:
在使用fetch="join"要区分开他和外连接的区别,他不会忽略配置文件中的加载策略,而使用了外连接就会忽略配置文件中使用了外连接的一端的所有加载策略,而替之为立即加载。
内连接: from Order o join o.items
外连接: from Order o left join o.items
预先抓取: from Order o left join fetch o.items;
八、Hibernate控制的事务
1.事务:指一组相互依赖的操作行为。事务保证原子操作的不可分,也就是操作的同时成功或同时失败。
2.DB事务必须具备的四个特性(即:ACID特性):
原子性(atomicity)、一致性(consistency)、隔离性(isolation)、
持续性(durability)
3.事务对象的方法,来实现对事务的支持:
Transaction tran=session.beginTranaction();//声明事务开始边界
tran.commit(); //提交事务
tran.rollback();//回滚事务
4.DB事务的生命周期:
执行SQLselect、insert
update和delete语句
|
5.hibernate的事务隔离级别
hibernate的事务隔离级别和JDBC中大致相同。
设置时要在hibernate.cfg.xml配置
<property name="hibernate.connection.isolation">4</property>
1)1:读未提交的数据(Read uncommitted isolation)
2)2:读已提交的数据(Read committed isolation)
3)4:可重复读级别(Repeatable read isolation)
4)8:可串行化级别(Serializable isolation)
6.hibernate的锁
(1)锁的基本原理:
1)当一个事务访问某种数据库资源时,如果执行select语句,必须先获得共享锁,如果执行insert、update或delect语句,必须获得独占锁。
资源上已经放置的锁
|
第二个事务进行读操作
|
第二个事务进行更新操作
|
无
|
立即获得共享锁
|
立即获得独占锁
|
共享锁
|
立即获得共享锁
|
等待第一个事务解除共享锁
|
独占锁
|
等待第一个事务解除独占锁
|
等待第一个事务解除独占锁
|
2)当第二个事务也要访问相同资源时,如果执行select语句,也必须获得共享锁,如果执行insert、update或delect语句,也必须获得独占锁。此时根据已经放置在资源上的锁的类型,来决定第二个事务应该等待第一个事务解除对资源的锁定,还是可以立刻获得锁。
(2)数据库系统按照封锁封锁程度可分为:
1)共享锁:用于读数据操作,它非独占的,允许其他事务同时读取其锁定的资源,但不允许其他事务更新它。
2)独占所:也叫排他锁,适用于修改数据的场合。他所锁定的资源,其他事物不能读取也不能修改。
3)更新锁:在更新操作的厨师阶段用来锁定可能要修改的资源,这可以避免使用共享锁造成的死锁现象。
(3)从应用程序的角度分为悲观锁和乐观锁,锁的设置
session.get(User.class , userid , LockMode.UPGRADE);
LockMode.UPGRADE,修改锁,在get()方法中加上这个设置作为第三个参数。
LockMode.NONE 无锁机制
LockMode.READ 读取锁
LockMode.WRITE 写入锁,不能在程序中直接使用
还可以使用Session.lock() Query.setLockMode() Criteria.setLockMode()方法来设置锁
1)悲观锁:指在应用程序中显式的为数据资源加锁,是由数据库本身所实现的,会对数据库中的数据进行锁定,也就是锁行。它可以防止丢失更新和不可重复度这类并发问题,但它会影响并发性能。
实现方式:
a)在应用程序中显式指定采用DBS的独占锁来锁定数据资源。
b)在DB表中增加一个表明记录状态的LOCK字段。
2)乐观锁,就是通过对记录加上某些信息来解决并发访问的问题。
乐观锁通过Hibernate的版本控制功能来实现,它比悲观锁具有更好的并发 性,所以应该优先考虑使用乐观锁。
����
posted on 2009-03-31 19:39
faye 阅读(790)
评论(0) 编辑 收藏