(1+N) selects with OnetoOne associations
Considering the following model:
@Entity
public class User {
@Id
@Column(name = "USER_ID")
private Long userId;
@Column(name = "FIRST_NAME")
private String firstName;
@Column(name = "LAST_NAME")
private String lastName;
@OneToOne
@PrimaryKeyJoinColumn
private UserExt userExt;
... //getters and setters
}
@Entity
public class UserExt {
@Id
@Column(name="USER_ID")
private Long id;
private String cdpId;
private Date lastChanged;
... //getters and setters
}
when executing :
Query query = session.createQuery("from User");
List<User> list = query.list();
Hibernate executes
Hibernate: select user0_.USER_ID as USER1_0_, user0_.FIRST_NAME as FIRST2_0_, user0_.LAST_NAME as LAST3_0_, user0_.EXT_USERNAME as EXT4_0_ from USER user0_
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
...
...
Using a query with specific properties works (select u.firstName, u.userExt.cdpId).
However since I want the full User Entity ("from开发者_如何转开发 User"), hibernate generates one select for each result row in the first.
I don't get it since the default fetch strategy should be LAZY not EAGER. Forcing it to LAZY didn't fix the problem.
There are two problems preventing lazy loading here:
- The default fetch strategy of
OneToOne
isEAGER
(and keep in mind thatLAZY
is just a hint to the persistence provider). LAZY
can only work on aOneToOne
association if the association is non-nullable (at least without using bytecode instrumentation).
9.1.23 OneToOne Annotation
The
OneToOne
annotation defines a single-valued association to another entity that has one-to-one multiplicity. It is not normally necessary to specify the associated target entity explicitly since it can usually be inferred from the type of the object being referenced.Table 16 lists the annotation elements that may be specified for a
OneToOne
annotation and their default values.@Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface OneToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default EAGER; boolean optional() default true; String mappedBy() default ""; }
I tested the following:
@OneToOne(optional = false, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private UserExt userExt;
And confirm that a simple from User
only loads all the users
Hibernate: select user0_.USER_ID as USER1_0_, user0_.FIRST_NAME as FIRST2_0_, user0_.LAST_NAME as LAST3_0_ from USER user0_
And doesn't perform N additional queries, the UserExt
are loaded lazily.
So, if you association is mandatory, use the appropriate mapping :) And if it is non-mandatory, you'll have to either:
- use bytecode instrumentation and no-proxy fetch (see related question below)
- use a fake ManyToOne instead (didn't test this mapping with a shared primary key)
- eager load the UserExt using a
join fetch
to avoid the N subsequent selects (of course, this somehow defeats the point of a separate table)
Note that Hibernate >= 3.x ignores the Fetch annotation when you use the Query interface. In that case, you need to write that explicitly. this is an example:
EntityManager em = [...]
[...]
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> usersRoot = criteria.from(User.class);
usersRoot.fetch("Address", JoinType.LEFT);
List<User> users = em.createQuery(criteria).getResultList();
Related questions
- OneToOne relationship with shared primary key generates n+1 selects; any workaround?
- Making a OneToOne-relation lazy
- fetchmode in jpa 2 criteriaquery
Reference
- JPA 1.0 specification
- Section 9.1.23 "OneToOne Annotation"
Default fetching strategy when using -ToOne such as @ManyToOne and @OneToOne is fetch=FetchType.EAGER NOT fetch=FetchType.LAZY
But Hibernate HQL overrides default fetching strategy. If you want to retrieve a fully initialized object by using just one query you must call
from
User u
left join fetch
u.userExt
use optional =true with a one-to-one relationship like this to avoid the n+1 issue with your own Fetch Strategy.
@OneToOne(fetch = FetchType.LAZY, optional=true)
@PrimaryKeyJoinColumn
精彩评论