'분류 전체보기'에 해당되는 글 140건

  1. 2018.04.17 코딩 테스트
  2. 2012.12.19 그들이 위임했을뿐
  3. 2012.08.27 thinking...
  4. 2012.06.19 작업중인 Project
  5. 2012.06.19 아키텍트의 역할2 1
  6. 2012.06.19 아키텍트의 역할
  7. 2010.04.04 DBFSlayers 1
  8. 2010.03.09 DB Framework 2.0 Short Explain 3
  9. 2010.02.24 Framework Cursor (cursor in MySql) 1
  10. 2009.08.14 언젠가...
IT 이야기2018. 4. 17. 18:33


코딩을 거의 1년간 안해서 감을 찾는다는 목적으로 간단한 퀴즈 검색을 했더니

[카카오 신입 공채 1차 코딩 테스트 문제] 라는게 있어서

시험삼아 코딩을 해봄. (파일 첨부. : SecretMap.java)


일단 처음에 문제를 보고 3-4시간 걸릴거라 생각했는데 생각보다 헤매서 중간에 야구 본 시간을 빼더라도 7-8시간 정도 걸림. 익숙하지 않은 키보드도...


가장 오래걸린 문제 - 버스 스탑 - 처음에 접근을 잘못함. 




입사문제로 이게 적합한가에 대해서는 부정적임. 


몇몇의 코딩바보를 걸러낼수는 있지만 

위 문제중 2-3문제를 풀수 있는 사람은 다른 문제도 모두 풀 수 있음. 시간의 조금 더 걸리느냐의 문제일뿐. 


즉 2-3문제를 푼 사람과 모두 푼 사람의 차이는 단지 시간의 차이 문제인데 이런 시간의 차이가 실질 프로그래밍하는데 의미있는 차이를 가지느냐고 한다면 그렇지 않다고 생각함. 즉 변별력이 없음 (프로그래밍 분야에 따라 조금 다르겠지만..) 시간 문제때문에 오히려 변별력을 가지는 적합한 예외처리나 충분한 테스트 그리고 유연한 클래스 디자인등을 할 수 없게 만듬. 


그리고 Util 함수/클래스는 대부분 오픈소스로 미리 구현되어 있는데 LRUCache 같은걸 만들어야 할 이유가 없음. 



적합한 방법은 사실 나도 매번 의문이지만

문제를 내는 방식으로 할 거라면 

메모리 구조/핸들링이나 쓰레드와 관련한 문제를 내지 않을까...





'IT 이야기' 카테고리의 다른 글

아키텍트의 역할2  (1) 2012.06.19
아키텍트의 역할  (0) 2012.06.19
다시 성능  (0) 2009.04.02
아키텍트 vs 트러블슈터 vs 컨설턴트  (0) 2009.03.26
튜닝  (0) 2009.03.26
Posted by bleujin
pds2012. 12. 19. 21:17
우리는 국민들에게 강요하지 않았다.
그들이 우리에게 위임했을 뿐
그리고 그들은 그 댓가를 치루는 거다.


from 괴벨

지옥(Hell) 끝 -> 지옥불(Inferno) 시작.


'pds' 카테고리의 다른 글

AL Client (XUL Sample)  (0) 2009.07.26
AL Ver 0.1  (0) 2009.07.24
AL의 첫 테스트 버전  (0) 2009.04.12
Framework - Source  (0) 2009.02.19
Bleujin Framework Jar  (0) 2009.01.15
Posted by bleujin
카테고리 없음2012. 8. 27. 01:35


그것은


캄캄한 물 속에 


잠겨드는 것과 비슷하다.




"답"은 


그 깜깜한 물 밑에만 있으며,





들어가면 들어갈 수록


다음번 "답"은





더욱 깊은 곳에서만 발견할 수 있게 된다.

Posted by bleujin
Framework2012. 6. 19. 22:18



ionFramework

Home : https://github.com/bleujin/ionframework

gitURL : https://github.com/bleujin/ionframework.git

doc : http://bleujin.springnote.com/pages/11948044


mongoNode

Home : https://github.com/bleujin/mongoNode

mongoSearchHome : https://github.com/bleujin/mongoSearch


gitURL : https://github.com/bleujin/mongoNode.git

mongoGitURL : https://github.com/bleujin/mongoSearch.git


doc : http://bleujin.springnote.com/pages/8134200



ISearcher
Home : https://github.com/bleujin/ISearcher
gitURL : https://github.com/bleujin/ISearcher.git
doc : http://bleujin.springnote.com/pages/11944056

 


Aradon

ServerHome : https://github.com/bleujin/aradon 

ClientHome : https://github.com/bleujin/aradonClient

ExtendHome : https://github.com/bleujin/aradonExtend

AgentHome : ...


ServerGitURL : https://github.com/bleujin/aradon.git.git

ClientGitURL : https://github.com/bleujin/aradonClient.git

ExtendGitURL : https://github.com/bleujin/aradonExtend.git


doc : http://bleujin.springnote.com/pages/8011148





Posted by bleujin
IT 이야기2012. 6. 19. 22:07



지휘자란 다양한 개성을 가진 연주가들의 집단(오케스트라)을 한데 모으는 머리의 역할
기계와는 달리 버튼을 누르면 움직이는게 아니므로 말하는 것을 듣게 하는 것도 큰일이다.
그래서 무섭거나 상냥하거나 깐깐하거나 친구같기도 하는 등 여러가지 타입의 지휘자가 생겨난다.




지휘관 타입


가장 지휘자다운 것이 지휘관 타입
오케스트라를 군대처럼 취급하여 한치의 흐트러짐 없는 작전 수행이 가능하도록 훈련을 게을리 하지 않고
실수나 명령 무시를 결코 인정하지 않는다.
또한 단원의 채용이나 해고 등 인사권도 발동하여 미소 한번 짓지 않고 모든 것을 장악하는 무서운 타입

어떤 의미로는 최강의 지휘자이지만
조금이라도 약점을 보이거나 실수라도 저지르면 권위가 실추되어 모반이 일어날 위험도 크다.

때문에 한순간의 방심도 용납할 수 없어 스트레스가 쌓인다. 덕분에 현대에는 거의 전멸한 타입


BOSS 타입


다음은 보스 타입,

어떤 때는 엄격하게, 어떤 때는 상냥하게 채찍과 당근을 절묘한 밸런스로 반복해
어느새 이사람의 말이라면 뭐든 듣겠어 라는 생각을 가지게 된다.
지휘의 테크닉과 함께 사람의 마음을 장악(꼬시기)하는 테크닉을 아울러 지니고 있어
마에스트로(가장) 또는 보스라는 애칭으로 불리면서 마음을 한데 모으고 즐기며 음악과 사람을 컨트롤 한다.

젊어서는 미워할 수 없는 타입 혹은 사랑받는 타입으로서 실적으로 올리고
연륜을 쌓으면 아무 말 하지 않아도 오케스트라가 따라오는 거장으로 승격할 수 있는 이상적인 지휘자


중간관리자 타입

한편 작곡가(사장)와 오케스트라(사원) 사이에 끼어서 양쪽에 머리를 숙이며 사이를 중재하는 것이 중간관리자 타입.
지휘자라고 하면 오케스트라에 뭐든 명령할 수 있다고 생각할지 모르나
수많은 대가들과 공연 경험이 있는 대선배 연주가나 혹은 지식이 풍부하여 이론만 내세우는 시끄로운 연주가

그리고 소리치면 울어버리는 젊은 연주가도 있다.
그들 모두를 따르게 하는 것은 여간 어려운 일이 아닐 수 없다.

따라서 오케스트라를 치켜 세워서 좋은 연주를 끌어내는 수단이 필요하게 된다.
예를 들면 "안돼 이렇게 해"가 아니라
"죄송합니다만 여긴 이런 식으로 연주해 주시겠어요" 라든가
"매우 멋진 연주였습니다. 하지만 조금 더 이런 느낌으로 해주신다면 더욱 멋진 연주가 될 것 같은데 어떠신가요?" 라고 말하는 식이다.
지휘자는 위에서 들볶고, 밑에서 규탄받는 힘든 일이기도 하다.
이런 이유로 최근 이런 타입이 많아진 것 같기도 하다.


학자 타입

다른 노선으로 작곡가가 쓴 악보를 100% 재현하는 것이 최고라며 모든 것을 쿨하게 해내는 것이 학자 타입,
음악에 대한 열정 등의 애매한 말은 하지 않고 악보에 적혀 있는 포르테나 피아노, 템포나 다이내믹을 정확하게 음으로 내는 것에 목숨을 건다.

이른바 컴퓨터 같은 과학자 타입, 말하는 것은 이성적이고 엄격하지만,
작품 연구에 전력을 다하기 때문에 설득력이 있어서 오케스트라도 말하는 바를 잘 듣는다.(라기 보다는 들을 수 밖에 없다. )
덕분에 틀리거나 감정이 흘러가는 일이 없어 연주는 편차가 없고 안정적이지만,
너무 이지적이라 음악의 열정(과 미소)는 조금 부족할지도 모르는 것이 옥의 티.



from 피아노의 숲


.......

이제 "지휘자"를 "아키텍트"로 바꿔서 읽어보자..




'IT 이야기' 카테고리의 다른 글

코딩 테스트  (0) 2018.04.17
아키텍트의 역할  (0) 2012.06.19
다시 성능  (0) 2009.04.02
아키텍트 vs 트러블슈터 vs 컨설턴트  (0) 2009.03.26
튜닝  (0) 2009.03.26
Posted by bleujin
IT 이야기2012. 6. 19. 22:04

지휘자는 클래식 음악에서는 작곡가와 견줄만큼 대단한 얼굴을 하고 있지만
생각해보면 조금 불가사의한 일이다.

여러가지 악기를 가진 70명 정도의 사람들이 앉은 큰 오케스트라 앞에서,
홀로 맨 앞의 단상 위에 올라서서 오른손에 가느다란 봉을 잡고 휘두른다.

확실히 곡의 시작이나 마자막의 짜잔~ 하는 음을 끊을 타이밍 등을 알려줄 사람이 필요하다는 건 누구나 알 수 있다.
하지만 나머지는 음악에 맞춰 춤추는 걸로만 보인다.

지휘자는 대체 무엇을 하는 걸까?
지휘자가 없다고 연주할 수 없는 것도 아니지 않은가?
그리고 지휘자에 따라 연주가 그토록 달라지는 것일까?




- 그런 질문에 대해 설명하고자 한다.

지휘자는 영어로 컨덕터(conductor)라고 한다.
때로는 경애를 담아 마에스트로(거장)라고 부르기도 하지만 큰 스승님 과 비슷한 느낌이다.

현재 세계 각 도시에는 반드시 라고 해도 좋을만큼 오케스트라(관현악단이나 교향악단)가 있고,
그들은 콘서트나 오페라(가극) 혹은 TV 방송, CD 녹음 등 날마다 활약하고 있다.

그리고 오케스트라가 연주할 때면
반드시(가장 잘난 듯한 얼굴로) 등장하는 것이 지휘자이다.




하지만 이 지휘자라는 존재가 옛날부터 있었던 것은 아니다.
4-5명 정도인 소 인원의 앙상블(합주)이나 코러스(합창)의 경우에는
누군가 리드하는 한 사람만 신호를 보내고 나머지는 서로 얼굴을 쳐다보며 연주하거나 노래를 부르면 되기 때문이다.

실제로 클래식 음악에서도 18세기 정도까지는
작곡가가 쳄발로(키보드)를 치며 손으로 혹은 가장 안쪽에 앉아 있는 수석 바이올린 연주자가 활로 신호를 보내어 연주했다.

그런데 편성이 커지면서 현악기(바이올린이나 첼로)에 목관악기(플룻이나 클라리넷)와 금관악기(트럼펫이나 호른)까지 가세하여 구성이 커지자
연주하면서 서로 쳐다본다는 것은 사실상 불가능 하게 되었다.

그래서 누구나 볼 수 있는 위치에서 앙상블을 맞취기 위해 지휘를 할 사람이 필요하게 되었고,
이것이 지휘자가 되었다.




하지만 처음의 지휘자는 전문 직업이 아니라 작곡가 겸 연주가인 악단의 리더가 그 역할을 맡아서 하는게 일반적이었다.
아니 그렇다기 보다는 옛날에는 연주하고 곡도 쓰고 지휘도 하는게 음악가였다.

참고로 그 무렵(이른바 바로크 시대)에는 단상위에 서서 굵은 막대(지팡이)로 바닥을 쿵쿵 쳐가며 지휘를 하기도 했다.

지금 생각하면 이상한 느낌이지만 입으로 하나 둘, 하나 둘 이라고 외칠수도 없고

지휘자가 있는 곡의 경우 대인원으로 나팔이나 큰 북이 둥둥 울리는 곡이 많았으니 그렇게 해도 그다지 방해되지는 않았던 것 같다.

하지만 조용한 곡의 경우에는 역시나 쿵쿵대는 소리가 시끄러웠고,
흥분해서 힘이 들어가면 막대로 자신의 발을 찍어서 위험하기도 했다.
실제로 룰리라는 작곡가는 자신의 발을 찍었고, 그 상처가 원인이 되어 사망에 이르렀다고 한다.

그래서 막대로 바닥을 쿵쿵 치는 것은 위험하다는 게 판명되었지만,
오케스트라처럼 50명이나 100명씩 되는 대인원이 연주하게 되면 손을 흔드는 것으로는 무대에서 잘 보이지 않는다.

그래서 누군가가 눈에 띄는 길고 흰 막대를 들고 휘두르는 방법을 생각해냈고,
이것이 지휘봉이 되었다.

이러한 연유로 지금처럼 지휘봉을 들고 지휘하게 된 것은 19세기에 들어서인데
독일의 작곡가 멘델스존(결혼 행진곡이나 바이올린 협주곡으로 유명)이 최초라고 일컬어지고 있다.




그리고 19세기 후반 이른바 낭만파의 시대가 되자 오케스트라의 음악이 한층 복잡해진다.
악상의 변화와 함께 한곡 안에서 템포가 빨라지거나 느려지고, 한창 고조됐다가 조용해지기도 했다.

이렇게 되자 아무래도 하나 둘, 하나 둘 하며 팔을 흔드는 것만으로는 쫓아갈 수 없었고
신호를 알기 쉽게 정확히 전달하는 데만도 전문적인 테크닉이 필요하게 되었다.
게다가 몇시간이나 계속 선 채로 양팔을 휘둘러야 하니 체력과 운동신경도 필요했다.

평소에 피아노 앞에 앉아서 악보를 그리기만 하는 (운동부족인) 작곡가에게는 매우 감당하기 힘든 일이 된 것이다.
그래서 자기 손으로 작곡도, 연주도 하지 않지만
클래식 대가들의 음악을 전문적으로 다루며

오페라 극장에서 매주 오페라를 지휘하거나 오케스트라의 정기 연주를 매달 지휘하는 지휘의 프로가 등장하게 된 것이다.

지휘자의 역할은 음악가라기 보다는 영화 감독 혹은 야구 감독에 가까울지도 모른다.

좌우간 아무 지식없이 보고 있으면, 지휘자는 오케스트라 앞에서 음악에 맞춰 지휘봉을 휘두르는 것으로만 보인다.

그건 영화 감독이 의자에 앉아 잘난 척하며 레디 고를 외치기만 하는 걸로 보이는 것과
야구 감독이 벤치에 떡하니 앉아서 선수에게 불평을 늘어놓기만 하는 것으로 보이는 것과 같은 이치이다.

확실히 배우나 선수에게 지시를 내리는 것이 전부다. 그러니 그렇게 보인들 어쩔 수 없을 수 밖에.





하지만 실제로는 이렇게 음악이나 연기나 스포츠의 프로들을 컨트롤해 하나로 묶어 통솔함으로써 작품 혹은 시합을 완성하여 성공이나 승리를 이끌어 낸다.

그 결과 지휘자나 감독에 따라 그다지 명곡이라 할 수없는 음악이 감동의 명곡이 되기도 하고
예산이 아주 적은 영화가 대히트작이 되거나 약팀이 일약 강팀의 반열에 오르기도 한다.(물론 그 반대도 존재하지만)

한정된 시간(이나 예산)안에서 어떻게 연주가나 배우나 선수를 활용하여 화려한 무대를 만들고, 독특한 작품 세계를 창조하고, 시합을 승리로 이끄는가?
그것이 지휘자나 감독의 역량이라는 것이다.





그렇게 생각하면 충분히 상상이 가리라 생각하는데,
실제로 콘서트나 영화나 시합의 본 무대가 시작되고 나면 지휘자나 감독이 할 수 있는 일은 그다지 없다.
연주가와 배우 혹은 선수들의 활약에 전면적으로 맡기는 수밖에 없는 것이다.
그래서 지휘자가 콘서트 본 공연에서 음악에 맞춰 지휘봉을 휘두르기만 하는 것으로 보이는 것은 충분히 가능한 일이다.

실제로 연주하고 있는 것은 살아있는 연주가들이므로
게임처럼 명령키를 누르면 완번히 컨트롤 할 수 있는 것도 아니고 버튼을 누른다고 안타가 나오는 것도 아니다.
할 수 있는 것은 기껏해야 액셀이나 브레이크로 조정하는 것 정도 뿐.

따라서 사실 본 무대 전의 연습이나 리허설만이 지휘자나 감독들이 제 역량을 발휘 할 수 있는 곳이다.
제대로 리허설을 진행하기만 한다면 본 부대는 "준비, 시작~"만 외쳐도 충분한 것이다.





예를 들면 훈련된 프로 오케스트라라면 초보자가 지휘대에 서서 곡에 맞춰 적당히 지휘봉만 휘둘러도(혹은 춤을 추더라도)
그럭저럭 연주를 할 수 있다. (이상한 춤으로 방해하지 않는다는 가정하에)

일류 스태프가 모여 제대로 훈련을 쌓은 팀이라면 가령 당신이 감독자리에 앉아 레디 고를 외치더라도 그럭저럭 해 나갈수 있을 것이다.
하지만 그렇다고 해서 누가 지휘(감독)해도 마찬가지라는 것은 아니다.

실제 감독은 본무대에 대비해 배우나 스태프나 선수를 선발하고,
각본이나 시함의 흐름을 음미하고, 콘티를 그려 촬영 앵글을 고려해 촬영 순서나 시간의 배분을 짜고,
다양한 트러블이나 상황의 변화에 따라 적합한 지시를 내리고,
배우나 선수를 혼내거나 칭찬하면서 (왜냐면 감독이 하는 말을 들어준다고 단정지울 수 없기 때문이다.) 연습과 리허설을 거듭해
최종적으론 그들이 힘을 완벽히 발휘할 수 있는 최적의 상황을 만들어야만 한다.




그를 위해서 지휘자라면 악보를 읽어서 작품 구석구석까지 알아야만 하며,
음의 실수를 구분해 들을 수 잇는 귀와 악기나 작품에 관한 지식,
다양한 나라의 언어, 나아가 작품의 배경이 되는 역사나 음향학 등의 교양,
때에 따라서는 분위기를 부드럽게 만들 재치까지 필요하다.
그 모든 것이  다 갖춰져야 비로서 오케스트라나 팀을 장악할 수 있는 것이다.
그것이 지휘자나 감독의 역할이다. 



from  피아노의 숲


.......

이제 "지휘자"를 "아키텍트"로 바꿔서 읽어보자..




'IT 이야기' 카테고리의 다른 글

코딩 테스트  (0) 2018.04.17
아키텍트의 역할2  (1) 2012.06.19
다시 성능  (0) 2009.04.02
아키텍트 vs 트러블슈터 vs 컨설턴트  (0) 2009.03.26
튜닝  (0) 2009.03.26
Posted by bleujin
Framework/Database2010. 4. 4. 01:01
Database Framework Scripting Layers is

a lightweight database abstraction layer suitable for high-load websites where you need the scalable advantages of connection pooling. DBFSlayer talks to clients via JSON over HTTP, meaning it's simple to monitor and can swiftly interoperate with any web framework you choose.

Features At A Glance
  • Simple HTTP interface
  • JSON-format messages
  • Multiple DB adapter (currently tested oracle 9 higher, mssql 2000 higher, mysql 5 higher)
  • Connection pooling
  • Multithreaded
  • Straight-forward configuration
  • Simple yet powerful access

Download
https://sourceforge.net/projects/dbfslayers/files/
dbfs_layers_fat.jar include following lib
     - jetty 6.1
     - servlet 2.5, jsp 2.5
     - struts 1.2.9
     - json-lib 2.3
     - bleujin framework_core_fat.jar(http://sourceforge.net/projects/clientcursordbf/)
     - Database JDBC client libraries(offical lib of oracle 9i ,mssql 2000 , mysql 5)


Install
   1. download dbfs_layer_web.zip(https://sourceforge.net/projects/dbfslayers/files/)
   2. extract dbfs_layer_web.zip
   5. download dbfs_layers_fat.jar at 2.dir (https://sourceforge.net/projects/dbfslayers/files/)
   6. configure webapps/simple/WEB-INF/default-config.xml
   6. execute java -jar dbfs_layer_fat.jar
   7. connect http://localhost:8080/simple/index.htm


Configuring Your Database
view webapps/simple/WEB-INF/default-config.xml


Example Usage

run : java -jar dbfs_layers_fat.jar
view : http://localhost:8080/simple/index.htm

<script  language="JavaScript">
    var dc = new Database(new Session('http://localhost:8080/simple/db.do', 'john'));

    var ins = dc.createUserCommand("insert into update_sample values(2, '222')") ;
    $('result').innerHTML = ins.execUpdate() + '<br/>\n' ;

    var cmd = dc.createUserCommand('select * from update_sample where a < :a') ;
    cmd.setPage(3, 1) ;   // 1 page per 3 unit
    cmd.setParam('a', '3') ;

    // var result = cmd.execQuery() ;   // excute. return JSON format
   
    $('result').innerHTML += cmd.execQuery()  + '<br/>\n';

    /*  execute procedure example
    var upt = dc.createUserProcedure('sample@selectEmpBy()') ;
    upt.setPage(3, 1) ;
    $('result').innerHTML = upt.execQuery() ;
   
    */
   
    /*  multiple query(same transaction) example
    var upt1 = dc.createUserCommand('select * from copy_tblc where no1 < :no1') ;
    upt1.setPage(3, 1) ;
    upt1.setParam('no1', '05') ;
   
    var upt2 = dc.createUserProcedure('sample@selectEmpBy()') ;
    upt2.setPage(3, 1) ;
   
    var upts = dc.createUserProcedures('multi query') ;
    upts.add(upt1).add(upt2) ;
   
    $('result').innerHTML = upts.execQuery() ;
    */
</script>



UTF8 Issues

The DBFSlayers JSON requires your data to be in UTF-8 format. This means your database should be in UTF-8 and it should return UTF-8 for queries.



Future Work
  • Batch MDL - batch insert, update, delete
  • Composite Query - select + mdl execute at same transaction
  • LOB Datatype support
  • JTA support at multiple DB
  • Security - add support for HTTPS / HTTP Auth as a basic access control mechanism (security is still primarily handled in the database).
  • Round-Robin Dispatching
  • Automatic Failover

  • Documentation - improvements to the documentation as suggested by the community.
  • Testing - better unit tests are in the works
  • Feeds/Get-if-modified-since Support - some cumulative stats mechanisms could support mechanisms to only download new log messages or if there is new log messages.
  • Language Bindings - the DBSlayer just speaks JSON + HTTP, so many languages should be able to talk to it. We welcome well-written client libraries for any language if you want to share yours.







'Framework > Database' 카테고리의 다른 글

DB Framework 2.0 Short Explain  (3) 2010.03.09
Framework Cursor (cursor in MySql)  (1) 2010.02.24
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Database2010. 3. 9. 12:46

framework_src.zip의 com.bleujin.framework.db.sample에 기본 적인 예제가 있으며 특히 com.bleujin.framework.db.sample에 가장 기초적인 예제가 있다.

해당 예제들을 실행하기 위해서는 sample_mysql.sql(mssql.sql, oracle.sql 역시 모두 같은 역할을 한다.) 의 예제 테이블 등의 내용을 실행시킨다 . 모든 테스트를 실행하기 위해서는 com.bleujin.framework.db.sample.SampleAllTest.java 를 실행한다.


주요 클래스

DBManager

     - 가장 먼저 생성해야 할 클래스로 대부분 JDBC URL과 userID, password를 생성자로 받는다.
        DB별로 혹은
            MySQLDBManger, MySQLPoolDBManager
            MSSQLDBManger, MSSQLPoolDBManager
            OracleDBManger, OracleDBManager, OracleCacheDBManger, Oracle9iCacheDBManger
            H2EmbedDBMangaer, HSQLDBManager 등등

DB별, 접속형태별(Pool여부, Cache여부)로 여러개의 구현체를 가지고 있다.

ex)
DBManager dbm = new MySQLPoolDBManager("jdbc:mysql://novision/test", "bleu", "redf") ;



IDBController


      - DBManger를 생성자로 받는 IQueryable Impl의 Factory 역할을 맡는다.
      - 현재는 IDBController의 구현체로 DBController 한개만 있다.
      - initSelf() 메소드로 dbm의 Pool 초기화 등의 작업을 실행하고 destroySelf() 메소드로 DBManager Pool을 해제시킨다. initSelf()는 생성후 꼭 실행시켜 주어야 한다. 보통의 경우 initSelf()는 프로그램 시작시에 한번, destorySelf()는 프로그램 종료시에 한번 실행한다. Static 변수로 참조하여 사용하는 경우가 일반적이지만 꼭 그럴필요는 없다.

ex)
DBController dc = new DBController(dbm) ;
dc.initSelf() ;
....
dc.destroySelf() ;

     - DBManger와 IDBController를 생성시키고 initSelf()를 실행했다면 이제 쿼리를 실행시킬 준비는 모두 완료되었다.
        가장 간단한 쿼리 실행은
        Rows rows = dc.execQuery(String select) ;
        int result = dc.execUpdate(String mdl) ; 이다.
       



IQueryable
    - 인터페이스이고 execQuery()와 execUpdate()의 주 메소드 2개를 가지고 있다. 모든 실행할수 있는 쿼리는 IQueryable를 상속받는다. 가장 많이 알려진 구현체로 UserCommand, UserCommandBatch, UserProcedure, UserProcedureBatch, UserPorcedures 등이 있다.




IUserCommand

     - 굳이 비슷한 걸 찾자면 JDBC의 PreparedStatement의 역할과 비슷하며 사용방법도 비슷하다.

ex)
IUserCommand cmd = dc.createUserCommand("select * ... where a = ? and b = ?") ;
cmd.addParam(1).addParam("abc") ;
// cmd.addParam(0, 1).addParam(1, "abc") ; // 위구문과 의미가 같다. param index는 0부터 시작한다.

// 혹은 아래와 같이 같은 변수를 여러개 사용하거나 편이성을 위해 named parameter를 사용할수 있다.
IUserCommand cmd = dc.createUserCommand("select * ... where a = :a and b = :b and c >= :a") ;
cmd.addParam("a", 1).addParam(b, "abc") ;

// 만약 특별한 형을 지정하고 싶다면. cmd.addParam("a", 1, Types.LONG) ; 와 같이 명시적으로 지정한다.


      - IUserCommand 실행
Rows rows = cmd.execQuery() ;  // select
int result = cmd.execUpdate() ;     // mdl

       - 실행계획 보기
//실행계획의 해당 DB의 기능을 이용하기 때문에 Format은 DB마다 다른데, MySQL은 표 형태로, MSSQL은 XML Graph형태로, Oracle은 문자열로 보여준다.
cmd.viewPlan(OutputSteam output) ;


       - Page 설정
// 쿼리를 실행하기 전에 Page를 설정하여 해당 Page의 결과값만을 가져올수 있다.
cmd.setPage(Page.create(10, 2)) ;     // 10개씩 했을때 2페이지 즉 11-20번째 row를 가져온다.
cmd.execQuery() ;

이전의 cursor 관련글에서도 확인하였듯이 가장 좋은 방법은 원하는 집함만을 억세스 해서 원하는 집합형태로 가져오는 쿼리를 사용하는 것이 Page 설정에 있어서의 가장 좋은 답이다. 그렇게 생각했기 때문에 예전의 버전에는 Page 관련 기능을 넣지 않았다. 다만 그것은 MySQL의 limit나 오라클의 rownum의 단순한 사용방법보다는 훨씬 더 고난이도이기 때문에 DB별로 자유자재로 할 수 있는 사람이 극히 드물고 또한 매우 번거롭다. 또한 그 효과를 볼수 있는 곳이 제한적이기 때문에 번거로움을 무시하고 모든 곳에 그와 같은 쿼리 방식을 사용하는것은 불필요하다는 아니지만 비효율적이다라는 생각이 들었다.


