JPA referencedColumnName 非主键列时FetchType.LAZY失效处理

在Spring JPA 的级联操作中,当配置referencedColumnName为非主键列,FetchType.LAZY就会失效。

下面我们通过一个例子来看一看这个问题,以及 通过 PersistentAttributeInterceptable 接口来解决这个问题。

referencedColumnName为主键列时

下面看一个 People 和 Address 的referencedColumnName为主键列的例子。

People

package com.johnfnash.learn.domain;

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

@Entity
public class People implements Serializable {
    
    private static final long serialVersionUID = -4580187061659309868L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;
    
    @Column(name = "name", nullable = true, length = 20)
    private String name;

    @Column(name = "sex", nullable = true, length = 1)
    private String sex;

    @Column(name = "birthday", nullable = true)
    private Timestamp birthday;
    
    // People 是关系的维护段,当删除 people时,会级联删除 address
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    // people 中的 address_id 字段参考 address 表的id字段
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

    public People() {
        super();
    }

    public People(String name, String sex, Timestamp birthday, Address address) {
        super();
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
        this.address = address;
    }

    // getter, setter

    @Override
    public String toString() {
        return "People [id=" + id + ", name=" + name + ", sex=" + sex + ", birthday=" + birthday + "]";
    }

}

由于 @JoinColumn 的配置,数据库中 people 表中会有一个 address_id 字段,通过外键引用 address 表的主键列(id)。

Address

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"}) 
public class Address implements Serializable {
    
    private static final long serialVersionUID = -4960690362029661737L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;
    
    @Column(name = "phone", nullable = false, length = 11)
    private String phone;
    
    @Column(name = "zipcode", nullable = true, length = 6)
    private String zipcode;
    
    @Column(name = "address", nullable = true, length = 100)
    private String address;

    //如果不需要根据Address级联查询People,可以注释掉
    //  @OneToOne(mappedBy = "address", cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false)
    //  private People people;
    
    public Address() {
        super();
    }
    
    public Address(String phone, String zipcode, String address) {
        this.phone = phone;
        this.zipcode = zipcode;
        this.address = address;
    }

    // getter, setter
    // ......

    @Override
    public String toString() {
        return "Address [id=" + id + ", phone=" + phone + ", zipcode=" + zipcode + ", address=" + address + "]";
    }
    
}

测试

创建 PeopleRepository,如下:

public interface PeopleRepository extends JpaRepository<People, Long> {
}

测试类:

import java.sql.Timestamp;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.johnfnash.learn.domain.Address;
import com.johnfnash.learn.domain.People;
import com.johnfnash.learn.repository.PeopleRepository;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PeopleAddressTests {

    @Autowired
    private PeopleRepository peopleRepository;
    
    @Test
    public void savePeople() {
        Address address = new Address("12345678901", "54002", "xxxx road");
        People people = new People("ZS", "1", new Timestamp(System.currentTimeMillis()), address);
        peopleRepository.save(people);
        System.out.println(people.getId());
    }
  
    @Test
    public void findPeople() {
        People people = peopleRepository.getOne(1L);
        System.out.println(people);
        //System.out.println(people.getAddress());
    }    
    
}

先执行 savePeople 方法,再执行 findPeople 方法。通过下面的log,我们可以看到 people 对象中的address确实没有加载。

Hibernate: select people0_.id as id1_4_0_, people0_.address_id as address_5_4_0_, people0_.birthday as birthday2_4_0_, people0_.name as name3_4_0_, people0_.sex as sex4_4_0_ from people people0_ where people0_.id=?
People [id=1, name=ZS, sex=1, birthday=2019-01-19 18:09:57.0]

打开 findPeople 中的注释,如下

@Test
public void findPeople() {
    People people = peopleRepository.getOne(1L);
    System.out.println(people);
    System.out.println(people.getAddress());
}

再次执行 findPeople 方法,输出的日志如下

Hibernate: select people0_.id as id1_4_0_, people0_.address_id as address_5_4_0_, people0_.birthday as birthday2_4_0_, people0_.name as name3_4_0_, people0_.sex as sex4_4_0_ from people people0_ where people0_.id=?
People [id=1, name=ZS, sex=1, birthday=2019-01-19 18:09:57.0]
Hibernate: select address0_.id as id1_0_0_, address0_.address as address2_0_0_, address0_.phone as phone3_0_0_, address0_.zipcode as zipcode4_0_0_ from address address0_ where address0_.id=?
Address [id=1, phone=12345678901, zipcode=54002, address=xxxx road]

