当被要求加载一个惰性字段时,Hibernate会加载所有惰性字段

时间:2014-08-25 13:35:26

标签: hibernate lazy-loading

我在学生和地址之间有一个二者关系。我希望Student的firstName和lastName字段是延迟加载的。我也想要地址字段懒惰。

这些是我的实体clasess:

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @LazyToOne(LazyToOneOption.NO_PROXY)
    private Address address;

    @Basic(fetch = FetchType.LAZY)
    @Column(name = "first_name")
    private String firstName;

    @Basic(fetch = FetchType.LAZY)
    @Column(name = "last_name")
    private String lastName;

    public Long getId() {
        return id;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + ", address=" + address + ", firstName="
                + firstName + ", lastName=" + lastName + "]";
    }

}

地址类:

@Entity
@Table(name = "addresses")
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "s_id")
    private Student student;

    @Column
    private String street;

    public Long getId() {
        return id;
    }

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

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

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

}

我的测试方法看起来像这样(Java 8 lambda只是在后面创建一个实体管理器并在事务中执行所有操作):

@Test
public void dummyTest() {
    JPA_UTILS.runInTransaction(e -> {
        Student s = e.find(Student.class, 150L);
        System.out.println("----------++++++++++++++-----------");
        s.getFirstName();
        System.out.println("----------++++++++++++++-----------");
    });
}

所以这里我从数据库加载一个现有的学生,然后获取lazy属性firstName(映射到first_name列)。问题是Hibernate不仅加载firstName而且加载lastName和地址字段:

just.hibernate.one2one.TestApp > dummyTest STANDARD_OUT
    Hibernate: 
        select
            student0_.id as id1_1_0_ 
        from
            students student0_ 
        where
            student0_.id=?
    ----------++++++++++++++-----------
    Hibernate: 
        /* sequential select
            just.hibernate.one2one.Student */ select
                student_.first_name as first_na2_1_,
                student_.last_name as last_nam3_1_ 
            from
                students student_ 
            where
                student_.id=?
    Hibernate: 
        /* load just.hibernate.one2one.Address */ select
            address0_.id as id1_0_1_,
            address0_.street as street2_0_1_,
            address0_.s_id as s_id3_0_1_,
            student1_.id as id1_1_0_ 
        from
            addresses address0_ 
        left outer join
            students student1_ 
                on address0_.s_id=student1_.id 
        where
            address0_.s_id=?
    ----------++++++++++++++-----------

我不想要这种行为,我只想加载我要求的内容。有人可以帮我找到问题吗?

由于

UPDATE1:

使用maven完成仪表,我只发布相关代码(我已尝试使用gradle并获得相同的结果)

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <tasks>
                        <taskdef name="instrument"
                            classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
                            <classpath>
                                <path refid="maven.runtime.classpath" />
                                <path refid="maven.plugin.classpath" />
                            </classpath>
                        </taskdef>
                        <instrument verbose="false">
                            <fileset dir="${project.build.outputDirectory}">
                                <include name="**/*.class">
                                </include>
                            </fileset>
                        </instrument>
                    </tasks>
                </configuration>
            </plugin>

4 个答案:

答案 0 :(得分:4)

有两个基本资源可以正确解决这个问题。首先,属性的延迟加载不是标准设置。参见:

  

Hibernate支持延迟获取各个属性。此优化技术也称为提取组。 请注意,这主要是营销功能;优化行读取比列读取的优化要重要得多。但是,只加载类的某些属性在极端情况中可能很有用。

其次,根据上述文档部分,有一篇详细的文章:

虽然这与NHibernate有关,但内容是相同的,因为这个功能来自Hibernate。

我们可以在那里阅读:

  

多个懒惰属性怎么样? NHibernate支持它们,但你需要记住一件事。 NHibernate将加载所有实体的延迟属性,而不仅仅是那些立即访问的属性。同样的道理,你不能急于从HQL中加载一些实体的惰性属性。

同样,我们可以理解,属性的lazy设置不是标准设置。它适用于某些非常具体的情况,例如:

  

此功能主要用于特殊情况,例如 Person.Image Post.Text 等。像往常一样,请谨慎使用它。

摘要:延迟加载属性(ValueTypes / strings,not associations)适用于特殊情况。它应该有助于我们避免加载一些巨大的列。但是,一旦访问了这样的属性,所有其他属性也会加载

答案 1 :(得分:0)

对于one2one关系,我认为以下文章解释得很好:  http://justonjava.blogspot.co.uk/2010/09/lazy-one-to-one-and-one-to-many.html

关于字段lastName,这是一个基本类型字段。 在Hibernate中,基本类型字段总是一次被查询,如果你声明它们被加载为懒惰则重新进行查询。

答案 2 :(得分:0)

我不知道你对它的了解所以我会解释;如果你已经知道大部分内容我为任何冒犯道歉。

根据经验,我们最初可以这样说:

@Entity
public class Student

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
 private Address address;

现在观察术语FetchType.Eager。所以在'Page1'中我们可以说有一个学生列表。然后,如果你点击学生名称,它应该转到'Page2'。因此,在“Page2”中,您需要显示学生的地址。

因此,在代码中,如果执行FetchType.Eager,则不必执行两个查询。所有你要做的就是学生s =“从学生中选择s,其中id ='1'”;

然后将此Object发送到'Page2'并执行System.out.println(s.getAddress());

现在只有当FetchType为Eager时才会这样。但如果它是懒惰的。您需要另一个查询,因为之前的错误会抛出一个惰性异常。那么你需要这样做 “从第2页的地址a中选择一个a.student.id =':id',然后执行System.out.println(resultOfSecondQuery);

现在这是我的经验,因为我们首先获得了EAGER然后它被改为LAZY并且错误开始在多个地方突然出现。因为我们不能再做s.getAddress()了。

在你的测试块中,看起来你得到了所有学生,然后打印出名字。您是否曾尝试在代码中打印出地址?它打印出来还是抛出错误?我不知道你的第四个街区在做什么。你写过那个或是Hibernate做了什么吗?

之证件:

http://java.dzone.com/articles/lazyeager-loading-using http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-hibspec-singleassoc-fetching http://howtoprogramwithjava.com/hibernate-eager-vs-lazy-fetch-type/

同样在jboss-hibernate的文档中说,你不需要延迟加载像lastName这样的简单属性。我知道你可能只是练习,但无论如何,lastName通常都是急切的。然而,像地址这样的东西是不同的。

答案 3 :(得分:0)

使用Hibernate,您可以使用@Basic(fetch=FetchType.LAZY)懒惰地加载列,但为此工作Hibernate需要字节码检测来拦截属性访问请求并按需发出辅助选择语句。如果使用Maven字节码增强插件,然后enableLazyInitialization配置属性必须设置为true:

<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
    <execution>
        <configuration>
            <failOnError>true</failOnError>
            <enableLazyInitialization>true</enableLazyInitialization>
        </configuration>
        <goals>
            <goal>enhance</goal>
        </goals>
    </execution>
</executions>
</plugin>

以上插件有其自身的缺点。因此,如果您只需要某些列,则可以编写命名查询,仅请求

这些列
TypedQuery<ViewTemplate> query=getEntityManager().createQuery("SELECT NEW com.abcd.test.Table(v.id,v.name,v.type) FROM ViewTemplate v where v.id>1",Table.class).getResultList();

在您的实体中定义一个只包含这些值的构造函数