JPA: How to fetch eagerly an embedded element coll

2020-06-12 04:45发布

问题:

Consider the following model

@Entity
// JPA and JAXB annotations here
public class Employee implements Serializable {
     // other fields, annotations, stuffs
     ...
     @ElementCollection(fetch = FetchType.LAZY,
        targetClass = Address.class)
     @CollectionTable(name = "employee_address",
        schema = "hris",
        joinColumns = @JoinColumn(name = "employee_id",
                nullable = false,
                referencedColumnName = "employee_id",
                foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT)))
     protected Set<Address> addresses;
     // setters, getters
     ...
 }

 @Embeddable
 // JAXB annotations here
 public class Address implements Serializable {
      // fields, setters, getters
 }

The Address class is annotated with @Embeddable annotation, and the Employee class has an embedded element collection of addresses. The element collection's fetch is set to FetchType.LAZY. Now, I would like to create a @NamedQuery that would retrieve all employees with addresses eagerly initialized. Knowing that JOIN FETCH will only work with entity collections annotated with @OneToMany or @ManyToMany based on JPA 2.1, how would I create a valid JPQL query that would allow me to eagerly retrieve embedded element collections?

回答1:

In the JPA 2.1 specification (JSR 338) I cannot find any hint that fetch joins only work on entity relationships (but not embeddables). JSR 338, section 4.4.5.3 even states:

A FETCH JOIN enables the fetching of an association or element collection as a side effect of the execution of a query.

As another hint the following minimal example (essentially resembling yours) executed with Hibernate 4.3.11 as JPA provider results in a single query:

Address embeddable:

@Embeddable public class Address { private String city; }

Employee entity:

@Entity public class Employee {

    @Id private Long id;

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "address", 
           joinColumns = @JoinColumn(name="employee_id"))
    private Set<Address> addresses;

}

JPQL Query:

em.createQuery("select e from Employee e join fetch e.addresses").getResultList();

Resulting SQL query:

select
    employee0_.id as id1_1_,
    addresses1_.employee_id as employee1_1_0__,
    addresses1_.city as city2_5_0__ 
from
    Employee employee0_ 
inner join
    address addresses1_ on employee0_.id=addresses1_.employee_id

So the above JPQL query seems to solve your problem.



回答2:

By the way, more effective way might be not to use join, but subselect

@Fetch(FetchMode.SUBSELECT)
@BatchSize(size=500)

it makes two selects, instead of one, but doesn't produce so much ambiguity.