这次因为用到了 address 信息,所以进行了查询。

referencedColumnName为非主键列时

依旧使用上面的例子,只是 People 类的 address 属性中 referencedColumnName 引用的字段改成非主键列 address。

// People 是关系的维护段,当删除 people时,会级联删除 address
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
// people 中的 address_id 字段参考 address 表的id字段
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;

通过下面的测试方法进行测试

@Test
public void findPeople() {
    People people = peopleRepository.getOne(1L);
    System.out.println(people);
    //System.out.println(people.getAddress());
}

输出日志如下,我们可以看到address对象立即就加载了,并没有延迟加载。

Hibernate: select people0_.id as id1_4_0_, people0_.address_id as address_5_4_0_, people0_.birthday as birthday2_4_0_, people0_.name as name3_4_0_, people0_.sex as sex4_4_0_ from people people0_ where people0_.id=?
Hibernate: select address0_.id as id1_0_0_, address0_.address as address2_0_0_, address0_.phone as phone3_0_0_, address0_.zipcode as zipcode4_0_0_ from address address0_ where address0_.address=?
People [id=1, name=ZS, sex=1, birthday=2019-01-19 18:47:31.0]

查询资料,网上说 ,当referencedColumnName指向的列不是主键列时,hibernate去查询address时,理论上懒加载是需要使用proxy的,需要proxy的情况是,知道每个people都对应了一个address对象,但是这个时候,hibernate不知道是不是每一个people都对应于一个address,那么这个时候,他就回去查询一次,确定是否有address,那么他都去查询一次了,所以这个时候懒加载也就失效了。

那么这种情况下有没有什么办法对address进行懒加载呢?在hibernate中,可以通过继承 PersistentAttributeInterceptable 接口来实现延迟加载字段。下面将进行介绍。

PersistentAttributeInterceptable 实现字段延迟加载

首先,修改一下 People 类,继承 PersistentAttributeInterceptable 接口并实现相应方法,修改注解的设置以及修改一下相关字段的set方法和get方法。

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;

@Entity
public class People implements PersistentAttributeInterceptable, Serializable {
    
    private static final long serialVersionUID = -4580187061659309868L;

    private PersistentAttributeInterceptor interceptor;
    
    private Long id;
    
    private String name;
    
    private String sex;
    
    private Timestamp birthday;
    
    private Address address;

    public People() {
        super();
    }

    public People(String name, String sex, Timestamp birthday, Address address) {
        super();
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
        this.address = address;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name", nullable = true, length = 20)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "sex", nullable = true, length = 1)
    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Column(name = "birthday", nullable = true)
    public Timestamp getBirthday() {
        return birthday;
    }

    public void setBirthday(Timestamp birthday) {
        this.birthday = birthday;
    }

    // People 是关系的维护段,当删除 people时,会级联删除 address
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    // people 中的 address_id 字段参考 address 表的id字段
    @JoinColumn(name = "address_id", referencedColumnName = "address")
    @LazyToOne(LazyToOneOption.NO_PROXY)
    public Address getAddress() {
        if (interceptor != null) {
            return (Address) interceptor.readObject(this, "address", address);
        }   
        return address;
    }

    public void setAddress(Address address) {
        if (interceptor != null) {
            this.address = (Address) interceptor.writeObject(this, "owner", this.address, address);
            return;
        }
        this.address = address;
    }

    @Override
    public String toString() {
        return "People [id=" + id + ", name=" + name + ", sex=" + sex + ", birthday=" + birthday + "]";
    }

    @Override
    public PersistentAttributeInterceptor $$_hibernate_getInterceptor() {
        return interceptor;
    }

    @Override
    public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor) {
        this.interceptor = interceptor;
    }
    
}

再次执行测试方法 findPeople,发现address又可以延迟加载了。

Hibernate: select people0_.id as id1_4_0_, people0_.birthday as birthday2_4_0_, people0_.name as name3_4_0_, people0_.sex as sex4_4_0_ from people people0_ where people0_.id=?
People [id=1, name=ZS, sex=1, birthday=2019-01-19 18:47:31.0]

注意事项:实现PersistentAttributeInterceptor的实体,重写PersistentAttributeInterceptor的get,set方法后才可以使用

参考

  1. 使用@ManyToOne(fetch=FetchType.LAZY),懒加载无效,这是怎么回事

  2. Hibernate 5 & JPA 2.1 延迟加载大字段属性

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值