Page History
Info |
---|
조금더 복잡한 쿼리를 ORM으로 시도해보겠습니다. Let's go |
Table of Contents | ||
---|---|---|
|
...
- district를 얻기위해 , 데이터베이스에게 일을 시키지 않습니다. 어플리케이션이 분산해서 연산할수 있음을 의미합니다.
- 조회가 100개가되었다고 해서, getDistrict() 를 호출하지 않으면(사용하는곳이없으면), 어플리케이션내에서 변환 연산조차 발생하지 않습니다. ( Lazy컨셉,호출이 필요하지 않으면 사용하지 않겠다.)
- 다양한 자바 함수사용이 가능합니다.
위와같이 변환할수있다고 하더라도, SQL문의 CASE문은 여러요소에사용되는 식이기때문에,
...
SQL에서 조건문처리는 JAVA의 CASE문과 작동방식은 동일하지만 아주 큰차이가 있습니다.
Note |
---|
CASE문을 변환하다, 본의 아니게 CASE의 본질에대해 고민해보았습니다. ( 정답은 아닐수 있습니다. ) SQL문에서 CASE는 식입니다. SQL문은 절차식 언어가 아니기때문에 반환값이 항상있으며 다양한 요소에 결합이 될수 있습니다. JAVA에서 CASE는 문장입니다. 문장의 중간에 포함될수 있으며 절차식 흐름을 변경만하고 값자체를 반환하지는 않습니다. 절차식에 익숙해져 있다고하면 SQL에서는 성능좋은식을 만들기가 어렵습니다. 어플리케이션 내에서도 람다식을 활용하여 리스트와같은 데이터를 필터처리한다면 SQL과 비슷한 사고방식으로 데이터가공처리가 될것입니다. |
...
UNION에서 중복은 제거된다라는 속성은 중요한 내용이며, 지정된 전체필드에서 중복을 찾는다란 것은
아주 큰 비용이드는 연산이 될것입니다. 중복을 무시할시 UNION ALL을 사용합니다.
그럼 UNION수행이 UNION수행을 JPA에서는 어떻게 작동되는지 구현해야하는지 확인해보겠습니다.
JPA에서 UNION 사용하기
아쉽게도 JPQL에서 UNION쿼리를 지원하지 않으며, 유니온된 결과 역시Entity에 정의하지 않은
새로운 튜플이기 때문에, SQL 맵핑처리도 원활하지 않습니다.
Native 쿼리 방식을 사용해야하며
사용방식에 만족스럽지는 못하지만 맵핑에도 어려움이 있어서
사용하여 오브젝트를 변환하는 전통적인 방법을 그대로 사용해해보겠습니다.
...
Warning |
---|
UNION과같이 JPQL이 지원하지 않는 명령문에서 JPA에서 바람직하게 어플리케이션의 데이터와엔티티와 맵핑하는 기능은 사용할수 활용 할수 없어보입니다. 어렵게 맵핑할수 있는 방식도 있지만 ( UNION JPA ENTITY ECLIPSE LINK 등으로 검색하면 됩니다.) 준비해야할 코드들이 너무 많습니다. UNION 은 특성상 ,모든 테이블을 합쳐서 처리하는 배치성에서 많이 사용되며 편리합니다사용되곤 합니다. 모든것을 간단하게 합할수 있지만 데이터량에 따라 메모리풀의 위험성을 가지고 있으며, 이부분에 대한 튜닝을 위해 어렵고, 코드가 길어지는 SQL문 작성능력및 트랜젝션처리가 요구되기도 합니다. JPA에서는 대량의 데이터를 안전하게 부분처리를 하는 패턴을 간단하게 설계할수 있으며 아래 아티컬을 참조합니다. 물론 이주제는 배치와 연관된내용으로 UNION과는 직접적인 상관은없습니다. https://memorynotfound.com/hibernate-jpa-batch-insert-batch-update-example/ |
...
Note |
---|
합집합,교집합은 순서가 다르다고 결과가 달라지지 않습니다. ( 드라이븐 시작조건에 의해 성능이 달라질순 있어도 - 실행계획에의해 순서에따라 성능이 다를수 있습니다. ) 하지만 차집합은 빼는것이기때문에 교환법칙이 성립되지않기때문에 순서에 유의합니다. 교환법칙 A +B = B + A : 같음으로 교환법칙 성립 A - B = B - A : 같지 않다. |
...
JOIN | UNION |
---|---|
|
|
윈도우 함수
MYSQL 5에서는 윈도우 함수가 지원되지 않아, Postgres 를 이용하였습니다.
윈도우처리는 논리적인 파티셔닝을 의미하며, MYSQL에서 PARTITION이란 키워드는
성능을 위한 실제 물리적인 파티셔닝을 의미해서 동일한 역활을 하는 쿼리를 찾기가 어려웠습니다.
Oracle과 SQL문호환성이 높은 Postgres DB를 추가로 설정하였으며
다중DB 환경설정 에서 두가지 DB를 설정하여 운영하는 방법을 정리하였으니 참고하세요
여기서 윈도우란 의미는 OS윈도우와 상관없으며, 특정 구간을 ROW단위로 중간집계할수 있는기능으로
논리적인 방식으로 윈도우 파티션되었다라고 이해하면 될것같습니다.
Group By | PARTITION BY |
---|---|
SELECT t.address,count(*) FROM address t GROUP BY address | SELECT name,address,COUNT(*) OVER(PARTITION BY address) FROM address |
자르기 기능과 동시에 집계기능이 수행되어 원래의 데이터에서 요약 집계 처리가 됩니다. | GroupBy와 비교를 하면, 집계기능을 사용하기위해 요약처리가 안되고 레코드수를 그대로 유지하면서 원하는 집계수를 볼수가 있으며 자르기 기능만 수행되었다라고 보시면됩니다. 이해를 돕기위해 색상으로 데이터를 사각형 분리를하였으며 논리적으로 파티션 혹은 자른 사각형이 윈도우같다고 하여 윈도우처리 되었다라고 표현됩니다. |
나이 많은순으로 랭크를 내고자 한다고하면, 윈도우함수인 RANK()를 활용하면 간단하게 구할수있습니다. 이 기능은 Group BY및 서브쿼리와 결합하여 구현하거나 어플리케이션내에서 랭킹을 부여하는 방법으로 구현 할수도 있겠으나,DB가 지원한다고 하면 윈도우함수 사용이 권장됩니다. postgresql기준으로 더 자세한 윈도우함수 종류와 사용방법을 살펴볼수 있습니다. https://www.postgresql.org/docs/10/static/functions-window.html |
이와 같이 윈도우 함수는 복잡한 집계및 집약처리에 유용하게 사용이되며, 성능계에서 특히 효율적인 집계를 위해
유용하게 사용이됩니다. 호환성의 관점에서는 Group BY에의한 집계 함수는
대부분의 DB에서 표준으로 사용가능하지만 논리적인 파티션 기능인 윈도우함수는 SQL표준이 모두 적용이 안되어 있으며
DB마다 사용방법과 지원함수가 각각 틀려서 JPA에서 표준인터페이스가 존재하지 않으며, ORM이란 특성에도 적합하지 않는
명령문입니다. 그래서 윈도우함수는 네이티브 SQL로 사용하거나 JPQL을 사용하기위해서는 방언 설정이 필요하게됩니다.
Note |
---|
다양한 집계와 통계처리는 JPA에서 지원하는 항목은 아니며 DBMS의 특성에따른 SQL문 지원을 알아야 하는 사항으로 다음을 참고합니다. 주요 DBMS특징정리 : http://d2.naver.com/helloworld/907716 |
어플리케이션에서 랭킹처리하기
SQL문으로 간단하게 처리할수 있고 성능적으로도 우수한, 윈도우함수 활용이 권장이 되지만
만약 사용하는 DB가 윈도우함수를 지원하지 못하고, 유사한 기능수행을 위해 성능이 느린 SQL문 작성이 요구될수도있습니다.
이경우 데이터베이스로부터 랭킹 데이터를 Read를 하고 어플리케이션 레벨에서 랭킹을 할당하는 방식을 구현해보겠습니다.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public class AddressAgeRank {
private String name;
private int age;
private int rank;
public AddressAgeRank(String name, int age, int rank) {
this.name = name;
this.age = age;
this.rank = rank;
}
@Override
public String toString() {
String result = String.format(
"AddressAgeRank[name='%s',age=%d,rank=%d]%n",
name, age,rank);
return result;
}
} |
랭킹에 사용되는 원본 데이터의 조건을 findByAgeBetween 을통해 10세에서 90미만의
데이터로 검색조건을 축소하였으며 이후에 sorted , map식을 사용하여
랭킹을 부여한후 우리가 원하는 랭킹 데이터로 변환하게 됩니다.
람다식을 활용하였습니다.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public void RankTest() {
int[] score = {Integer.MIN_VALUE};
int[] no = {0};
int[] rank = {0};
List<AddressAgeRank> ageRankList =
addressRepo.findByAgeBetween(10, 90).stream()
.sorted((a,b) -> b.getAge() - a.getAge() )
.map(p -> {
++no[0];
if (score[0] != p.getAge()) rank[0] = no[0];
return new AddressAgeRank(p.getName(),score[0] = p.getAge(), rank[0] );
})
.collect(Collectors.toList());
ageRankList.forEach(item ->{
System.out.println(item.toString());
});
} |
Expand | ||
---|---|---|
| ||
Note | ||
---|---|---|
| ||
ORM은 쿼리를 모르는 사람이 편리하게 사용할수 있는 툴일까? 로 시작하여 비교적 쉬운 데이터 모델은 ORM을(SQL PART-A) 통해 무난하게 학습진행되었지만 제가 원하는 진정한 변환은 SQL-PART2였으며 여기서 문제가 발생하였습니다. 이것의 변환을 위해 DB학습을 더해야했으며 표준적이지 않지만 해결방법을찾아야했습니다. ORM은 DB를 이해하고 OOP로 변환하고 일치시키려는 컨셉을 가지고 있기때문에 DB와 OOP 연마를 함께해야하며 평균이상 더 해야한다란 결론에 도달하였습니다. 그러고도 복잡한 쿼리에대한 성능문제 예를 들면 N+1의 문제를 해결해야합니다. ORM N+1 문제를 검색하면, 엄청난 량의 각기 다른문제를 검색할수 있으며 이것을 해결하는 표준적인 방법이 없으며 ORM을 사용하는 각각의 프레임워크마다 해결방식이 다릅니다. ORM에 도전하기위해서는 기존개발 패러다임이 바뀌어야하고 다음과같은 더 고난이도의 해결해야할 과제가 있음을 알게되었습니다.
|