Hibernate的延迟加载

341 查看

Hibernate中,延迟加载针对属性类别可以分为两类,一类是延迟属性加载,另一类是延迟关联实体加载。

属性延迟加载

属性有可以分为两种类型:一种是集合属性,一种是非集合属性(如String、Integer……)。

集合属性

集合属性的延迟加载通过PersistentSet、 PersistentList、PersistentBag、PersistentMap、PersistentSortedMap、PersistentSortedSet作为代理类来实现,代理类中保存了session以及owner属性,owner属性表示了集合属性所属的one侧的实体。

非集合属性

非集合属性的延迟加载相对比较复杂。仅通过@Basic(fetch = FetchType.LAZY)注解是无法实现延迟加载的。需要让实体实现FieldHandled接口,声明FieldHandler属性,通过拦截器原理注入对应的FieldHandler属性,起到类似于上述代理类的作用,FieldHandler同样也保持了session,以及需要延迟加载的属性。下面的代码实现了非集合属性的延迟加载

@Basic(fetch = FetchType.LAZY)
@Column(name="CONTENT")
public String getContent() {
    if (fieldHandler != null) {
        return (byte[]) fieldHandler.readObject(this, "content", content);
    }
    return null;
}
public void setContent(byte[] content) {
    this.content = content;
}
@Override
public void setFieldHandler(FieldHandler handler) {
    this.fieldHandler = handler;
}
@Override
public FieldHandler getFieldHandler() {
    return this.fieldHandler;
}

Hibernate官网对非结合属性的延迟加载有如下的评论:

Lazy property loading requires buildtime bytecode instrumentation. If your persistent classes are not enhanced, Hibernate will ignore lazy property settings and return to immediate fetching.
A different way of avoiding unnecessary column reads, at least for read-only transactions, is to use the projection features of HQL or Criteria queries. This avoids the need for buildtime bytecode processing and is certainly a preferred solution.

大致的意思就是:应该是因为,我们并未用到编译时字节码增强技术的原因。如果只对部分property进行延迟加载的话,hibernate还提供了另外的方式,也是更为推荐的方式,即HQL或者条件查询。
更为推荐的方式说白了就是在查询的时候就过滤掉不需要的属性,以下列出HQL和Criterial的两种实现方法:

// criterial实现
criteria.setProjection(
                Projections.projectionList().add(Projections.property("id"), "id")
                        .add(Projections.property("age"), "age")).setResultTransformer(
                Transformers.aliasToBean(User.class));

// HQL实现
Query query = session.createQuery("select u.id as id,u.age as age from User u where u.id=2");
query.setResultTransformer(Transformers.aliasToBean(User.class));

关联实体延迟加载

关联实体延迟加载分两种情况,一种是多对一,另一种是一对一。

多对一关联

关联实体是多个实体时(包括一对多、多对多):此时关联实体将以集合的形式存在,Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。

一对一关联

关联实体是单个实体时(包括一对一、多对一):当 Hibernate 加载某个实体时,延迟的关联实体将是一个动态生成代理对象。Hibernate 使用 Javassist 项目动态生成的代理类——当 Hibernate 延迟加载关联实体时,将会采用 Javassist 生成一个动态代理对象,这个代理对象将负责代理“暂未加载”的关联实体。但是在一对一关系中,延迟加载是有陷阱的。一对一关联一般有两种形式,一种是主键关联;另一种是外键关联。

主键关联

数据表Husband,两列属性id,name
数据表Wife,两列属性id,name

Husband实体:
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
@PrimaryKeyJoinColumn
getWife()
@GenericGenerator(name = "Wife", strategy = "foreign", parameters = { @Parameter(name = "property", value = "husband") })
Wife实体:
@OneToOne(mappedBy = "wife", fetch = FetchType.LAZY)
getHusband()

以上是hibernate中的配置。其中“optional=false”的配置时关键,否则即使配置了fetch策略为lazy,也无法做到在获取husband实体的时候延迟加载wife实体。optional的默认值是true,表示关联的实体可以为null。在一对一的延迟加载中,hibernate并非一定对需要延迟加载的实体生成一个动态代理对象,而是当被关联的实体确定不为null时,才会生成,否则直接将其置为null。所以问题就来了,对于两个通过主键关联的一对一实体,在获取到其中一个实体后,要判断与之关联的实体是否存在,则必须要再查询一次数据库才可以。这也就是为什么在设置了延迟加载策略后,hibernate还是立即发送了一次查询请求给数据库。
要解决一对一关系中的延迟加载,共有两种方法:一种就是上面提到的,把optional设置为false,即关联的实体一定不为null。这样一来,hibernate就会立即为配置延迟加载的实体生成一个动态代理类。
但是这又存在一个坑,在创建实体husband的时候,其主键为null(还未生成),wife的主键也为null,此时save的话,hibernate理论上应该先insert husband实体,然后用生成出来的husband_id作为wife的主键进行insert。可事与愿违,hibernate认为此情况违背了optional=false的假设,故会抛异常IdentifierGenerationException,即wife的id为null。具体原因不太知晓,猜测可能是因为持久化的顺序,先持久化husband,此时wife的主键为null,但是你又配置了optional=false,故前后矛盾而抛出异常。

外键关联

数据表Husband,3列属性id,name, wife_id
数据表Wife,两列属性id,name
其中husband_id与Husbande中的id关联

Husband实体:
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "wife_id", , referencedColumnName = "id")
getWife()
Wife实体:
@OneToOne(mappedBy = "wife", fetch = FetchType.LAZY)
getHusband()

这样一来,直接可以通过husband中的wife_id的null与否来判断wife实体是否为null。