Rows


   - 쿼리를 실행할수 있는 Interface인 IQueryable의 하위 구현체는 IUserCommand, IUserProcedure, IUserCommmandBatch, IUserProcedureBatch, UserProceudres, TimeoutQuery ... 등등 아주 많은데 그중의 모든 Select 문의 결과값으로 ResultSet의 인터페이스를 구현한 Rows를 반환한다. ResultSet Interface를 구현하였기 때문에 기존의 메소드를 거의 모두 지원하며 몇가지 추가 유틸리티 메소드를 제공한다.

  - 기존의 일반 JDBC에서 반환하는 ResultSet의 구현체와는 다르게 Rows는 Value Object 기반의 클라이언트 커서를 사용한다. 따라서 쿼리의 실행후 pstmt.close(), rs.close(), freeConnection()의 과정은 전부 내부에서 이루어지고 호출자에게 노출되지 않는다. 일단 IDBControler가 초기화된후 dc.getRows() 혹은 cmd.execQuery() 실행후 사용자는 Resource의 반환등에 전혀 신경쓸 필요 없으며 내부적으로 상황에 맞는 결과 캐쉬를 통해 실행속도를 보장한다. rows는 인터페이스 규약상 close()가 있지만 close()를 하지않아도 단순 ValueObject이므로 가비지 컬렉터에 의해 자동으로 정리가 된다.

   - Rows는 거의 순수하게 Value 객체이기 때문에 Framework에서 자동으로 반환한 Resource와 상관없이 Clob 억세스와 쿼리 재실행 등이 가능하며 Serialized XML 형태로 언제든 변환이 가능하다.


rows.getNextPage() ; // 다음 페이지의 rows를 가져온다.
rows.getPrePage() ;; // 이전 페이지의 rows를 가져온다.

   - Clob Access
     JDBC에는 Clob과 Blob의 표준 datetype이 있지만 DB마다 지원여부와 사용방법이 조금씩 다르다. 또한 그 사용방법이 대부분 번거롭게 때문에 varchar의 글자수 제한으로 어쩔수 없이 Clob을 사용해야 하는 경우 매우 불편하다. 그래서 Datatype이 Clob이더라도 DB에 따라 자동 형변환을 통해 rows.getString()을 통해 Access한다. (Blob은 getBinaryStream()) 만약에 Clob에 들어가는 데이타가 100M가 넘는다면 이 방법을 사용하는 것을 신중히 생각해야 할지도 모른다. 그러나 텍스트 1M를 넣는 경우도 매우 흔치 않을뿐더러 그경우에도 Blob으로 다루는 방법을 선택하는게 좋다.

    # insert 혹은 update시 parameter는 DB 벤더에 상관없이 setClob(String str), setBlob(InputStream input) 메소드를 사용하면 된다.




IUserCommandBatch

   - 특정 테이블에 10건의 insert를 해야 한다면 10개의 UserCommand를 실행시키는 것보다 10개의 Arrary를 parameter에 set한후 실행시키는 Batch를 사용하는게 좋다. 평균적으로 최소한 Batch는 건당 insert 속도가 최소 1ms 이하를 보장하며 이는 UserCommand의 기준 속도인 50ms보다 약 50배가 더 빠르다.

ex)
IUserCommandBatch cmd = dc.createUserCommandBatch("insert into update_sample values(?, ?)") ;
int max = 1000 ;
for (int i = 0; i < max; i++) {
        cmd.addBatchParam(0, i) ;
        cmd.addBatchParam(1, i + "th ..") ;
 }
int count = cmd.execUpdate() ;
와 같이 설정하거나..

int[] a = new int[max];
String[] b = new String[max] ;
for (int i = 0; i < max; i++) {
    a[i] = i ;      b[i] = i + "th .." ;
}
cmd.addParam(a).addParam(b) ;
int count = cmd.execUpdate() ;
등과 같이 직접 Array를 인자로 설정한다.


    - 하나의 Batch문에 에 가장 효율적인 건수는 JVM에서 관리되는 Array가 차지하는 메모리에도 밀접한 관련이 있긴 하지만 대략 10,000-100,000 사이가 가장 좋다. row의 평균사이즈를 150-250 byte로 했을때 대부분은 만건당 최대 10초이내의 효과를 보인다.




IUserProcedure

   - 가장 많은 오해를 받지만 가장 효율적이며 많이 사용되는 객체이다. IUserProcedure는 오라클이나 MSSQL의 Procedure와는 직접적인 관계는 없다. 프로그램에 직접 ANSI SQL를 사용했을 경우 이후 DB에 변동이 있다면 재앙수준의 변경을 요구한다. 특히 SQL이 String 변수로 관리되기 때문에 직접 모든 프로그램을 열어서 수정해야 한다. 컴파일 오류가 아닌 런타임 오류가 나기 때문에 수정에 대한 확신도 매우 어렵다.

    이러한 문제로 몇년전부터는 IBatis등에서 XML 형태로 Key Value형태로 관리한후 Key 형태로 Access 하는 방식이 좀더 많이 퍼지게 되었다. 비슷하게 UserProcedure는 앞에서 말한 DB 벤더에 디펜던트한 Procedure와는 직접적 상관없이 그냥 IDName을 가진 Message 객체에 불과하다. 그 Message를 어떻게 해석할 것인가는 DB Manager에 달려 있으며

현재 Framework에 포함되어 있는 OracleDBManger는 패키지와 프로시저, MSSQL은 Procededure로, MySQL은 Procedure 혹은 Function으로 H2DBManager는 특정 XML 파일의 Key-Value 형식의 SQL name으로 해석하고 있다. 즉 UserProcedure는 DB Maanger를 어떻게 구현하는가에 따라 해석방법이 정해지며 일단 같은 name을 가진다면 DB 벤더의 프로시저 혹은 패키지 지원여부와 상관없이 동일한 결과값을 보장해야 한다.


ex)
IUserProcedure upt = dc.createUserProcedure("Sample@selectBy(:a)") ;
upt.addParam("a", 1); 
Rows rows = upt.execQuery() ;

만약 DBManager가 MySQLPoolDBManager라면 위 구문을 Sample_selectBy라는 procedure라고 해석하며
해당 MySQL에는 아래와 같은 Procedure가 있다면 해당 프로시저를 실행한후 그 결과 Rows를 반환한다.

CREATE PROCEDURE sample_selectBy(v_a int)
BEGIN
    SELECT * FROM update_sample WHERE a > v_a ;
END//



만약 DBManger가 오라클이라면 아래와 같이 Sample Package의 selectBy function을 실행시킨후 결과셋을 반환한다.

CREATE OR REPLACE PACKAGE BODY Sample
    is
        function selectBy (v_a number) return Types.cursorType
        is
            rtn_cursor Types.cursorType ;
        begin
            open rtn_cursor For
            select * from update_sample where a > v_a ;
           
            return rtn_cursor ;
        end ;

         ................
       
End Sample ;


     - 이와 같이 DB가 Procedure를 지원하는가 혹은 지원하지 않는가와 상관없이 IUserProcedure는 쓰이며 그 해석방법은 DBManager에게 달려있다. IUserCommand보다 IUserProcedure의 사용을 더 권장하는 이유는 DB 벤더의 장점을 최대한 활용할 수 있고 개발과 유지보수에 있어서의 장점때문이다. 물론 기타 성능이나 보안등의 자잘한 이유는 제껴두고서라도 말이다.


IUserProcedureBatch

    - IUserCommandBatch 처럼 UserProcedure의 Batch 버전이다. 다만 Proceudre 구조상 Batch라서 해서 UserProcedure와 비교해 커다란 성능차이는 없다. 다만 하나의 Procedure에 여러개의 SQL문을 담을 수 있다는 걸 생각하면 복잡한 배치처리 작업에나 쓸만하다.



