hibernate注解(三)
我们继续介绍hibernate注解的相关内容
1. OneToOne懒加载问题
一对一注解时,若采用外键列进行实体的关联的话,懒加载问题是需要注意下的。如下:
Student表:
id int not null
name varchar(50) not null
card_id int not null
Card表:
id int not null
card_no varchar() not null
Student类
-
public
class Student {
-
-
private
int id;
-
private String name;
-
private Card card;
-
-
....
-
-
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
-
@JoinColumn(name =
"card_id")
-
public Card getCard() {
-
return card;
-
}
-
-
public void setCard(Card card) {
-
this.card = card;
-
}
-
}
Card类
-
public
class Card {
-
-
private
int id;
-
private String cardNo;
-
private Student student;
-
-
....
-
-
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy =
"card")
-
public Student getStudent() {
-
return student;
-
}
-
-
public void setStudent(Student student) {
-
this.student = student;
-
}
-
}
按照上述关系,运行代码后我们会发现,Student获取Card时可以懒加载,但Card获取Student时却无法懒加载,那么这是为什么呢?
原因:在Card表里由于没有关系字段,因此仅从Card表角度看无法知道拥有该卡的学生是谁(除非在Card表建立Student表的外键student_id,但由于冗余了关系字段,因此很少有人这么干吧【注意sql的join语句是考虑了两张表的】),而从Student角度不一样,由于含有card_id字段可能清楚知道该学生拥有一张卡。
正是由于上面的原因,因此当从Card获取Student时,hibernate为了确定Student表中到底有没有该Card,因此发了一条sql:select * from Student where card_id = ?,参数既是该Card的id,以此来维护Card与Student的关系。
还有一种解释,但需要理解hibernate的懒加载的机制:代理。
2. 懒加载原理
hibernate使用了代理(Proxy),对实体的调用会被代理接受和处理,hibernate可以设置这个代理被调用到的时候去加载数据,从而实现延迟加载。那么对于一个映射对象,要么它有值,要么它是null,对于null值建立代理是没多大作用的,而且也不能对null建立动态代理。那就是说hibernate在对延迟加载建立代理的时候要考虑这个映射的对象是否是null。如果是null不需要建立代理,直接把映射的值设置成null,如果映射的对象不为null,那么hibernate就建立代理对象。
简而言之,为null就不能懒加载,为代理对象才能懒加载。
那么解决上述问题的办法呢?
● Card类的getStudent改为manyToOne,并设置unique=true。缺点:需要将字段改为Set<Student>。
● 手动为Student建立代理对象。(不建议这么做,而且我也没试过)
● 采用no-proxy懒加载机制,对类用instrument进行增强,即用类增强器对二进制Class文件进行强化处理。(很少有人这么做,不推荐)
以下是利用ant调用hibernate类增强器对class文件进行强化处理,ant的build.xml脚本如下:
-
<?xml version="1.0" encoding="UTF-8"?>
-
<project name="hibernatelazy" default="instrument" basedir=".">
-
<property name="lib.dir" value="./lib"/>
-
<property name="classes.dir" value="./classes"/>
-
-
<path id="lib.class.path">
-
<fileset dir="${lib.dir}">
-
<include name="**/*.jar"/>
-
</fileset>
-
</path>
-
<target name="instrument">
-
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
-
<classpath path="${classes.dir}"/>
-
<classpath refid="lib.class.path"/>
-
</taskdef>
-
<instrument verbose="true">
-
<fileset dir="${classes.dir}/com/derek/known/hbm">
-
<include name="Knownquestions.class"/>
-
</fileset>
-
</instrument>
-
</target>
-
</project>
其中注意:
<property name="lib.dir" value="./lib"/>所需的JAR文件路径。
<property name="classes.dir" value="./classes"/>编译输出路径。
我把build.xml放在了WEB-INF目录下,输出路径就设置为该目录下的classes目录,待增强的字节码文件为classes目录下的com/derek/known/hbm/Knownquestions.class; 在命令行下切换到此目录,执行ant命令,即生成新的Knownquestions.class。
● 放弃懒加载,改为join的加载策略,或者使用hql:from Card t inner join fetch t.student r
3. 懒加载注解方式详解
● FetchType(@OneToOne、@OneToMany等注解里的fetch属性对应的类,本身没有注解)
JPA标准的通用加载策略注解属性。FetchType可选值意义与区别如下:
FetchType.LAZY:懒加载,在第一次访问关联对象的时候加载
FetchType.EAGER:立刻加载,在查询主对象的时候同时加载关联对象。
● @Fetch
hibernate定义了加载关联关系的获取策略。Fetch可选值意义与区别如下:
FetchMode.JOIN:始终立刻加载,使用外连(outer join)查询的同时加载关联对象,忽略FetchType.LAZY设定。
FetchMode.SELECT:支持懒加载(除非设定关联属性lazy=false),当访问每一个关联对象时加载该对象,会累计产生N+1条sql语句
FetchMode.SUBSELECT:支持懒加载(除非设定关联属性lazy=false),在访问第一个关联对象时加载所有的关联对象。会累计产生两条sql语句。
● @LazyToOne
hibernate定义了@ManyToOne 和@OneToOne 关联的延迟选项。LazyToOne可选值意义与区别如下:
LazyToOneOption.PROXY:基于代理的延迟加载。
LazyToOneOption.NO_PROXY:基于字节码增强的延迟加载 - 注意需要在构建期处理字节码增强。
LazyToOneOption.FALSE:非延迟加载的关联。
● @LazyCollection
hibernate定义了@ManyToMany和@OneToMany 关联的延迟选项。LazyCollection可选值意义与区别如下:
LazyCollectionOption.TRUE:集合具有延迟性,只有在访问的时候才加载。
LazyCollectionOption.EXTRA:集合具有延迟性,并且所有的操作都会尽量避免加载集合, 对于一个巨大的集合特别有用,因为这样的集合中的元素没有必要全部加载。
LazyCollectionOption.FALSE:非延迟加载的关联。
延迟和获取选项的等效注解
Annotations | Lazy | Fetch |
---|---|---|
@[One|Many]ToOne](fetch=FetchType.LAZY) | @LazyToOne(PROXY) | @Fetch(SELECT) |
@[One|Many]ToOne](fetch=FetchType.EAGER) | @LazyToOne(FALSE) | @Fetch(JOIN) |
@ManyTo[One|Many](fetch=FetchType.LAZY) | @LazyCollection(TRUE) | @Fetch(SELECT) |
@ManyTo[One|Many](fetch=FetchType.EAGER) | @LazyCollection(FALSE) | @Fetch(JOIN) |