스프링 데이터 JPA에서 FetchMode의 작동 방식
프로젝트의 3개의 모델 오브젝트(투고 말미에 있는 모델과 저장소 스니펫)와 관계가 있습니다.
가 전화했을 때PlaceRepository.findById을 하다
("sql")
SELECT * FROM place p where id = argSELECT * FROM user u where u.id = place.user.idSELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id
를 읽은 후 쿼리를 사용해야 .휴지 상태 문서를 읽은 후 확인할 수 있는 한 항상 JOIN 쿼리를 사용해야 합니다..FetchType.LAZY로 changed to 로 변경되다FetchType.EAGER PlaceSELECT를 ), (SELECT) 및 .City( 「」의 경우)FetchType.LAZY로 changed to 로 변경되다FetchType.EAGERJOIN을 하여 쿼리합니다 (JOIN)
★★★★★★★를 사용하는 CityRepository.findById 두 가지 진압하다
SELECT * FROM city c where id = argSELECT * 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
}
시구정촌:
@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
}
저장소:
플레이스 저장소
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
Place findById(int id);
}
사용자 저장소:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findAll();
User findById(int id);
}
시티 저장소:
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {
City findById(int id);
}
페치 모드는 항상 ★★★★★★★★★★★★★★를 사용하고 있다.@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)적대적인 이유는@Fetch(FetchMode.JOIN) JPA에 합니다.FetchType.EAGER.
신속한 이 아닙니다.또, 가능한 동작에 는, query-time 「」 「 query-time 」, 「 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는 엔티티 매니저를 사용하여 쿼리를 생성하고 엔티티 매니저에 의해 쿼리가 구축되어 있는 경우 Hibernate는 가져오기 모드를 무시합니다.
다음은 제가 사용한 작업입니다.
메서드를 덮어씁니다.
@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)); }메서드 중간에 를 추가하여 가져오기 모드를 적용합니다. 그러면 휴지 상태가 올바른 조인(join)을 사용하여 쿼리를 만들 수 있습니다.
(다른 확장점이 없었기 때문에 아쉽게도 기본 클래스에서 메서드 전체와 관련된 개인 메서드를 복사해야 합니다.)
구현:
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); } } }
스프링 데이터 jpa에서 사용되는 엔티티 관리자는 가져오기 모드를 무시합니다.
Repository 메서드보다 @EntityGraph 주석을 사용합니다.
@EntityGraph(attributePaths = { "user", "hashtags"})
Page<LIPost> findByVoteTypeIn(Set<VoteType> listOfVotetype, Pageable paging);
여기서 사용자 및 해시태그는 LIPost 엔티티의 속성입니다.
스프링 데이터 JPA에 의한 쿼리 빌드는 왼쪽 외부 조인을 사용하여 관련 엔티티(사용자 및 해시태그) 데이터를 가져옵니다.
이 경우 엔티티 클래스에 @NamedEntityGraph 주석을 사용할 필요가 없습니다.
FetchType.LAZY됩니다." 관계가 쿼리가 ( SELECT는 FIRES MULTLE SELECT로 하다.
FetchType.EAGER된 부모 한 모든 합니다.(USE는 부모 테이블을 포함합니다.)JOIN)
: 정보를 해야 하는 : " " " 를 선택합니다.FetchType.EAGER 를 FetchType.LAZY.
ㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇ.FetchType.LAZY상위 테이블 정보를 가져오도록 선택한 경우 코드 내의 위치에 활성 DB 세션 팩토리가 필요합니다.
:의 경우: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
가져오기 모드는 ID로 개체를 선택할 때만 작동합니다. 예를 들어, 를 사용합니다.entityManager.find()스프링 데이터는 항상 쿼리를 생성하므로 가져오기 모드 설정은 사용할 수 없습니다.가져오기 조인과 함께 전용 쿼리를 사용하거나 엔티티 그래프를 사용할 수 있습니다.
최상의 성능을 원하는 경우 실제로 필요한 데이터의 하위 집합만 선택해야 합니다.이 작업을 수행하려면 일반적으로 불필요한 데이터 가져오기를 피하기 위해 DTO 방식을 사용하는 것이 좋습니다. 그러나 JPQL 컨스트럭터 식을 통해 DTO 모델을 구성하는 전용 쿼리를 정의해야 하므로 일반적으로 오류가 발생하기 쉬운 보일러 플레이트 코드가 상당히 많이 발생합니다.
Spring Data의 예측은 도움이 되지만, 어느 시점에서는 Blaze-Persistence Entity Views와 같은 솔루션이 필요합니다.이것에 의해, 매우 간단하게 할 수 있고, 슬리브에 많은 기능이 있어 편리합니다.게터가 필요한 데이터의 서브셋을 나타내는 엔티티별로 DTO 인터페이스를 작성하기만 하면 됩니다.문제에 대한 해결책은 다음과 같습니다.
@EntityView(Identified.class)
public interface IdentifiedView {
@IdMapping
Integer getId();
}
@EntityView(Identified.class)
public interface UserView extends IdentifiedView {
String getName();
}
@EntityView(Identified.class)
public interface StateView extends IdentifiedView {
String getName();
}
@EntityView(Place.class)
public interface PlaceView extends IdentifiedView {
UserView getAuthor();
CityView getCity();
}
@EntityView(City.class)
public interface CityView extends IdentifiedView {
StateView getState();
}
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
PlaceView findById(int id);
}
public interface UserRepository extends JpaRepository<User, Long> {
List<UserView> findAllByOrderByIdAsc();
UserView findById(int id);
}
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {
CityView findById(int id);
}
부인합니다. 저는 Blaze-Persistence의 저자입니다. 그래서 편견이 있는 것 같습니다.
dream83619 답변에 대해 자세히 설명하여 네스트된 휴지 상태를 처리했습니다.@Fetch 하다.는 내포된 에서 주석을 때 재귀적 방법을 했습니다.중첩된 관련 클래스에서 주석을 찾기 위해 재귀적 방법을 사용했습니다.
따라서 커스텀 저장소를 구현하여getQuery(spec, domainClass, sort)된 모든 개인 메서드:(도해야 합니다.아쉽게도 참조되는 모든 개인 메서드 :(도 복사해야 합니다.
여기 코드가 있습니다.복사된 개인 메서드는 생략됩니다.
EDIT: 나머지 개인 메서드가 추가되었습니다.
@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;
}
}
http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html
다음 링크에서:
Hibernate 위에서 JPA를 사용하는 경우 Hibernate에서 사용하는 FetchMode를 JOIN으로 설정할 수 없습니다.However에서 Hibernate 위에서 JPA를 사용하는 경우 Hibernate에서 사용하는 FetchMode를 JOIN으로 설정할 수 없습니다.
Spring Data JPA 라이브러리는 생성된 쿼리의 동작을 제어할 수 있는 도메인 기반 설계 사양 API를 제공합니다.
final long userId = 1;
final Specification<User> spec = new Specification<User>() {
@Override
public Predicate toPredicate(final Root<User> root, final
CriteriaQuery<?> query, final CriteriaBuilder cb) {
query.distinct(true);
root.fetch("permissions", JoinType.LEFT);
return cb.equal(root.get("id"), userId);
}
};
List<User> users = userRepository.findAll(spec);
Vlad Mihalcea에 따르면 (https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/) 참조)
JPQL 쿼리는 기본 가져오기 전략을 재정의할 수 있습니다.내부 또는 왼쪽 조인 가져오기 지시어를 사용하여 가져올 내용을 명시적으로 선언하지 않으면 기본 선택 가져오기 정책이 적용됩니다.
JPQL 쿼리가 선언된 가져오기 전략을 덮어쓸 수 있으므로 다음을 사용해야 합니다.join fetch참조된 엔티티를 열심히 로드하거나 단순히 EntityManager를 사용하여 id별로 로드하기 위해(취득 전략에 따르지만 사용 사례에 대한 솔루션은 아닐 수 있음)
언급URL : https://stackoverflow.com/questions/29602386/how-does-the-fetchmode-work-in-spring-data-jpa
'programing' 카테고리의 다른 글
| 명령 프롬프트를 사용하여 mysql 데이터베이스를 내보내려면 어떻게 해야 합니까? (0) | 2023.02.05 |
|---|---|
| CREATE TABLE 문에 생성되지 않은 인덱스 및 제약 조건 (0) | 2023.02.05 |
| node.install 서버의 모든 인스턴스를 중지합니다. (0) | 2023.01.26 |
| JAVA_의 설정 방법Mac OS X 10.9의 홈 환경 변수 (0) | 2023.01.26 |
| 판다의 복잡한 기준으로 선정.데이터 프레임 (0) | 2023.01.26 |