UserProcedures
    - s가 뒤에 하나 붙어 있다. UserProcedure는 자신이 IQueryable의 구현체이면서 IQueryable의 구현체를 담을수 있는 composite 형태로 되어 있다. UserProcedures에 담긴 모든 MDL문은 자동으로 하나의 Transaction으로 처리가 된다. 그리고 물론 각각의 개별적인 실행보다는 조금 더 빠르다.

ex)
 IUserCommand cmd1 = dc.createUserCommand("insert into update_sample values(?, ?)") ;
 cmd1.addParam(1).addParam("abc") ;
 IUserCommand cmd2 = dc.createUserCommand("delete from update_sample where a = ?") ;
 cmd2.addParam(1);
       
 UserProcedures upts = dc.createUserProcedures("Multi MDL") ;
 upts.add(cmd1).add(cmd2) ;

 int result = upts.execUpdate() ;
 assertEquals(2, result) ;
    
    - 만약 UserProcedures에 여러개의 Select Query를 집어넣고 execQuery()를 실행하면 어떻게 될까? 이 경우에 여러개의 결과값을 반환하게 되는데 Rows.getNextRows()를 통해 다음 결과셋을 얻을수 있다. UserProcedures에 담긴 IQueryable 객체는 add된 순서대로 실행된다.

ex)        
  IUserCommand cmd1 = dc.createUserCommand("select * from copy_sample") ;
  cmd1.setPage(Page.create(10, 1)) ;
  IUserCommand cmd2 = dc.createUserCommand("select 3 from dept_sample") ;
       
  UserProcedures upts = dc.createUserProcedures("Multi Query") ;
  upts.add(cmd1).add(cmd2) ;
       
  Rows first = upts.execQuery() ;  // first query result ;
  assertEquals(true, first.getRowCount() == 10) ;
       
  Rows second = first.getNextRows() ; // second query result ;
  assertEquals(3, second.firstRow().getInt(1)) ;




XAUserProcedure

    - 앞의 UserProcedures는 하나의 DB에 대한 Transaction 보장이며 이기종 DB 즉 멀티 DB에 대한 멀티 MDL문의 분산 트랜잭션은 XAUserProcedure를 통해 할수 있다.


    MSSQL의 접속관리자인 mdc와 오라클의 접속관리자인 odc가 아래와 같이 이미 생성되어 있다고 하자.
// prepare
MSSQLDBManager mManager = new MSSQLDBManager("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=test", "bleu", "redf");
DBController mdc = new DBController(mManager) ;
mdc.initSelf() ;

OracleDBManager oManager = new OracleDBManager("jdbc:oracle:thin:@novision:1521:al", "al", "redf") ;
DBController odc = new DBController(oManager) ;
odc.initSelf() ;
       

// tx1 : MSSQL용 쿼리 집합을 만든다.        
TxTransaction tx1 = new MSSQL2000TxTransaction(mManager, "mquery") ;
tx1.add((Queryable)mdc.createUserCommand("insert into copy_tblc values('111', 111)")) ;
       
// tx2 : oracle용 쿼리 집합을 만든다.
TxTransaction tx2 = new Oracle9iTxTransaction(oManager, "oquery") ;
tx2.add((Queryable)odc.createUserCommand("insert into copy_tblc values('111', 111)")) ;
       

// add tx : 두개의 쿼리 집합을 XAUserProcedure에 넣는다.
TxTransaction[] txs = new TxTransaction[]{tx1, tx2} ;
XAUserProcedure upts = new XAUserProcedure(txs) ;

// 실행한다.
upts.execUpdate() ;


// clear : 더이상 사용되지 않을 DB 접속정보라면 정리한다.
mdc.destroySelf() ;
odc.destroySelf() ;


      - 보통의 XaTransaction 보다 훨씬 더 간편하게 실행할 수 있다. 결국 인터페이스의 간결화로 호출자는 자세한 XaTransaction 과정에 대해 알 필요가 없다.

MSSQL에서 XaTransaction을 사용하기 위해서는
     * Be sure that you have copied your sqljdbc.dll file from C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\SQLServer JTA\ to your SQL Server's "binn" directory (most likely C:\Program Files\Microsoft SQL Server\MSSQL\Binn).
     * Then open your instjdbc.sql script from C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\SQLServerJTA\ within Query Analyzer and run the script. This will install the extended stored procedures into SQL Server so that it can reference the sqljdbc.dll file.

     의 과정을 실행해야 하며 MS DTC가 기동중이어야 한다. MySQL은 얼마전까지 XaTransaction을 지원하지 않았고 현재도 InnoDB storage engine에서만 지원하는 걸로 알고 있다.



CombinedQuery

     - CombinedQuery는 UserProcedure와 조금 비슷한데 단지 UserProcedures와는 달리 MDL문과 Query문을 동시에 실행시킬수 있다는 점이 다르다.


ex)
IUserCommand ins = dc.createUserCommand("insert into update_sample values(?,?)") ;
ins.addParam(1).addParam("abc") ;
       
IUserCommand sel = dc.createUserCommand("select * from update_sample") ;
       
CombinedUserProcedures upts = dc.createCombinedUserProcedures("combined") ;
upts.add(ins, "ins", IQueryable.UPDATE_COMMAND).add(sel, "sel", IQueryable.QUERY_COMMAND) ;

// 타입이 다른 ins문과 sel문을 동시에 실행시킨다.       
upts.execUpdate() ;
       
Map result = upts.getResultMap() ; // for access sel'result
       
int rowcount = (Integer)result.get("ins") ;
Rows rows = (Rows)result.get("sel") ;
       
assertEquals(1, rowcount) ;
assertEquals("abc", rows.firstRow().getString("b")) ;

    - 역시 하나의 Transaction으로 처리된다. 주로 마이그레이션 작업이나 session scope temporary table을 가지고 작업할때 쓰인다. 혹은 Session Level Configuration 명령문과 select문을 혼합할때 쓰이기도 한다. 즉 특정경우에 Set Ansi_null Off 시키고자 할때 session 명령문은 Update로 Select문은 Query로 실행시킨다. 앞어 거듭 말했듯 Rows는 클라이언트 커서이기 때문에 한건을 insert 하고 해당 건을 select하고 해당 건을 지우는 3개의 명령문을 combinedProcedure에 넣고 실행시켜도 제대로 된 Rows 결과물을 볼 수 있다.




ExtraServant


    - ExtraServant는 IQueryable의 구현 클래스가 아니며 이름 그대로 IDBController의 하인 역할를 수행한다. 예컨데 모든 쿼리의 실행시간을 System.out으로 확인하고 싶다고 하자.

ex)
dc.addServant(new StdOutServant(StdOutServant.All)) ;

를 실행시켜 주면 해당 dc로부터 생성된 모든 IQueryable 객체는 실행후 StdOutServant를 통해 쿼리 이름과 실행시간을 System.out에 출력한다. 그와 동시에 300ms 이상의 모든 쿼리는 따로 기록해 두고 싶다고 한다면.

ex)
dc.addServant(new TraceOfLateProcServant(300)) ;
를 추가시키면 된다.

dc는 1개 이상의 ExtraServant를 가지며 모든 쿼리는 해당 쿼리 작업을 완료후 Chain된 ExtranServant를 통해 임의의 추가 행동을 지정할 수 있다. ExtraServant는 별도의 Thread로 관리되며 ExtraServant의 동작시간과 행동은 기존의 작업에 영향을 주지 않는다.

별도의 IDBController를 새로 만들어서 Connection만 공유한채 Servant 정보는 달리 유지시킬수 있다.

