简体   繁体   中英

JPA/Hibernate FetchType.LAZY is not working

Well, there are many questions exactly with this title but none of them have proper answers or they are not exactly the same as mine.

I have two entities:

Person:

@Entity
@Table(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
@Access(AccessType.FIELD)
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "firstname")
    private String firstName;

    @Column(name = "lastname", length = 100, nullable = false, unique = false)
    private String lastName;

    @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.MERGE, mappedBy="owner")
    private Set<Car> cars;

    public Long getId() {

        return id;
    }

    public void setId(Long id) {

        this.id = id;
    }

    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;
    }

    public Set<Car> getCars() {

        return cars;
    }

    public void setCars(Set<Car> cars) {

        this.cars = cars;
    }

    @Override
    public String toString() {

        return String.format("(%d, %s, %s)",id, firstName, lastName);
    }
}

And Car:

@Entity
@Table(name = "Car")
@Inheritance(strategy = InheritanceType.JOINED)
@Access(AccessType.FIELD)
public class Car {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.MERGE)
    @JoinColumn(name="id_person", columnDefinition="BIGINT")
    private Person owner;

    @Column(name="name")
    private String name;

    @Column(name="model")
    private String model;

    public Long getId() {

        return id;
    }

    public void setId(Long id) {

        this.id = id;
    }

    public Person getOwner() {

        return owner;
    }

    public void setOwner(Person owner) {

        this.owner = owner;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) {

        this.name = name;
    }

    public String getModel() {

        return model;
    }

    public void setModel(String model) {

        this.model = model;
    }

    @Override
    public String toString() {

        return String.format("(%d, %s, %s, %s)", id, name, model, owner);
    }
}

Also I've defined a foreign key constraints in my Mysql database between Person and Car table on id_person column

My persistence.xml file is as followings:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="PersistenceUnit" transaction-type="RESOURCE_LOCAL">

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
          <!-- Configuring JDBC properties -->
          <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/hibernatedb" />
          <property name="javax.persistence.jdbc.user" value="root" />
          <property name="javax.persistence.jdbc.password" value="DA_PASSWORD" />
          <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />

          <!-- Hibernate properties -->
          <property name="hibernate.show_sql" value="true" />
          <property name="hibernate.format_sql" value="true" />
          <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
          <property name="hibernate.hbm2ddl.auto" value="validate" />

          <!-- Configuring Connection Pool -->
          <property name="hibernate.c3p0.min_size" value="5" />
          <property name="hibernate.c3p0.max_size" value="20" />
          <property name="hibernate.c3p0.timeout" value="500" />
          <property name="hibernate.c3p0.max_statements" value="50" />
          <property name="hibernate.c3p0.idle_test_period" value="2000" />
        </properties>
    </persistence-unit>
</persistence>

Inside my code, I try to select Cars using Criteria query as following:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> q = cb.createQuery(Car.class);
Root<Car> root = q.from(Car.class);

q.select(root);

When I get the results and print them

TypedQuery<Car> typedQuery = entityManager.createQuery(q);
List<Car> cars = typedQuery.getResultList();
log.info("*****************************{}", cars);

To my surprise, It prints:

*****************************[(1, Hiundai, 2016, (1, Homer1530962140, Simpson)), (2, Benz, 2016, (1, Homer1530962140, Simpson)), (3, Benz,
2017, (2, Homer12935192, Simpson))]

That means for each car items, owners eagerly have been fetched too!

Here are the database query logs:

2017-02-17T14:02:58.324926Z   391 Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
2017-02-17T14:02:58.325405Z   391 Query SHOW COLLATION
2017-02-17T14:02:58.335552Z   391 Query SET NAMES latin1
2017-02-17T14:02:58.335772Z   391 Query SET character_set_results = NULL
2017-02-17T14:02:58.336160Z   391 Query SET autocommit=1
2017-02-17T14:02:58.336349Z   391 Query SET autocommit=0
2017-02-17T14:02:58.720821Z   391 Query SHOW FULL TABLES FROM `hibernatedb` LIKE 'Car'
2017-02-17T14:02:58.724527Z   391 Query SHOW FULL TABLES FROM `hibernatedb` LIKE 'Car'
2017-02-17T14:02:58.725337Z   391 Query SHOW FULL COLUMNS FROM `Car` FROM `hibernatedb` LIKE '%'
2017-02-17T14:02:58.729899Z   391 Query SHOW FULL TABLES FROM `hibernatedb` LIKE 'Person'
2017-02-17T14:02:58.730468Z   391 Query SHOW FULL TABLES FROM `hibernatedb` LIKE 'Person'
2017-02-17T14:02:58.730887Z   391 Query SHOW FULL COLUMNS FROM `Person` FROM `hibernatedb` LIKE '%'
2017-02-17T14:02:59.022835Z   391 Query select car0_.id as id1_0_, car0_.model as model2_0_, car0_.name as name3_0_, car0_.id_person as id_perso4_0_ from Car car0_
2017-02-17T14:02:59.041016Z   391 Query SHOW WARNINGS
2017-02-17T14:02:59.045266Z   391 Query select person0_.id as id1_1_0_, person0_.firstname as firstnam2_1_0_, person0_.lastname as lastname3_1_0_ from Person person0_ where person0_.i
d=1
2017-02-17T14:02:59.059184Z   391 Query SHOW WARNINGS
2017-02-17T14:02:59.064163Z   391 Query select person0_.id as id1_1_0_, person0_.firstname as firstnam2_1_0_, person0_.lastname as lastname3_1_0_ from Person person0_ where person0_.i
d=2
2017-02-17T14:02:59.065827Z   391 Query SHOW WARNINGS
2017-02-17T14:02:59.070262Z   391 Query rollback
2017-02-17T14:02:59.070468Z   391 Quit

It is quite obvious that a separate query is issued to get Person information while I don't seem to ask such a thing in my code.

Why is this happening?

You requested the owner information when you converted the car to a string.

@Override
public String toString() {

    return String.format("(%d, %s, %s, %s)", id, name, model, owner);
}

At that point, it had to retrieve the owner to execute your toString().

log.info("*****************************{}", cars);

When you execute this:

  1. Logging framework calls toString() on a list
  2. toString() is called on each Car
  3. toString method of the Car class calls toString() method of the Person class. And this is the part where "magic" happens. Turns out that Hibernate uses generated proxy classes to enable lazy loading for some associations (one-to-one, many-to-one). In other words, Hibernate initializes field Car.owner with a reference to a proxy class that extends the Person class. This proxy class contains additional logic that handles lazy loading.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM