내 프로젝트의 세 가지 모델 객체 (게시물 끝에있는 모델 및 저장소 스 니펫)간에 관계가 있습니다.
내가 호출 PlaceRepository.findById
하면 세 가지 선택 쿼리가 실행됩니다.
( “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 쿼리를 사용해야합니다. 클래스 (추가 SELECT가있는 쿼리) 에서로 FetchType.LAZY
변경 될 때 쿼리에는 차이가 없으며 , (JOIN을 사용한 쿼리)로 변경 될 때 클래스에 대해서도 동일합니다 .FetchType.EAGER
Place
City
FetchType.LAZY
FetchType.EAGER
CityRepository.findById
화재 억제를 사용할 때 두 가지 선택 :
SELECT * FROM city c where id = arg
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
}
시티:
@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
답변
객체를 선택할 때 가져 오기 모드에만 작동합니다 ID로 사용하여, 즉 entityManager.find()
. SpringData는 항상 쿼리를 생성하므로 가져 오기 모드 구성은 아무 소용이 없습니다. 페치 조인과 함께 전용 쿼리를 사용하거나 엔터티 그래프를 사용할 수 있습니다.
최상의 성능을 원하면 실제로 필요한 데이터의 하위 집합 만 선택해야합니다. 이렇게하려면 일반적으로 불필요한 데이터를 가져 오지 않도록 DTO 접근 방식을 사용하는 것이 좋지만 JPQL을 통해 DTO 모델을 구성하는 전용 쿼리를 정의해야하므로 일반적으로 오류가 발생하기 쉬운 상용구 코드가 많이 발생합니다. 생성자 표현식.
스프링 데이터 프로젝션이 여기에서 도움이 될 수 있지만 어느 시점에서는 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의 저자이므로 편견이있을 수 있습니다.
답변
중첩 된 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 :
Hibernate에서 JPA를 사용하는 경우 Hibernate에서 사용하는 FetchMode를 JOIN으로 설정할 방법이 없지만 Hibernate에서 JPA를 사용하는 경우 Hibernate에서 사용하는 FetchMode를 JOIN으로 설정할 수있는 방법이 없습니다.
SpringData JPA 라이브러리는 생성 된 쿼리의 동작을 제어 할 수있는 Domain Driven Design Specifications 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);