ex)
DBController newDc = new DBController("newDC", dc.getDBManager()) ;
newDc.addServant(new StdOutServant(StdOutServant.All)) ; // 모든 IQueryable를 화면에 프린트하는 Servant를 추가한다.
newDc.addServant(new StdOutServant(StdOutServant.All)) ; // 한개 더 추가한다.
newDc.initSelf() ;
       
newDc.getRows("select 1 from copy_sample") ;
newDc.destroySelf() ;

위의 경우 newDC는 기존 dc.getDBManger의 Owner가 아니기 때문에 destroySelf()를 실행시켜도 해당 DBManger의 Pool은 파괴되지 않는다.

     - ExtraServant는 다양한 상황에 새롭게 상속받아 구현함으로써 다양한 역할을 수행할 수 있다. 이를테면 특정 사용자가 접속했을때 MSN으로 메시지를 보내주는 Servant를 구현할 수도 있고 특정 쿼리들이 실행되었을때 Log를 남기는 Servant를 구현할수도 있다.



   Handler

   - Rows는 여러종류의 Handler를 사용하여 다양한 타입으로 변신이 가능하다.


Rows rows = dc.getRows("select * from copy_sample order by no1", 10, 1) ;

첫번째 row를 Map 형태로 바꿀 수도 있고
ex)
Map results = (Map)rows.toHandle(new MapHandler()) ;
assertEquals(2, results.size()) ;   // column 수 확인

모든 row를 Map의 List 형태로 바꿀 수도 있고.
List<Map> results = (List<Map>)rows.toHandle(new MapListHandler()) ;
assertEquals(10, results.size()) ; // row수 확인

모든 row를 Bean List 형태로 바꿀 수도 있고
List<TestBean> results = (List<TestBean>)rows.toHandle(new BeanListHandler(TestBean.class)) ;
TestBean row = results.get(0);

assertEquals(1, row.getNo1()) ;
assertEquals("01", row.getNo2()) ;

assertEquals(2, results.get(1).getNo1()) ;
assertEquals("02", results.get(1).getNo2()) ;


특정 컬럼의 값만을 얻어올수도 있다.
Object value = rows.toHandle(new ScalarHandler(2)) ; // 첫번째 row의 2번째 컬럼의 값
assertEquals("1", value.toString()) ;




6년전에 해당 Framework를 처음 개발했을때 목적은 4가지이다.

1. 풀링을 구현하되 프레임워크 사용자가 실수로라도 Connectionless를 만들 수 있는 방법을 원천적으로 차단한다.
    -> Connectio은 외부에서 보이지 않으며 Client Cursor 사용으로 결과셋 전달후 Resource는 자동으로 바로 반환한다. 즉 사용자는 JDBC의 Resource에 대해 알 필요도 없으며 알 수도 없다.

2. 프로그램에 SQL문을 심지 않는다.
    -> UserProcedure의 적극적인 사용으로 Query문은 모두 중앙집중 관리 되며 이는 개별적인 DB 벤더의 장점은 최대한 활용함과 동시에 DB 벤더에 종속되지 않는다는 상이한 목표를 동시에 추구한다. IQueryable의 모든 구현체는 단순히 Message역할만을 수행하기 때문에 JDBC의 PreparedStatement와 달리 정리해줘야 할 Resource가 아니다.

3. LOB 핸들링을 쉽게 한다.
   -> 벤더마다 그리고 버전마다 Lob Handling에 있어서의 사용방법이 다른걸 간단한 인터페이스로 통합시고 사용자는 byte 단위의 Access Handling을 하지 않아도 되게 한다.

4. 사용방법은 쉬워야 함과 동시에 여러가시 발생할 수 있는 상황에 쉽게 적응 가능해야 한다. 동시에 불필요한 퍼포먼스 감소를 일으켜서는 안된다.



이후.. 한동안 추가 기능이 없다가 최근에

  - MySQL을 기존의 MSSQL, Oracle과 더블어 기본 테스트 항목및 지원DB로 함.
  - 인터페이스의 소폭 수정
  - viewPlan(Query의 실행계획보기)
  - Paging (쿼리의 Page 설정, nextPage, prePage Access)
  - NamedParameter

등등의 잡다한 기능을 넣었다. 가장 큰 이유는 Paging 때문인데 JDBC에 Client cursor에 Page 관련 메소드가 있는데 제대로 implement한  JDBC가 거의 없어서 그냥 만들었다. -ㅅ-. Page는 DB 마다 처리 방법이 다르지만 해당 DB에 가장 효율적인 방법을 사용하려고 했다.








사용자는 framework_core_fat.jar를 추가하고 해당 DB의 JDBC jar만 추가시켜서 사용하면 된다.

아래와 같은 Apache Common Jar를 사용한다.
<fatjar.jarsource file="lib\jericho-html-2.5.jar" relpath=""/>  // 얘는 parser framework에 쓰임
<fatjar.jarsource file="lib\jmock-core-1.2.0.jar" relpath=""/>
<fatjar.jarsource file="lib\junit.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-pool-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-beanutils-1.8.0.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-collections-3.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-dbcp-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-digester.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-fileupload-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-io-1.2.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-lang-2.3.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-logging-1.0.4.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-net-1.3.0.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-validator-1.1.4.jar" relpath=""/>



'Framework > Database' 카테고리의 다른 글

DBFSlayers  (1) 2010.04.04
Framework Cursor (cursor in MySql)  (1) 2010.02.24
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Database2010. 2. 24. 09:55


이전글에서 커서의 일반 지식과 오라클과 MSSQL의 커서 방식에 대해 얘기 했으니 3번째로 MySQL의 커서 방식을 이야기해보자. 사실 개인적으로 MySQL은 상대적으로 자주 사용하는 DB가 아니다. MySQL은 "거의" 무료지만 다른 2개의 디비에 비해 기능과 지원이 부족하고 다른 '완전한' 무료에 비해 그리 많이 좋지도 않는 어정쩡한 위치에 있기 때문이다.

거의 1년만이므로..
다시 기본조건을 상기해보자.

JDBC로 stmt.execQuery("select * from millonRow_tblc") ; 를 실행했을때..


Large ResultSet의 Handling에 관한 문제이다.


우리가 먼저 시작해야 할 점은 사용자는 과연 모든 결과가 필요할까? 라는 점이다.

물론 100만개의 Row가 모두 필요한 프로그램일수도 있다. 하지만 그 1%의 확률로 모든 Row가 필요한 프로그램이라고 하더라도 그 중에 다시 99% 이상은 DB에서 연산이 가능하다. DB는 다른 어떤 통계 프로그램보다 많은 연산자와 빠른 속력을 지원해주기 때문에 정말 화면에 모든 Rows를 뿌려야 하는 0.01%의 확률의 프로그램이 아니라면 연산을 DB에서 하는게 좋다.

어쨌거나 지금 그 0.01%의 확률의 프로그램을 만들어야 한다고 가정하자.
즉 당신은 이유야 어쨌거나 "select * from millonRow_tblc" 모든 결과 셋이 필요하다.


DB가 MySQL일때
pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
rs = pstmt.execQuery() ;
를 실행해보자.

아마도 특별히 Java의 Heap 메모리를 늘리지 않았다면 Out of Memory 예외를 보게 될것이다.
단 한행도 rs.next()를 하지 않았음에도 불구하고 말이다.

한행이 약 100byte라고 가정했을때 0.1k * 1000000 = 100M로
Select의 모든 결과셋의 크기가 기본 Java Heap Memory의 64M를 초과하기 때문이다.


