FetchMode如何在Spring Data JPA中工作?
我在我的项目中有三个模型对象之间的关系(模型和存储库代码片尾)
当我打电话PlaceRepository.findById
它会触发三个select查询:
( “SQL”)
-
SELECT * FROM place p where id = arg
-
SELECT * FROM user u where u.id = place.user.id
-
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
更改为Place
类中的FetchType.EAGER
(具有附加SELECT的查询)时,查询在查询中没有区别,而当FetchType.LAZY
更改为FetchType.EAGER
(JOIN查询)时, City
类也相同。
当我使用CityRepository.findById
抑制两个select:
-
SELECT * FROM city c where id = arg
-
SELECT * FROM state s where id = city.state.id
我的目标是在所有情况下都具有山姆行为(或者总是JOIN或SELECT,尽pipeJOIN首选)。
模型定义:
地点:
@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 }
市:
@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 }
库:
PlaceRepository
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom { Place findById(int id); }
UserRepository:
public interface UserRepository extends JpaRepository<User, Long> { List<User> findAll(); User findById(int id); }
CityRepository:
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom { City findById(int id); }
我认为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); }
在这里检查文档
首先, @Fetch(FetchMode.JOIN)
和@ManyToOne(fetch = FetchType.LAZY)
是敌对的,一个指示EAGER抓取,另一个指示LAZY抓取。
提前取指不是一个好的select ,对于可预测的行为,最好使用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); }
Spring-jpa使用实体pipe理器创build查询,如果查询是由实体pipe理器构build的,Hibernate将忽略获取模式。
以下是我使用的工作:
-
实现一个从SimpleJpaRepositoryinheritance的自定义仓库
-
重写
getQuery(Specification<T> spec, Sort sort)
方法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)); }
在该方法的中间,添加
applyFetchMode(root);
应用获取模式,使Hibernate用正确的连接创build查询。(不幸的是,我们需要从基类中复制整个方法和相关的私有方法,因为没有其他扩展点。)
-
实现
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); } } }
“ FetchType.LAZY
”只会触发主表。 如果在你的代码中调用任何其他具有父表依赖项的方法,那么它将触发查询来获取表信息。 (FIRES MULTIPLE SELECT)
“ FetchType.EAGER
”将直接创build包含相关父表的所有表的连接。 (使用连接)
何时使用:假设您强制需要使用从属父表信息,然后selectFetchType.EAGER
。 如果您只需要某些logging的信息,则使用FetchType.LAZY
。
请记住,如果您select检索父表信息, FetchType.LAZY
需要一个活动的数据库会话工厂。
例如对于LAZY
:
.. Place fetched from db from your dao loayer .. only place table information retrieved .. some code .. getCity() method called... Here db request will be fired to get city table info
额外的参考
我详细阐述了dream83619的答案,以便处理嵌套的Hibernate @Fetch
注解。 我使用recursion方法来查找嵌套关联类中的注释。
所以你必须实现自定义存储库并重写getQuery(spec, domainClass, sort)
方法。 不幸的是,你也必须复制所有引用的私有方法:(。
这里是代码, 复制私有方法被省略。
编辑:添加其余的私人方法。
@NoRepositoryBean public class EntityGraphRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> { private final EntityManager em; protected JpaEntityInformation<T, ?> entityInformation; public EntityGraphRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); this.em = entityManager; this.entityInformation = entityInformation; } @Override protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<S> query = builder.createQuery(domainClass); Root<S> root = applySpecificationToCriteria(spec, domainClass, query); query.select(root); applyFetchMode(root); if (sort != null) { query.orderBy(toOrders(sort, root, builder)); } return applyRepositoryMethodMetadata(em.createQuery(query)); } private Map<String, Join<?, ?>> joinCache; private void applyFetchMode(Root<? extends T> root) { joinCache = new HashMap<>(); applyFetchMode(root, getDomainClass(), ""); } private void applyFetchMode(FetchParent<?, ?> root, Class<?> clazz, String path) { for (Field field : clazz.getDeclaredFields()) { Fetch fetch = field.getAnnotation(Fetch.class); if (fetch != null && fetch.value() == FetchMode.JOIN) { FetchParent<?, ?> descent = root.fetch(field.getName(), JoinType.LEFT); String fieldPath = path + "." + field.getName(); joinCache.put(path, (Join) descent); applyFetchMode(descent, field.getType(), fieldPath); } } } /** * Applies the given {@link Specification} to the given {@link CriteriaQuery}. * * @param spec can be {@literal null}. * @param domainClass must not be {@literal null}. * @param query must not be {@literal null}. * @return */ private <S, U extends T> Root<U> applySpecificationToCriteria(Specification<U> spec, Class<U> domainClass, CriteriaQuery<S> query) { Assert.notNull(query); Assert.notNull(domainClass); Root<U> root = query.from(domainClass); if (spec == null) { return root; } CriteriaBuilder builder = em.getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; } private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) { if (getRepositoryMethodMetadata() == null) { return query; } LockModeType type = getRepositoryMethodMetadata().getLockModeType(); TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type); applyQueryHints(toReturn); return toReturn; } private void applyQueryHints(Query query) { for (Map.Entry<String, Object> hint : getQueryHints().entrySet()) { query.setHint(hint.getKey(), hint.getValue()); } } public Class<T> getEntityType() { return entityInformation.getJavaType(); } public EntityManager getEm() { return em; } }
- CacheProvider的exceptionNoClassDefFoundError
- 为什么我需要在Hibernate中进行只读操作?
- 如何在Spring Boot中使用Springpipe理的Hibernate拦截器?
- @GeneratedValue和@GenericGenerator之间的区别
- 如何configurationSQL Server的hibernateconfiguration文件
- 如何使用REST API进行身份validation? (浏览器+本机客户端)
- 注解@Id和@GeneratedValue(strategy = GenerationType.IDENTITY)有什么用? 为什么生成types是身份?
- 何时以及如何使用hibernate二级caching?
- Hibernate是一个Android应用程序的矫枉过正?