FetchMode는 SpringData JPA에서 어떻게 작동합니까?
내 프로젝트의 세 가지 모델 객체 (게시물 끝에있는 모델 및 저장소 스 니펫)간에 관계가 있습니다.
내가 호출 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
그것은 다소 비정상적인 행동입니다. Hibernate 문서를 읽은 후 알 수있는 한 항상 JOIN 쿼리를 사용해야합니다. 클래스 (추가 SELECT가있는 쿼리) 에서로 FetchType.LAZY변경 될 때 쿼리에는 차이가 없으며 , (JOIN을 사용한 쿼리)로 변경 될 때 클래스에 대해서도 동일합니다 .FetchType.EAGERPlaceCityFetchType.LAZYFetchType.EAGER
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
}
저장소 :
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);
}
SpringData는 FetchMode를 무시한다고 생각합니다. 저는 SpringData로 작업 할 때 항상 @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)다른 하나는 LAZY가 가져 제안하면서 하나가 열망 인출을 지시, 적대적이다.
즉시 가져 오기는 좋은 선택 이 아니며 예측 가능한 동작의 경우 쿼리 시간 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는 가져 오기 모드를 무시합니다.
다음은 내가 사용한 해결 방법입니다.
메서드 재정의
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가 올바른 조인으로 쿼리를 생성하도록합니다.(안타깝게도 다른 확장 점이 없기 때문에 기본 클래스에서 전체 메서드 및 관련 개인 메서드를 복사해야합니다.)
구현
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"은 관련 상위 테이블을 포함한 모든 테이블의 조인을 직접 생성합니다. (사용 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
중첩 된 Hibernate 주석을 처리하도록 dream83619 답변에 대해 자세히 설명했습니다 @Fetch. 중첩 된 관련 클래스에서 주석을 찾기 위해 재귀 적 방법을 사용했습니다.
따라서 사용자 지정 저장소를 구현 하고 getQuery(spec, domainClass, sort)메서드를 재정의해야 합니다. 불행히도 참조 된 모든 private 메서드를 복사해야합니다.
다음은 코드입니다. 복사 된 private 메서드는 생략되었습니다.
편집 : 나머지 개인 방법을 추가했습니다.
@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 :
if you are using JPA on top of Hibernate, there is no way to set the FetchMode used by Hibernate to JOINHowever, if you are using JPA on top of Hibernate, there is no way to set the FetchMode used by Hibernate to JOIN.
The Spring Data JPA library provides a Domain Driven Design Specifications API that allows you to control the behavior of the generated query.
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);
According to Vlad Mihalcea (see https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/):
JPQL queries may override the default fetching strategy. If we don’t explicitly declare what we want to fetch using inner or left join fetch directives, the default select fetch policy is applied.
It seems that JPQL query might override your declared fetching strategy so you'll have to use join fetch in order to eagerly load some referenced entity or simply load by id with EntityManager (which will obey your fetching strategy but might not be a solution for your use case).
The fetch mode will only work when selecting the object by id i.e. using entityManager.find(). Since Spring Data will always create a query, the fetch mode configuration will have no use to you. You can either use dedicated queries with fetch joins or use entity graphs.
When you want best performance, you should select only the subset of the data you really need. To do this, it is generally recommended to use a DTO approach to avoid unnecessary data to be fetched, but that usually results in quite a lot of error prone boilerplate code, since you need define a dedicated query that constructs your DTO model via a JPQL constructor expression.
Spring Data projections can help here, but at some point you will need a solution like Blaze-Persistence Entity Views which makes this pretty easy and has a lot more features in it's sleeve that will come in handy! You just create a DTO interface per entity where the getters represent the subset of data you need. A solution to your problem could look like this
@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);
}
Disclaimer, I'm the author of Blaze-Persistence, so I might be biased.
참고URL : https://stackoverflow.com/questions/29602386/how-does-the-fetchmode-work-in-spring-data-jpa
'IT TIP' 카테고리의 다른 글
| Rails 3.1, RSpec : 모델 유효성 검사 테스트 (0) | 2020.10.31 |
|---|---|
| R 데이터에서 이전 행의 값을 사용합니다. (0) | 2020.10.31 |
| 원격 Redis 서버에 연결하는 방법은 무엇입니까? (0) | 2020.10.31 |
| Java 8에서 열거 형 반복 (0) | 2020.10.30 |
| Visual Studio Code에서 강조 텍스트 색 변경 (0) | 2020.10.30 |