MySQL에서 - 비록 공식문서에는 나와 있지 않지만
당신이 특별한 다른 옵션을 사용하지 않았을때 Client Cursor Location에 Static Cursor Type을 사용한다.

즉 JDBC에서 위 예제를 실행했을때
해당 쿼리의 모든 결과셋을 Client(여기서는 Application Server가 Client)에 전송하고 Client에서는 그 결과셋을 모두 받아서 Client 메모리안에 Table 형의 결과셋을 만들어 채우는 과정을 한다. 보통의 다른 Set 알고리즘과 마찬가지로 기본 Set이 거의 채워지면 해당 Set의 기존 Size를 1.5배 늘리고 이 용량이 다시 채워지면 다시 1.5배를 늘리는 동작을 반복한다.
다시 말해서 위의 경우 DB에 던지는 결과셋을 받아서 계속 Set의 용량을 늘리다가 어느순간 한계 Heap Size에 도달하게 되는 것이다.

따라서 정말 millonRow_tblc의 Table의 모든 Row가 필요하다면 Heap Size를 늘려야 한다. 그러나 근본적인 해결책은 아니다. 왜냐하면 Heap의 Size를 10배로 늘렸다고 해도 10명이 동시에 해당 프로그램을 실행하면 역시 Out of Memory Exception을 보게 된다. 백만 Row를 모두 받아서 Clinet에 Set을 구축하기까지는 꽤 오랜(아마도 컴퓨터 성능에 따라 1 - 10분 사이) 시간이 걸리기 때문에 10명이라는 수치는 매우 자주 일어나는 경우가 될것이다. 물론 사용자가 단 1명이더라도 결과를 보기위해서는 1-10분을 기다려야 한다.



공식적인 해결방법은 아니지만 MySQL에서는 일종의 꽁수같은 방법이 있다. 이것은 일종의 버그이므로 앞으로도 계속 지원되는 방법이라고는 보장할수 없다.

pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
psmt.execQuery() ;

위와 같이 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY를 사용하고
FetchSize에 Integer.MIN_VALUE를 설정하면
MySQL은 Client에 더이상 Static Cursor를 사용하지 않는다. 위와 같이 작성할경우 MySQL은 FireHouse Cursor방식을 사용한다. FIreHouse Cursor는 앞에서 나왔던 MSSQL의 기본 cursor이다.

공식적인 cursor Type에서는 언급하지 않지만 인터넷 환경에서는 묵시적으로 자주 사용된다. FireHouse 커서 동작방식에 대해 말하기 전에 MYSQL의 JDBC는 Server Cursor Location을 지원하지 않는다. 물론 근본적으로 JDBC의 API에 Cursor Location을 설정할수 있는 메소드가 없기 때문이기도 하지만 그럼에도 MSSQL에서는 CursorType에 따라 묵시적으로 바뀌는 것과 달리 MySQL에서는 아예 Server Cursor Locatiion을 공식적으로 지원하지 않는다.

따라서 MSSQL의 FireHouse 커서와는 조금 다른 동작을 한다.

pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
rs = psmt.execQuery() ;
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}

와 같이 실행하였다면 반갑게도 바로 결과가 나올것이다. Connection Pool을 사용했다면 0.1초 이내에 말이다.

유레카~ 라고 소리지르기 전에..알아야 할 것이 있다.
FireHouse 커서방식은 Static Cursor Type과 달리 모든 결과셋을 Client에 구축하지 않고 Row Level 단위로 Access할수 있는 장점이 있긴 하지만 단점이 2가지가 있다.
첫번째는 rs는 더이상 previons()나 relative/absolute move를 지원하지 않는다. 이는 FireHouse의 동작방식을 생각해보면 당연한 것이다. FireHouse방식에서는 rs.next()를 호출하는 순간에 더 이상 이전 previous의 커서의 위치나 정보를 보관하지 않는다. next()를 호출할때마다 이전 record의 정보는 메모리에서 날림으로서 Out Of Memory를 피하는 방식이다. 그리고 당연히 rs.getRowCount() 같은 메소드도 사용할수 없다. 만약 RowCount를 알고 싶다면 while(rs.next()) i++ 와 같이 구할수 밖에 없다.

첫번째는 사실 그다지 큰 문제는 아니다. 보다 중요한 것은 두번째 단점이다.
두번째 단점은 MYSQL의 Client Location의 FireHouse Cursor는 멈추지 않는다는 것이다.
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}
와 같이 단지 10행만을 사용한다고 하더라도 MYSQL DB는 그걸 인지할수 없으며 여전히 남은 99만 9990개의 남은 row를 Client에 전송한다.

그래서
pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
rs = psmt.execQuery() ;
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}
까지 0.1초가 걸리더라도..

rs.close() ;
이 문장을 실행하는데 1분 - 10분 정도가 걸린다. FireHouse는 일종의 Data Stream이기 때문에 모든 결과셋을 받기 전까지는 rs를 close 할수 없다. 그렇다고 rs.close()를 하지 않으면 해당 Connection은 미아가 되어 얼마지나지 않아 Connection Pool Exception을 보게 된다.

가끔 특정 사이트를 보면 화면에 내용은 거의 다 뿌려졌는데 브라우저 하단의 Progress Bar는 여전히 진행중인 사이트를 본다면 반이상은 JSP로 위의 방식으로 코드를 작성했기 때문이다. response.flush()로 일단 화면에 보여지지만 JSP코드 하단의 rs.close()를 하는데 오래오래 걸리고 있기 때문이다.

MVC가 머고간에 일단 JSP에서 JDBC를 억세스 해서 close()를 하기전에 일단 Access한것만이라도 flush로 먼저 보낸다면 화면에는 빨리나온것처럼 보이기에 일단 급한 마음에 그렇게 한것이다.

Lazy Initialize 패턴같은 사용방식처럼 보일수도 있지만 이런다고 문제는 해결되지 않는다. 일단 Client에서 빨리 보여지느냐 늦게 보여지느냐에 상관없이 FireHouse Cursor Mode에서 Server인 DB는 여전히 남은 Row 들을 보내고 있다. 그 말은 귀중한 자원인 DB는 Network Channel에 사용하지 않을 Row를 쓰고 있고 여전히 Application Servers는 사용하지 않을 Row을 받고 있다는 얘기이다.

최근의 대부분의 하드는 100Mb read/sec 이상의 속도를 가진다. 그럼에도 대부분의 Network Channel은 고작해야 10Mb / sec이기 때문에 필요도 없는 Row를 Network로 보내느라 DB는 계속 바쁘다.

처음에 언급한바와 같이 상황에 따라 DB에 따라 심지어는 버전에 따라 이와 같은 문제를 해결하는 방식은 다르기에 이전의 Mssql의 방식을 사용할수는 없다.(다시 말하지만 MySQL의 JDBC에서는 Server Cursor Location이 없다.) 해결방법은 다음글에서.. =ㅅ=


'Framework > Database' 카테고리의 다른 글

DBFSlayers  (1) 2010.04.04
DB Framework 2.0 Short Explain  (3) 2010.03.09
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Another Lore2009. 8. 14. 15:49

AL을 처음 시작할때인 2007년에
AL의 최종 진화 모습은 DB를 사용하는게 아니라 DB 그 자체라고 얘기를 한적이 있는데..

결국 DB를 만들고 있다 -ㅅ-;

험난하다. -ㅅ-; 후. 

'Framework > Another Lore' 카테고리의 다른 글

XUL 단상  (0) 2009.07.15
레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin