FetchMode如何在Spring Data JPA中工作

Sir*_*eta 74 java spring hibernate jpa spring-data-jpa

我确实在我的项目中有三个模型对象之间的关系(帖子末尾的模型和存储库片段).

当我打电话时PlaceRepository.findById它会触发三个选择查询:

( "SQL")

  1. SELECT * FROM place p where id = arg
  2. SELECT * FROM user u where u.id = place.user.id
  3. SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id

这是相当不寻常的行为(对我而言).在阅读Hibernate文档后我可以告诉它应该总是使用JOIN查询.在类中FetchType.LAZY更改为 查询(使用附加SELECT 查询)时,查询没有区别,更改为 (使用JOIN查询)时类相同.FetchType.EAGERPlaceCityFetchType.LAZYFetchType.EAGER

当我使用CityRepository.findById抑制火时两个选择:

  1. SELECT * FROM city c where id = arg
  2. SELECT * FROM state s where id = city.state.id

我的目标是在所有情况下都有一个sam行为(总是JOIN或SELECT,但是JOIN首选).

型号定义:

地点:

@Entity
@Table(name = "place")
public class Place extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_user_author")
    private User author;

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_city_id")
    private City city;
    //getters and setters
}
Run Code Online (Sandbox Code Playgroud)

市:

@Entity
@Table(name = "area_city")
public class City extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_woj_id")
    private State state;
    //getters and setters
}
Run Code Online (Sandbox Code Playgroud)

库:

PlaceRepository

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
    Place findById(int id);
}
Run Code Online (Sandbox Code Playgroud)

UserRepository:

public interface UserRepository extends JpaRepository<User, Long> {
        List<User> findAll();
    User findById(int id);
}
Run Code Online (Sandbox Code Playgroud)

CityRepository:

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
    City findById(int id);
}
Run Code Online (Sandbox Code Playgroud)

小智 92

我认为Spring Data忽略了FetchMode.在使用Spring Data时,我总是使用@NamedEntityGraph@EntityGraph注释

@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  …
}

@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}
Run Code Online (Sandbox Code Playgroud)

请查看此处的文档

  • FetchMode与SPRING无关.这是一个HIBERNATE注释! (4认同)
  • 在实际情况下,@ EntityGraph几乎是不可能的,因为它无法指定我们要使用哪种类型的Fetch(JOIN,SUBSELECT,SELECT,BATCH)。结合使用@OneToMany关联,即使我们使用查询`MaxResults`,也可以使Hibernate将整个表提取到内存中。 (2认同)

Vla*_*cea 43

首先,@Fetch(FetchMode.JOIN)并且@ManyToOne(fetch = FetchType.LAZY)是对立的,一个指示EAGER取出,而另一个指示LAZY取.

渴望获取很少是一个很好的选择,对于可预测的行为,最好使用query-time JOIN FETCH指令:

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {

    @Query(value = "SELECT p FROM Place p LEFT JOIN FETCH p.author LEFT JOIN FETCH p.city c LEFT JOIN FETCH c.state where p.id = :id")
    Place findById(@Param("id") int id);
}

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom { 
    @Query(value = "SELECT c FROM City c LEFT JOIN FETCH c.state where c.id = :id")   
    City findById(@Param("id") int id);
}
Run Code Online (Sandbox Code Playgroud)

  • 有没有办法用Criteria API和Spring Data Specifications实现相同的结果? (3认同)
  • 不是fetch部分,它需要JPA获取配置文件. (2认同)

dre*_*619 17

Spring-jpa使用实体管理器创建查询,如果查询是由实体管理器构建的,Hibernate将忽略获取模式.

以下是我使用的工作:

  1. 实现从SimpleJpaRepository继承的自定义存储库

  2. 覆盖方法getQuery(Specification<T> spec, Sort sort):

    @Override
    protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) { 
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(getDomainClass());
    
        Root<T> root = applySpecificationToCriteria(spec, query);
        query.select(root);
    
        applyFetchMode(root);
    
        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }
    
        return applyRepositoryMethodMetadata(entityManager.createQuery(query));
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在方法的中间,添加applyFetchMode(root);以应用获取模式,以使Hibernate使用正确的连接创建查询.

    (不幸的是,我们需要从基类复制整个方法和相关的私有方法,因为没有其他扩展点.)

  3. 实施applyFetchMode:

    private void applyFetchMode(Root<T> root) {
        for (Field field : getDomainClass().getDeclaredFields()) {
    
            Fetch fetch = field.getAnnotation(Fetch.class);
    
            if (fetch != null && fetch.value() == FetchMode.JOIN) {
                root.fetch(field.getName(), JoinType.LEFT);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)