이전 글 : https://developer-youn.tistory.com/193
0. 이 글을 쓰는 이유
하... 그니까 이게 제 실수긴 한데.. 미친듯한 N+1문제가 발생을 하긴 했습니다.. 그래서 다시..
1. 그니까 그 문제의 부분이 어디냐면..
if (!Hibernate.isInitialized(collection)) {
Hibernate.initialize(collection)
}
여기서 join이 걸려있는 경우 한번에 모아서 fetch를 하는게 아니라 각각 초기화를 하다 보니 정말 join이 걸리면 그 개수만큼 N번 날려서...
2. 그래서 고민은 꼬리에 꼬리를 물며
내가 왜 이렇게 만들었더라.. 라고 다시 한번 머리를 돌려보았습니다.
- 일단 tuple: Array<Any?>, aliases: Array<String> 로 transformTuple에 제공되고 있었고 어디선가 본 글에 이제 alias를 잘 이용하는게 좋을 것.. 이라는 글을 봤었던 것 부터 모든게 시작이었던 것 같습니다.
3. 다시 처음부터
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.transform;
import java.util.List;
/**
* Much like {@link RootEntityResultTransformer}, but we also distinct
* the entity in the final result.
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Gavin King
* @author Steve Ebersole
*/
public class DistinctRootEntityResultTransformer implements TupleSubsetResultTransformer {
public static final DistinctRootEntityResultTransformer INSTANCE = new DistinctRootEntityResultTransformer();
/**
* Disallow instantiation of DistinctRootEntityResultTransformer.
*/
private DistinctRootEntityResultTransformer() {
}
/**
* Simply delegates to {@link RootEntityResultTransformer#transformTuple}.
*
* @param tuple The tuple to transform
* @param aliases The tuple aliases
* @return The transformed tuple row.
*/
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return RootEntityResultTransformer.INSTANCE.transformTuple( tuple, aliases );
}
/**
* Simply delegates to {@link DistinctResultTransformer#transformList}.
*
* @param list The list to transform.
* @return The transformed List.
*/
@Override
public List transformList(List list) {
return DistinctResultTransformer.INSTANCE.transformList( list );
}
@Override
public boolean[] includeInTransform(String[] aliases, int tupleLength) {
return RootEntityResultTransformer.INSTANCE.includeInTransform( aliases, tupleLength );
}
@Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return RootEntityResultTransformer.INSTANCE.isTransformedValueATupleElement( null, tupleLength );
}
/**
* Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
}
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.transform;
import org.hibernate.internal.util.collections.ArrayHelper;
/**
* {@link ResultTransformer} implementation which limits the result tuple
* to only the "root entity".
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Gavin King
* @author Steve Ebersole
*/
public final class RootEntityResultTransformer extends BasicTransformerAdapter implements TupleSubsetResultTransformer {
public static final RootEntityResultTransformer INSTANCE = new RootEntityResultTransformer();
/**
* Disallow instantiation of RootEntityResultTransformer.
*/
private RootEntityResultTransformer() {
}
/**
* Return just the root entity from the row tuple.
*/
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple[ tuple.length-1 ];
}
@Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return true;
}
@Override
public boolean[] includeInTransform(String[] aliases, int tupleLength) {
boolean[] includeInTransform;
if ( tupleLength == 1 ) {
includeInTransform = ArrayHelper.TRUE;
}
else {
includeInTransform = new boolean[tupleLength];
includeInTransform[ tupleLength - 1 ] = true;
}
return includeInTransform;
}
/**
* Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
}
이 두 코드를 잘 보면 transformTuple을 할 때 alias는 사용안하고 냅다 tuple[ tuple.length -1] 로 root entity를 뽑아버리는데 alias를 매핑해야한다는 생각에서 뭔가가 꼬이기 시작했습니다.
4 .그래서 정말 미친척을 해보기로 결심
아 정말 정말 이대로 최소한의 변형만 가지고 만들어보았습니다.
import org.hibernate.query.ResultListTransformer
import org.hibernate.query.TupleTransformer
import java.io.Serializable
class DistinctRootEntityResultTransformer<T : Any>(
private val entityClass: Class<T>,
) : TupleTransformer<T>,
ResultListTransformer<T>,
Serializable {
private class Identity(private val entity: Any) {
override fun equals(other: Any?): Boolean = other is Identity && this.entity === other.entity
override fun hashCode(): Int = System.identityHashCode(entity)
}
override fun transformTuple(tuple: Array<out Any?>, aliases: Array<out String>?): T {
val root = tuple.firstOrNull { entityClass.isInstance(it) } ?: throw IllegalArgumentException(SOMETHING_MESSAGE)
return entityClass.cast(root)
}
override fun transformList(resultList: MutableList<out T>): List<T> {
val seen = mutableSetOf<Identity>()
val distinctResults = mutableListOf<T>()
for (entity in resultList) {
if (seen.add(Identity(entity))) {
distinctResults.add(entity)
}
}
return distinctResults
}
}
5. 약간의 걸림돌이
기존에는 addEntity를 하면서 적당히 이게 어떤 타입으로 매핑되어야하는지 명시를 해주었는데
join을 걸고 진행하는 경우 다시 한 번 명시를 해주어야했습니다.
근데 이제는 이게 자동으로 매핑을 하게 되면서 마지막 부분은 필요가 없는 것 처럼 보이더군요..(아마도)
6. 그래서 결론은
진짜로 진짜로 해치웠나