조인 및 행 기반 제한 (페이징)을 사용하여 최대 절전 모드에서 고유 한 결과를 얻는 방법은 무엇입니까?
다른 테이블에 대한 조인이있는 Hibernate Criteria 쿼리에서 행 기반 제한 (예 : setFirstResult(5)
and setMaxResults(10)
)을 사용하여 페이징을 구현하려고합니다 .
당연히 데이터가 무작위로 잘리고 있습니다. 그 이유는 여기 에 설명되어 있습니다 .
해결책으로 페이지는 조인 대신 "두 번째 SQL 선택"을 사용하도록 제안합니다.
createAlias()
대신 중첩 선택을 사용하도록 기존 기준 쿼리 ( 를 사용하는 조인이 있음 )를 변환하려면 어떻게 해야합니까?
고유 한 수화 된 개체 목록 대신 고유 ID 목록을 요청하여 원하는 결과를 얻을 수 있습니다.
이를 기준에 추가하기 만하면됩니다.
criteria.setProjection(Projections.distinct(Projections.property("id")));
이제 행 기반 제한에 따라 올바른 수의 결과를 얻을 수 있습니다. 이것이 작동하는 이유는 프로젝션이 SQL 쿼리가 수행 된 후 결과를 구별하기 위해 결과를 필터링하는 ResultTransformer가 수행하는 작업 대신 SQL 쿼리의 일부로 구별 검사 를 수행하기 때문 입니다.
주목할만한 점은 객체 목록을 가져 오는 대신 나중에 최대 절전 모드에서 객체를 수화하는 데 사용할 수있는 ID 목록을 얻게된다는 것입니다.
이 코드를 내 코드와 함께 사용하고 있습니다.
이를 기준에 추가하기 만하면됩니다.
criteria.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);
해당 코드는 네이티브 SQL의 테이블에서 select distinct *와 같습니다. 이것이 도움이되기를 바랍니다.
FishBoy의 제안에 따라 약간의 개선이 이루어졌습니다.
두 개의 개별 단계가 아닌 한 번의 조회로 이러한 종류의 쿼리를 수행 할 수 있습니다. 즉, 아래의 단일 쿼리는 고유 한 결과를 올바르게 페이지로 표시하고 ID 대신 항목을 반환합니다.
ID 프로젝션이있는 DetachedCriteria를 하위 쿼리로 사용하고 기본 Criteria 개체에 페이징 값을 추가하기 만하면됩니다.
다음과 같이 보일 것입니다.
DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(MyClass.class);
//add other joins and query params here
idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
Criteria criteria = getSession().createCriteria(myClass);
criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
criteria.setFirstResult(0).setMaxResults(50);
return criteria.list();
@FishBoy의 제안에 대한 작은 개선 사항은 id 프로젝션을 사용하는 것이므로 식별자 속성 이름을 하드 코딩 할 필요가 없습니다.
criteria.setProjection(Projections.distinct(Projections.id()));
session = (Session) getEntityManager().getDelegate();
Criteria criteria = session.createCriteria(ComputedProdDaily.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("user.id"), "userid");
projList.add(Projections.property("loanState"), "state");
criteria.setProjection(Projections.distinct(projList));
criteria.add(Restrictions.isNotNull("this.loanState"));
criteria.setResultTransformer(Transformers.aliasToBean(UserStateTransformer.class));
이것은 나를 도왔다 : D
해결책:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
아주 잘 작동합니다.
ORDER BY를 사용하려면 다음을 추가하십시오.
criteria.setProjection(
Projections.distinct(
Projections.projectionList()
.add(Projections.id())
.add(Projections.property("the property that you want to ordered by"))
)
);
이제 중복되거나 억제 된 항목의 문제없이 일반 쿼리 및 페이지 매김 방법을 사용할 수있는 다른 솔루션을 설명하겠습니다.
이 솔루션은 다음과 같은 장점이 있습니다.
- 이 기사에서 언급 한 PK id 솔루션보다 빠름
- Ordering을 유지하고 PK의 큰 데이터 세트에 'in 절'을 사용하지 않습니다.
전체 기사는 내 블로그 에서 찾을 수 있습니다.
Hibernate는 디자인 타임뿐만 아니라 쿼리 실행에 의해 런타임에도 연관 페칭 방법을 정의 할 수있는 가능성을 제공합니다. 따라서 우리는이 aproach를 간단한 relfection 항목과 함께 사용하고 컬렉션 속성에 대해서만 쿼리 속성 가져 오기 알고리즘을 변경하는 프로세스를 자동화 할 수도 있습니다.
먼저 엔터티 클래스에서 모든 컬렉션 속성을 확인하는 메서드를 만듭니다.
public static List<String> resolveCollectionProperties(Class<?> type) {
List<String> ret = new ArrayList<String>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(type);
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
if (Collection.class.isAssignableFrom(pd.getPropertyType()))
ret.add(pd.getName());
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
return ret;
}
그런 다음이 작은 도우미 메서드를 사용하여 해당 쿼리에서 FetchMode를 SELECT로 변경하도록 기준 개체를 조언 할 수 있습니다.
Criteria criteria = …
// … add your expression here …
// set fetchmode for every Collection Property to SELECT
for (String property : ReflectUtil.resolveCollectionProperties(YourEntity.class)) {
criteria.setFetchMode(property, org.hibernate.FetchMode.SELECT);
}
criteria.setFirstResult(firstResult);
criteria.setMaxResults(maxResults);
criteria.list();
이를 수행하는 것은 디자인 타임에 엔티티의 FetchMode를 정의하는 것과 다릅니다. 따라서 UI의 페이징 알고리즘에 대한 일반 조인 연결 페칭을 사용할 수 있습니다. 이는 대부분의 경우 중요한 부분이 아니고 가능한 한 빨리 결과를 얻는 것이 더 중요하기 때문입니다.
다음은 Distinct를 수행하기 위해 다중 투영을 수행 할 수있는 방법입니다.
package org.hibernate.criterion;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.type.Type;
/**
* A count for style : count (distinct (a || b || c))
*/
public class MultipleCountProjection extends AggregateProjection {
private boolean distinct;
protected MultipleCountProjection(String prop) {
super("count", prop);
}
public String toString() {
if(distinct) {
return "distinct " + super.toString();
} else {
return super.toString();
}
}
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
return new Type[] { Hibernate.INTEGER };
}
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery)
throws HibernateException {
StringBuffer buf = new StringBuffer();
buf.append("count(");
if (distinct) buf.append("distinct ");
String[] properties = propertyName.split(";");
for (int i = 0; i < properties.length; i++) {
buf.append( criteriaQuery.getColumn(criteria, properties[i]) );
if(i != properties.length - 1)
buf.append(" || ");
}
buf.append(") as y");
buf.append(position);
buf.append('_');
return buf.toString();
}
public MultipleCountProjection setDistinct() {
distinct = true;
return this;
}
}
ExtraProjections.java
package org.hibernate.criterion;
public final class ExtraProjections
{
public static MultipleCountProjection countMultipleDistinct(String propertyNames) {
return new MultipleCountProjection(propertyNames).setDistinct();
}
}
샘플 사용법 :
String propertyNames = "titleName;titleDescr;titleVersion"
criteria countCriteria = ....
countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);
https://forum.hibernate.org/viewtopic.php?t=964506 에서 참조
NullPointerException
일부 경우에! criteria.setProjection(Projections.distinct(Projections.property("id")))
모든 쿼리 없이 잘 진행됩니다! 이 솔루션은 나쁘다!
또 다른 방법은 SQLQuery를 사용하는 것입니다. 제 경우에는 다음 코드가 잘 작동합니다.
List result = getSession().createSQLQuery(
"SELECT distinct u.id as usrId, b.currentBillingAccountType as oldUser_type,"
+ " r.accountTypeWhenRegister as newUser_type, count(r.accountTypeWhenRegister) as numOfRegUsers"
+ " FROM recommendations r, users u, billing_accounts b WHERE "
+ " r.user_fk = u.id and"
+ " b.user_fk = u.id and"
+ " r.activated = true and"
+ " r.audit_CD > :monthAgo and"
+ " r.bonusExceeded is null and"
+ " group by u.id, r.accountTypeWhenRegister")
.addScalar("usrId", Hibernate.LONG)
.addScalar("oldUser_type", Hibernate.INTEGER)
.addScalar("newUser_type", Hibernate.INTEGER)
.addScalar("numOfRegUsers", Hibernate.BIG_INTEGER)
.setParameter("monthAgo", monthAgo)
.setMaxResults(20)
.list();
구별은 데이터베이스에서 이루어집니다! 반대 :
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
개체를로드 한 후 메모리에서 구분이 수행됩니다!
'programing' 카테고리의 다른 글
C ++ 템플릿 매개 변수를 하위 클래스로 제한 (0) | 2020.11.03 |
---|---|
gradle (1.1.2-5)로 빌드하는 데 사용되는 kotlin 버전이 IDE 플러그인 (1.1.2-4)에 번들로 제공되는 버전과 다릅니다. (0) | 2020.11.03 |
UITextField 예측 텍스트 비활성화 (0) | 2020.11.03 |
angular2의 구성 요소에 문자열 값을 전달하는 방법 (0) | 2020.11.03 |
해당 이름의 작업이 이미 존재하므로 작업 '래퍼'를 추가 할 수 없습니다. (0) | 2020.11.03 |