最近学习有些疲惫,明天就是元旦了,好快!学习幸福,今天放松一下看看电影,明天再写学习日志吧!
新年快乐!现在是2010年1月1日星期五,2009年并未怎么赚钱,但一如既往每年的成长都让我高兴并心怀感激。我将继续加强自我人格的建设、继续努力学习!
前天的课程内容,我总结的有些唐突。今明两天全休息,所以我要认真总结一下,因为hibernate十分重要,虽然它目前不适于对速度要求较高的应用。Hibernate比struts要复杂的多,因为hibernate是负责数据库操作的框架。
传智播客的教学理念就是做实现原理和应用的教学,而不像某些只做肤浅的应用教学,这一点通过我在下边的总结就可以看出来,只做应用的教学是十分不利的并且学费还那么贵。在这新的一年我,我祝愿好人一生平安、健康快乐、幸福!并祝愿那些社会中骗子早日离开这个世界,让我们的民族发展更好些!
Hibernate整体的前两小部分内容为hibernate入门与Java对象持久化概述,这一点我在前天的日志中有简要的介绍,所以在此就不再总结了。我直接从重点内容开始!
一、对象-关系 映射基础
1.对象间的基本关系
首先我简要阐明一下对象之间的基本关系,在这以后UML的课程中也会深入的学习。对象具有的四种基本关系:
关联关系:关联关系在设计模式中是被提倡优先使用于继承关系的。关联关系就是将一个对象做为别一个对象的成员,是一种包含的关系。
依赖关系:对与对象之间的方法的调用,不存在包含的关系。
聚集关系:这个关系比较有趣,比如人的手和身体。如果身体不存在了,手也就不存在了。是一种整个与部分的关系。
一般关系:就是继承关系。
上边的这四种关系是对前一天的补充。对象-关系的映射基础没有涉及这些,而是单一对象通过hibernate与数据库的映射关系。
2.持久化类的属性及访问方法
首先回顾一下持久化,我们知道持久化层是从业务逻辑层中分离出来的专门用于数据库操作的这些部分。持久化层中的持久化类,便是我们之前早已学习的domain类。
1).持久化类的访问者有两个,一是JAVA应用程序,二是hibernate。
写:Java应用程序通过setter设置持久化对象的属性,hibernate通过getter获取持久化对象的属性并生成相应的SQL语句对表格进行操作。
读:hibernate通过setter设置持久化对象的属性,Java应用程序通过getter获取持久化对象的属性。
2).基本数据类型和包装类型
通过前天的日志,我们知道关联对象的属性与表格的字段是通过property元素节点设置的:
<property name="gender" column="gender" type="integer" /> |
基本的type是hibernate的类型,我们在持久化类中定义的gender属性为int。定义为int类型会有什么弊端?比如,我们有个学生成绩表。如果某个学生没有参加某一学科的考试,但我们却使用了int类型,它的默认值为0,当查看学生成绩时,他到底是考了0分还是没有考试?所以最好将持久化类中的gender属性定义为Integer,它的默认值为null。查询成绩时看到的是null,那么他肯定是没参加考试哦!(注意:数据库中的对应字段应该为字符型)
3).hibernate访问持久化类属性的策略
Hibernate通过name指定的值访问持久化对象。Hibernate通过name值,反射持久化对象的对方法。比如,name的值为gender。Hibernate会直接反射持久化对象的getGender和setGender方法。所以我们必须保证持久化对象中有对应的方法。这是因为property有一个access属性,它的默认值为property。
如果指定access的值为field,则hibernate直接根据name值反射持久化对象的属性。此时,我们必须保证持久化对象中有对应的属性。
4).在持久化类的方法中加入程序逻辑
通过3)我们知道,如果access的值为property,hibernate直接反射持久化对象的方法。在这个方法中我们就可以加入程序逻辑。老徐举了一个生动的例子,比如Customer类中有firstname和lastname两个属性。但我们只想让hibernate通过getName方法获得一个firstname+lastname的字符串,此时我们就可以在getName方法中将firstname与lastname两个属性值合并为一个中间使用 “.”连接的字符串返回。
使用hibernate获取数据表中的数据时,hibernate会调用持久化对象的setName方法。我们在这个方法中将传递进来的参数使用“.”分隔,然后分别设置到firestname和lastname属性中。
5).hibernate的hql语句
我们在使用JDBC、DBUtil时使用的都是SQL语句。但hibernate比较特殊,它使用的是自己的一套东西叫hql语句。比如我们调用session.find方法,传递的hql语句为:
"from customer as c where c.name='itcast'" |
其中的customer指向持久化对象的映射文件,name指向持久化对象的映射文件中的property元素的name属性。此时需要注意access属性的值。
6).设置派生属性
Property元素中,有一个formula属性。它的值是一个sql表达式,hibernate将根据此表达式计算的值设置到持久化对象的属性上。比如,我们要统计订单表中的总计:
<property name="totalprice" formula="(select sum(o.PRICE) from ORDERS o where o.CUSTOMER_ID=ID)" /> |
十分方便!
7).控制insert和update属性
映射属性 | 作用 |
<property>: insert属性 | 若为false,在insert语句中不包含该字段,该字段永远不能被插入。默认值true。 |
<property>: update属性 | 若为false,update语句不包含该字段,该字段永远不能被更新。默认值为true。 |
<class>:mutable属性 | 若为false,等价于所有的<property>元素的update属性为false,整个实例不能被更新。默认为true。 |
<class>:dynamic-insert属性 | 若为true,等价于所有的<property>元素的dynamic-insert为true,保存一个对象时,动态生成insert语句,语句中仅包含取值不为null的字段。默认false。 |
<class>:dynamic-update属性 | 若为true,等价于所有的<property>元素的dynamic-update为true,更新一个对象时,动态生成update语句,语句中仅包含取值被改变的字段。默认false。 |
8).处理SQL引用表示符
在SQL语法中,表示符是指用于为数据库表、视图、字段或索引等名字的字符串,常规表示符不包括空格,也不包含特殊字符,因此无需使用引用符号。如果数据库表名或列名包含特殊字符,可以使用引用表示符。如:
<property name="name" column="’CUSTOMER NAME’" /> |
此时的column的指定的字段名,必须使用’与括起来。
9).设置类的包名
在映射文件中包含了多个类,而这些类又在同一个包中。此时我们可以不在class属性的name属性中指定类的完整名称。首先我们在hibernate-mapping元素中添加一个package属性,之后的所有class子元素的name属性直接指定类名即可。
<hibernate-mapping package="cn.itcast.cc.hibernate"> |
二、映射对象标识符
在Java应用程序中,JVM使用地址来区分一个类的多个实例。在关系型数据库中使用主键来区分记录。在hibernate中使用OID来建立内存中的对象和关系数据库中对应的记录,其实OID就是持久化对象中的id属性对应数据库中的主键。之所以使用OID是因为hibernate的缓存技术,也需要使用OID进行自动监控。
1.关系数据库中的主键
主键不能为null、唯一且永远不会改变。
MySQL中的自动增长主键,将字段类型设置为 primary key auto_increment。
SQLServer中的自动增长主键,将字段类型设置为 primary key identity。
Oracle中的自动增长主键,需要自定义序列:create sequence seq_customer increment by 1 start with 1。
2.JAVA对象的“主键”、Hibernate中用对象表示符(OID)来区分对象
就是对象的地址,使用==与equal()比较两个对象是否相同。
Hibernate中使用OID来维护对象与数据记录的对应关系。
3.在hibernate中配置OID对应关系与主键生成器
<id name="id" type="long" column="ID"> <generator class="increment" /> </id> |
持久化对象的id属性与数据表格的ID字段对应,并使用hibernate提供的内置标示符生成器——increment。Hibernate提供了表识符生成器接口:net.sf.hibernate.id.IdentifierGenerator,并提供了多种内置的实现。
4.映射对象表示符
表示符生成器 | 描述 |
Increment | 适用于代理主键。由hibernate自动以递增的方式生成表识符,每次增量为1。 |
Identity | 适用于代理主键。由底层数据库生成表识符,条件是数据库支持自动增长数据类型。 |
Sequence | 适用于代理主键。Hibernate根据底层数据库序列生成标识符,条件是数据库支持序列。 |
Hilo | 适用于代理主键。Hibernate根据hign/low算法生成标识符。Hibernate把特定表的字段作为“hign”值。默认情况下,采用hibernate_unique_key表的next_hi字段。 |
Native | 适用于代理主键。根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo。 |
Uuid.hex | 适用于代理主键。Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。 |
assigned | 适用于自然主键。由java程序负责生成标识符,不能把setID()方法声明为Private的。尽量避免使用自然主键。 |
5. Hibernate中的increment
<id name="id" type="long" column="ID"> <generator class="increment"/> </id> |
适用范围:
1.由于不依赖与底层数据库,适合所有的数据库系统。
2.单个进程访问同一个数据库的场合,集群环境下不推荐适用。
3.OID必须为long、int或short类型,如果把OID定义为byte类型,抛异常。
4.在每次插入记录时,hibernate都会先调用“select max(id) from table;”返回最大的数据表中最大的id值,然后+1设置为新记录的id。使用increment会产生多进程访问的安全问题。所以在使用不依赖与底层数据库的主键时要注意这个问题。
6. Hibernate中的identity
<id name="id" type="long" column="ID"> <generator class="identity"/> </id> |
由底层数据库生成标识符,需要把字段定义成自增型。因为由数据库底层生成标识符,所以它是先在数据库中计算id,然后再返回id。这与increment不同,所以要使用identity防止多进程访问的安全。
7. Hibernate中的sequence
<id name="id" type="long" column="ID"> <generator class="sequence"> <param name="sequence">tester_id_seq</param> </generator> </id> |
适用范围:
底层数据库要支持序列,Oracle DB2 SAP等。
OID必须为long、int或short类型。
7. Hibernate中的hilo
<id name="id" type="long" column="ID"> <generator class="hilo"> <param name="table">hi_value</param> <param name="column">next_value</param> <param name="max_lo">100</param> </generator> </id> |
使用范围:
该机制不依赖于地层数据库,因此适用于所有的数据库系统。
OID必须为long、int、short类型,如果为byte类型的话,会抛出异常。
Net.sf.hibernate.id.IdentifierGeneratorException:this id generator generates Long、integer、short。
7. Hibernate中的native
<id name="id" type="native" column="ID"> <generator class="native" /> </id> |
适用范围:
该类型能根据底层数据库系统的类型,自动选择合适的标识符生成器,因此很适合于跨数据库的平台,即在同一个应用中需要连接多种数据库系统的场合。
OID与以上类同。
7. Hibernate中的自然主键
1).单个自然主键
<id name="id" column="NAME" type="string"> <generator class="assigned" /> </id> <version name="version" column="VERSION" unsaved-value="0" /> |
自然主键:把具有业务含义的字段作为主键叫做自然主键。
2).复合主键
<composite-id> <key-property name="name" column="NAME" type="string"> <key-property name="companyId" column="COMPANY_ID" type="long"> </composite-id> <version name="version" column="VERSION" unsaved-value="0" /> |
使用联合主键的持久化类需要实现serializable接口和覆盖equals()、hashCode()方法。
<composite-id name="costomerid" class="mypack.CustomerId"> <key-property name="name" column="NAME" type="string"> <key-property name="companyId" column="COMPANY_ID" type="long"> </composite-id> |
可以使用customId属性来设置连联合主键。
三、映射一对多关联关系
一对多的关系,还有俺再重复吗?先把老徐的东西拿出来:
(我们的UML课程是由老徐来传授的,上课时他的图解画的很专业)
1.多对一单向关联关系
我们使用订单表(orders)与客户表(customers)。订单对象(Order)中包含一个客户对象(Customer),这是对象的多对一关系(一个客户可能有多个订单)。
我们需要向Order的映射文件中添加一个many-to-one元素:
<hibernate-mapping> <class name="cn.itcast.cc.hibernate.persistence.Order" table="orders" lazy="false"> <id name="id" column="id" type="integer"> <generator class="increment"></generator> </id> <property name="orderNo" column="orderno" type="string" /> <property name="price" column="price" type="float" /> <many-to-one name="customer" column="cid" class="cn.itcast.cc.hibernate.persistence.Customer" not-null="true" cascade="save-update"> </many-to-one> </class> </hibernate-mapping> |
many-to-one:
name:设定待映射的持久化类的名字。
column:设定和持久化类的属性对应的表的外键。
class:设定持久化类的属性的类型。
not-null:是否允许为空。
Customer与Order的关系是:聚集关系。
测试代码:
private static void manyToOne(){ //获取session相当于获取了一个连接 Session session = sefac.openSession(); Transaction tra = session.beginTransaction(); //创建一个Customer对象 Customer customer = new Customer(); customer.set...(...); //创建一个Order对象 Order order = new Order(); order.set...(...); order.setCustomer(customer); //保存Order到数据库,注意此处没有保存cutomer对象哦! session.save(order); //提交事件 tra.commit(); //关闭会话 session.close(); } |
代码中没有保存customer对象,但因为在many-to-one元素中添加了“cascade="save-update"”(级联添加)属性,所以在保存order时,hibernate会自动保存customer对象。如果没有设置“cascade="save-update"”属性,则会抛异常:“org.hibernate.PropertyValueException: not-null property references a null or ransient value: cn.itcast.cc.hibernate.persistence.Order.customer”。
别忘记:“config.addClass(Order.class);”哦!
2. 一对多双向关联关系
什么是双向关联呢?上边的是单向,我们只设置了order的customer属性。我们再设置customer的orders属性,便形成了双向关联。我们需要在customer中添加一个orders属性,它的类型为set,set是一个集合并且不可包含重复信息,这样可以避免以后发生重复记录的错误。在类被实例化时便将orders初始化为HashSet,这样可以避免空指针异常!
1).级联添加:
我们有设置order的映射文件的many-to-one,在此我们也必须设置customer的set元素:
<set name="orders" cascade="save-update" lazy="false"> <key><column name="cid"></column></key> <one-to-many class="cn.itcast.cc.hibernate.persistence.Order"/> </set> |
name:设定待映射持久化类的属性名。
cascade:设定级联操作的程度。
key子属性:设定与所关联的持久化类对应的标的外键。
one-to-many子属性:设定所关联的持久化类。
我们可以设置cascade的值为“delete”这样在删除一个客时,也可以自动删除与客户级联的orders。
cascade属性值 | 描述 |
none | 忽略关联对象,默认值 |
save-update | 保存或更新当前对象时,级联保存关联的临时对象,更新关联的游离对象。 |
delete | 删除对象时,级联删除关联的对象。 |
all | 包含save-update和delete的行为。 |
delete-orphan | 删除所有和当前对象解除关联关系的对象。 |
all-delete-orphan | 包含all和delete-orphan的行为。 |
添加代码:
customer.getOrders().add(order); |
此时我们只保存customer或order对象,hibernate都可以将两个对象的数据保存到对应的表中。
注意:如果映射文件没有设置“cascade="save-update"”属性,则保存失败并抛异常!
2).级联删除:
我们调用session.delete(customer);方法时会一同删除customer对象的orders吗?不会!如果我们希望能够删除,我们必须设置Set元素的属性:cascade="delete"。
那如果我删除一个order,能自动删除对应的customer吗?你想可以吗?这本身就不符合业务逻辑!
3).Set集合的inverse属性:
这个属性十分重要,涉及到hibernate缓存监控技术。(hibernate缓存监控技术在后面会有介绍)。Inverse属性使得,如果持久化对象一旦发生改变就自动更新数据表中的记录。(持久化对象后面会有介绍)通过load、save、update返回或设置的对象都是持久化对象,这些对象一起发生改变,比如set了一个新值,hibernate就会自动将新值更新到数据库中。当调用了session.close方法是,这些持久化对象就不存在了!
结论:
1.在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,这可以提高性能。
2.在建立两个对象的关联时,应该同时修改关联两端的相应属性:
Customer.getOrders().add(order);
Order.setCustomer(customer);
这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改关联两端的对象的相应属性:
Customer.getOrders().remove(order);
Order.setCustomer(null);
一对一关联与多对多关联,我在此就不做总结了。把多对一和一对多搞明白了,这些就容易了!
四、操纵持久化对象
在hibernate中有三个缓存:一个是普通的SessionFactory的缓存,用来存储映射文件和SQL查询语句。一个是Session的缓存(一级缓存)用来存放持久化对象。一个是二级缓存(这个还没学)。
操纵持久化对象,玩的就是一级缓存。Session接口提供save、update、delete、load、get方法,这些方法设置或读取的对象被保存在一级缓存中,这些对象被称为持久化对象。我们前边提到的所有持久化对象,必须是保存在缓存中的对象。
1.JAVA对象在JVM中的生命周期
我们知道JAVA有自己的垃圾回收器机制,当一个对象不再被直接或间接的引用时,它就是垃圾对象。垃圾回收会将它释放掉。我们new出来的java对象被放在堆中,而对象的引用放在栈中。创建一个java对象JVM需要做三步,比如 String str = new String();:
1. new String()对象放在堆中。
2. str放在栈中。
3. 将str指向堆中的对象。
如:
1. String str1 = new String();
2. String str2 = str1;
3. str1 = null;
4. str2 = null;
请问放在堆中的对象何时被销毁?第三步吗?No!是第四步。这个对象从第1步“出生”,到第4步执行结束后“死亡”。
2.理解session的缓存
持久化对象被保存在缓存里,这么做有什么好处?
1. 减少访问数据库的频率。
2. 保证缓存中的对象与数据库中的记录保存同步。(监控技术)
3. 当缓存中的持久化对象之间存在循环关联关系时,Session会保证不出现访问对象图的死循,还以及由死循环引起的JVM堆栈溢出异常。
监控技术是如何实现的?session保存在缓存中的持久化对象,实质上是保存了对象的引用。那么同时与这个引用对应一个持久化对象的clone对象。Session监控就是将持久化对象与在缓存中的clone对象进行对比,如果发生改变就自动对数据库进行相应的操作。这样就保持了持久化对象与数据库记录的同步。
Session在清理缓存时,按照以下顺序执行sql语句。
1.按照应用程序调用save()方法的先后顺序,执行所有的对实体进行插入的insert语句。
2.所有对实体进行更新的update语句。
3.所有对实体进行删除的delete语句。
4.所有对集合元素进行删除、更新或插入的sql语句。
5.执行所有对集合进行插入的insert语句。
6.按照应用程序调用delete()方法的先后执行。
默认情况下:
1.当应用程序commit()方法的时候,先清理缓存,然后在向数据库提交事务。
2.当调用find()或iterator()时,如果对象属性发生变化,会先清理缓存,以保证查询结果能够反映持久化对象的最新状态。
3.显式调用flush()。
3.操作Session缓存的重要方法:
Session的清理模式:“session.setFlushMode(FlushMode.AUTO);”
清理缓存的模式 | Session的查询方法 | 事务的commit() | Session的Flush() |
FlushMode.AUTO | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FLushMode.NEVER | 不清理 | 不清理 | 清理 |
FlushMode.NEVER 模式受主键影响,当主键为identity时,受底层数据库影响所以NEVER无效。如果为increment时,由于这个主键不受底层数据库的影响,所以NEVER有效!
清理方法:
Session.flush();//清理、刷出数据库与缓存同步,但不提交事务。
Session.refresh();//刷新。让缓存与数据库同步。
Session.clear();//清空。清空缓存中的引用。
4.hibernate中Java对象的状态:
临时状态(transient):用new语句创建,没有被持久化(没有调用Session对方法),不处于session中,该对象成为临时对象。
持久化状态(persistent):已经被持久化,加入到session的缓存中。该状态的对象为持久化对象。
游离状态(detached):已经被持久化,但不处于session中(seesion清空或被delete)。该状态的对象为游离对象。
如:
程序代码 | 生命周期 | 状态 |
tx = session.beginTransaction(); Customer c = new Customer(“Tom”,new HashSet); | 开始生命周期 | 临时状态 |
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; | 结束生命周期 | 结束生命周期 |
1).临时对象特征
l 不处于session中,不被任何session关联。
l 数据库中没有对应的记录。
l 以下情况,对象进入临时状态:
1. new语句刚创建了一个对象。
2. session的delete方法使持久化对象或游离对象转变为临时对象,对于游离对象,该方法从数据库中删除记录,对于持久化对象,该方法从数据库中删除记录,还要删除缓存中的对象。
临时对象判断法:
1.OID为null。
2.具有version属性并取值为null。
3.在映射文件中为<id>元素设置了unsaved-value属性,并且OID属性取值与属性匹配。
4.在映射文件中为<version>元素设置了unsaved-value属性,并且version属性取值与属性匹配。
5.自定义了Interceptor实现类,并且isUnsaved方法返回Boolean.true。如果id的类型为long,则默认值为0,此时需要在配置文件中设置id的unsaved-value为0。
2) . 持久化对象特征
l 位于一个session缓存中,总是被一个session关联。
l 持久化对象和数据库记录相对应。
l 清理缓存时,会根据对象属性变化,同步更新数据库。
l save把临时对象转变为持久化对象。
l load或find或get返回的对象总是持久化状态。
l find方法返回的list存放的都是持久化对象。
l update、save、SaveOrUpdate和Lock方法使游离对象装变为持久化对象。
在实际的应用程序中应该避免一个java对象被多个session实例关联,会导致重复执行sql语句,并且极容易出现一些并发问题。
3).游离对象特征
l 不再位于session的缓存中,游离对象不被session关联。
l 游离对象由持久化转变过来的,因此在数据库中可能还存在与它对应的记录(前提条件是没有其他程序删除了这条记录)。
l close方法使缓存被清空,缓存中的所有的对象都变为游离对象。如果没有引用他们的话,他们就会结束生命周期。
l evict方法从缓存中删除一个持久化对象,使他变为游离态对象。当缓存中保存了大量的持久 化对象时,会消耗许多内存空间,使用该方法删掉一些对象来节省空间。
5.操作持久化对象的方法
1).save:
l 对象加入缓存,成为持久化对象。
2). update:
l 将游离对象转变为持久化对象。不论对象属性是否发生变化,该方法都会执行update操作。如果希望仅当属性变化时才执行update语句的话可进行如下配置:
<class name=“…”
table=“…”
select-before-update=“true”>
3). saveOrUpdate:
l 该方法同时包含save和update方法,如果参数是临时对象就用save方法,如果是游离对象就用update方法,如果是持久化对象就直接返回。
4) .load和get:
l 从数据库加载指定的OID持久化对象。
l 如果数据库中不存在该记录时,load方法会抛出异常,而get方法返回null。
5). delete
l 如果参数是持久化对象,就执行一个delete语句。若为游离对象,先使游离对象被session关联,使他变为持久化对象,然后计划执行一个delete语句。
6.与触发器协同工作
触发器的行为导致缓存与数据库中的数据不一致。解决办法是执行完操作后,立即调用session的flush方法和refresh方法,迫使缓存与数据库同步。
Session的update操作方法盲目的激活触发器,如果游离状态的对象的属性和数据库一致,则更新操作是多余的。
为避免这种情况:
<class name="" table="" select-before-update="true">
……
</class>
五、映射组成关系
这种关系是关联关系与聚集关系的融合。比如,客户有一个家庭地址和公司地址,而地址中又包含国家、省、城市、街道、邮编等信息。那么此时我们就应该将地址单独拿出来做成一个类。看老徐的UML图:
我们为的customer添加了两个地址属性,同时我们需要向映射文件中的class元素中添加两个compont子元素(此处我们只列出一个):
<compont name=“homeaddress” class=“mypack.Address”> <parent name=“customer” /> <property name=“street” type=“string” column=“HOME_STREET” /> <property name=“city” type = “string” cloumn=“HOME_CITY” /> <property name=“street” type = “string” cloumn=“HOME_STREET” /> </component> |
区分值和实体:
持久化类的属性分为两种:值(value)类型和实体类型(entity)类型。值类型没有OID,不能被单独持久化,他的生命周期依赖于所属的持久化类的对象的生命周期,组件类型就是一种值类型;实体类型有OID,可单独持久化。上面的address是值类型,customer是实体类型。
值类型不能单独拿出来给hibernate操作,否则将出错。
复合组成关系:
复合组成关系,就是上面组成关系的嵌套。Hibernate可以进行处理,但我们需要向映射文件中添加映射复合组成关系的信息。
六、hibernate映射类型
1.内置映射类型
Hibernate | java | sql | 取值范围 |
integer int | int Integer | INTEGER | 4 |
long | long 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 | String | VARCHAR | 变长串 |
boolean | boolean Boolean | BIT | 布尔 |
yes_no | boolean Boolean | CHAR(1) | 布尔 |
true_false | boolean Boolean | CHAR(1) | 布尔 |
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 |
3. 大对象类型的映射
Hibernate | java | sql |
binary | byte[] | VARBINARY(BLOB) |
text | String | CLOB |
serializable | 实现类 | BARBINARY(BLOB) |
clob | sql.Clob | CLOB |
blob | sql.Blob | BLOB |
*如果持久化类的字段为blob或clob类型,保存时需要包含两步:
Customer c = new Customer(); //先保存一个空的clob实例 c.setDescription(Hibernate.createClob()); session.save(c); session.flush(); //刷新记录 session.refresh(customer,LockMode.UPGRADE); oracle.sql.CLOB clob = c.getDescription(); //写入大文本 java.io.Writer pw = clob.getCharacterOutputStream(); pw.write(longtext); pw.close(); tx.commit(); session.close(); |
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 |
七、hibernate的检索策略
检索策略是hibernate中提高访问效率的重要核心,检索就是查询。我们下面来看看这些策略:
检索策略的作用域 | 可选的检索策略 | 默认的检索策略 | 运行时行为受影响的session的检索方法 |
类级别 | 立即 延迟 | 延迟 | load |
关联级别 | 立即 延迟 迫切左外连接检索 | 多对一和一对一关联:外连接检索 | load get |
一对多和多对多关联:立即检索 |
1.类级别
立即检索,class的lazy属性值为false。当我们一调用session.load方法时,Session便去查找数据库,并产生持久化对象。
延迟检索,class的lazy属性值为true。当我们一调用session.load方法时,session使用了一个动态代理对象代理我们的持久化对象。当我们调用持久化对象的get或set方法真正涉及到需要访问数据库的内容时,session才会去查找,此时才会初始化代理对象。在调用getId()时不会去查数据库,因为代理对象已通过load方法的第二个参数将id值赋值给了代理对象。但在调用session.get方法时,session会立即查数据库。
2.关联级别
立即检索,class的lazy属性值为false和关联类型的set元素的lazy属性为falze。比如,当查询customer信息时,也会立即查询与customer相关的订单表。执行了两次查询
延迟检索,如果set的lazy为true,则不会立即查询与customer相关的记录。而是当调用customer.getOrders().getXXX时才会执行查询Order的操作。
迫切左外连接,set的fecth属性值为join。当查询customer表时,使用左外连接一同查询出oreders。只执行一次查询。
上面是一对多的关联级别的检索。
使用延迟检索一定要保证在session.close之前将代理对象赋值完成(执行查询)。
今天的课程到此结束,检索策略还没完成。待续!~~
新年快乐!