부모 노드가 자식 리스트를 보유하는 방식은

데이터에 내 자식 리스트(배열)을 표시하면 된다.

앞서 구현한 자식 노드에서 부모 노드를 지정하는 방식은

부모가 하나이기 때문에 parent 변수 하나만 추가하면 됐지만

한 부모의 자식은 여럿이 될 수 있기 때문에

배열(children)을 사용해야 한다.

그리고, 자식의 자식의 자식의 ... 로 작성될 수 있기 때문에

배열의 배열의 배열...

과 같이 무한하게 표시될 수 있다.

이렇게 무한하게 표현되는 데이터를 처리하기 위해서는

데이터가 있는 만큼 무한하게

반복해서 처리할 수 있는 재귀 함수가 필요하다.

데이터(행)를 처리하고

해당 데이터에 자식이 있으면

다시 데이터를 처리하고

해당 데이터에 자식이 있으면 ....

이렇게 반복되는 것을 하나만 함수로 작성하고

데이터가 없을 때까지 반복해서 호출하는 것을 재귀함수 라고 한다.



sample2_1.html


먼저, 20 라인의 데이터를 보면

앞서 구현한 자식 노드에서 부모 노드를 지정하는 방식과 비슷하게

자신을 나타내는 ID가 추가되었다.

다만, 부모 대신에 자식들(children)이 배열로 구현되어 있다.

부모는 하나지만

자식은 여러 명일 수 있기 때문에 복수형으로 사용한다.

자식 필드로 children이 선언되고

부모와 동일한 구조로 데이터가 저장된다.

여기서는 처리하지 않았지만

자식의 자식일 경우

Children을 계속 넣어주면 된다.


앞서의 방식보다 코드가 깔끔해 보이는데

재귀 호출을 사용하였다.

재귀 호출, 재귀 함수라고도 하는데

자신이 자신을 부르는 것을 의미한다.

자식이 어느 정도(몇 대손)까지 있을 지 모르기 때문에

자식에 대한 처리를 위해 재귀 호출을 사용했다.


재귀 함수인 setData는

파라미터로 데이터와 부모 태그, 깊이(몇 대손)를 주게 된다.

데이터는 자식 리스트(children)를 의미하고,

부모 태그는 부모 TR을 의미하는 데

첫 데이터(단군)은 테이블(table) 태그이다.

깊이는 처음에 0 일 것이고

재귀호출 할 때 마다 1 씩 더해 준다.


주어진 데이터의 개수 만큼 반복해서 $.each( data, function( index, row)

TR을 생성하고

CSS 클래스 명으로 이름을 부여한다.

다만, 위치는

부모가 없으면 테이블에 추가하고(appendTo)

부모가 있으면 부모 다음에 넣어준다 (insertAfter).

다만 조금 더 정확하게 구현하려면 부모 다음이 아니고

해당 부모의 마지막 자식 다음이 되어야 한다.

구현 방법이 복잡해서 여기서는 부모 다음에 추가했다.

부모가 있는지 없는지는

파라미터로 넘어온 부모의 태그 타입(nodeName)이

테이블이면 부모가 없는 것으로 처리했다.


들여쓰기 하는 방식은

자식 노드에서 부모 노드를 지정하는 방법과 같이

깊이의 개수만큼 SPAN 태그를 생성해 준다.


마지막으로 자식이 있으면 if (row.children)

다시 자신(setData)을 호출하고

파라미터로 자식 리스트를 넣어주면 된다.


자식 노드에서 부모 노드를 지정하는 방식(sample2.html)과

부모 노드가 자식 리스트를 보유하는 방식(sample2_1.html)을 구현하였다.

일반적으로 대부분의 라이브러리들이 (TreeGrid jQuery, EasyTree)

부모 노드가 자식 리스트를 보유하는 방식을 사용하고 있다.


하지만, 개인적으로

자식 노드에서 부모 노드를 지정하는 방식이 더 좋다고 생각한다.

데이터 베이스에 데이터가 저장되는 방식이

자식 노드에서 부모 노드를 지정하는 방식과 같기 때문이다.

(본 블로그의 무한 댓글 참조)

즉, 데이터 베이스에 ID와 Parent 필드로 저장하고 있다.

부모 노드가 자식 리스트를 보유하는 방식을 사용하게 되면

ID와 Parent 필드로 구성된 데이터를 정리해서

자식 리스트를 작성해야 하기 때문이다.

즉, Java나 PHP등의 서버 언어에서의 처리가 있어야 한다.


따라서 JS 코드만 보면

자식 노드에서 부모 노드를 지정하는 방식이 더 복잡하지만

데이터 베이스의 데이터를 그대로 사용할 수 있기 때문에

이후의 기능 개선도

자식 노드에서 부모 노드를 지정하는 방식으로 구현할 예정이다.



라이브러리화 한다는 것은

HTML 파일에 같이 작성된 자바 스크립트(JS) 코드를

별도의 JS 파일에 함수로 구현하고

이 함수만 호출하면

TreeGrid를 사용할 수 있도록 하겠다는 것이다.

또, 사용자(개발자)가 자신의 상황에 맞추어

간단한 설정만 하면 TreeGrid를 사용할 수 있도록 하는 것이다.

즉, TreeGrid 라이브러리 파일(sample3_treegrid9.js)과

이 라이브러리를 사용하는 HTML 파일(sample3.html)로 분할 해서 구현한다.


라이브러리화한 후,

HTML 파일에 남아있는 코드를 보면

복잡한 코드는 사라지고 option과 data만 남은 것을 볼 수 있다.

option과 data는

TreeGrid 사용자의 상황에 따라 바꿔서 지정하고

사용자는 30 라인에 있는 것처럼

TreeGrid9을 실행만 시키면 된다.


sample3.html


기존의 코드는 다음과 같이 sample3_treegrid9.js파일에 작성하고

HTML(sample3.html)에서는

8 라인에서 작성한 것처럼 <script src="sample3_treegrid9.js"></script>

JS 파일을 가져오기 하면 된다.

sample3_treegrid9.js


이상의 코드와

이전 예제(sample2.html)와 차이는

1 라인의 TreeGrid9 함수뿐이다.

외부 파일로 빼면서 함수로 구성한 것이다.

모든 코드는 동일하다.

다만, HTML에서 호출할 때 함수로 호출하지 않고

New를 사용해서 클래스로 실행했다.

간단하게는 함수화해서 함수를 호출해도 되지만

앞으로 여러 가지 기능을 추가해 나갈 것이기 때문에

클래스 방식으로 사용하였다.

JS에서는 함수와 클래스가 유사하게 사용된다.

자세한 설명은 [JS 클래스]로 검색하면 찾을 수 있다.



본 예제는 모든 데이터를 화면에 출력하고

자식 노드는 부모 노드 보다 뒤로 들여쓰기 하여 구현했다.

이번 단계에서는 부모 노드를 클릭하면

자식 노드를 감추고 보여주는 기능을 구현할 예정인데

쉽게 코드를 설명하기 위해

숨기는 기능부터 먼저 구현해 본다.


기능 구현을 위해 고려해야 할 대상은 다음과 같다.

  • 클릭 대상 선택
  • 자식이 있는 부모와 없는 부모, 자식을 보여주고 있는지 숨기고 있는지 구분하는 방법


둘 다 같은 결과를 필요로 하는데,

하나의 노드(행)를 클릭해서 자식을 숨기게 되면

현재 어떤 노드가 선택되었는지 보여 주는 기능을 구현하기 어렵다.

(행 선택 기능으로 여기서는 구현하지 않는다.)

그리고 두 번째 기능을 구현하기 위해

대부분 노드 앞에 특정 기호를 사용하게 된다.

즉, 특정 기호로 자식이 있는지 없는지 표시하고

특정 기호를 클릭하면 자식을 보이거나 숨기게 구현다.


윈도우 탐색기를 예로 하면

하위 폴더가 있는 경우 + 기호가 나오고

부모 폴더의 + 기호를 클릭하면

하위 폴더가 나타나며 – 기호로 바뀌게 된다.

여기서는 이 기호를 Expander라고 하고

Expander를 클릭하면 자식을 숨기거나

보이도록 구현한다.


다음으로 처리해야 할 것은

Expander를 클릭하면

내 자식 노드들을 찾아서 숨기는 것이다.

나의 자식은 나를 부모로 지정한 노드들을 의미하고

나를 부모로 지정한 노드들은

클래스명에 "treegrid9-parent-"를 이용하여

나(나의 ID)를 지정한 노드들을 의미한다.


이상의 개념을

다음과 같이 코드로 구현하면 되는데

코드를 보기 전에

위 개념을 토대로 직접 작성 해보는 것이 실력향상에 도움이 될 수 있다.


sample4.html

HTML 파일에서 11 라인에 CSS 클래스 하나를 추가 했다.

자식 노드를 숨기고 보여주기 위한 expander와 관련된 것으로

treegrid9-indent와 같은 기능을 하지만

expander 기호에 마우스를 올리면

클릭할 수 있는 기호로 바꾸는 스타일이 추가된 것이다.


sample4_treegrid9.js

먼저 30 라인을 보면 If문 조건이 바뀌었다.

이전에는 첫 번째 필드(i===0)이고

깊이가 0 이상 (depth>0)이면 들여쓰기를 하도록 했는데,

현재는 깊이 조건이 빠져서

모든 노드에서 들여쓰기 span이 생성된다.

자식이 있는 부모에 expander 기호를 붙이게 되면

다음과 같이 자식이 없는 부모와 줄 맞춤이 맞지 않게 된다.

           + Job1
           Job3

따라서 모든 노드에 span을 생성하고

자식이 있는 부모 노드만 span에 expander 기호와

CSS 스타일(treegrid9-expander)를 넣어서 보기 좋게 만들었다.


다음으로 기존 코드에 추가된

13 라인부터 22 라인을 이해하면 된다.

자식이 부모를 지정하는 방식이기 때문에

자식을 생성하다가

부모 ID(row.parent)로 부모를 찾아서 $(".treegrid9-" + row.parent)

부모의 클래스 명에 treegrid9-expanded라는

이름이 있으면 부모 표시가 된 것이니 넘어가고

없으면 부모 표시가 되지 않은 것이니 !parent.hasClass("treegrid9-expanded")

부모 표시와 처리를 진행하게 된다.

클래스명 treegrid9-expanded는 식별자로 사용되는 이름이고

별도의 CSS 지정을 하지 않았다.

이렇게도 많이 사용하기 때문에 기억해두면 좋을 것이다.


부모 처리가 되지 않았으며

먼저, expander로 사용할 span 태그를 찾는다.

여기서는 부모(parent[0] 즉 TR)의

첫 번째 셀(TD) 중에서 첫 번째 자식(span)을

expander 기호를 넣을 태그로 보고 .addClass("treegrid9-expander")

– 기호를 넣어준다. expander.html("-")


expander(기호)를 클릭하면

현재 노드의 자식 노드를 찾아서 숨겨주는 처리를 해야 한다.

클릭한 개체(expander 즉 this)에

가장 가까운(closest) TR을 찾아서 $(this).closest("tr")

현재 노드를 파악하고,

현재 노드의 ID을 추출한 뒤 getNodeId(tr)

해당 ID을 부모로 지정한 "tr.treegrid9-parent-" + id

모든 자식(TR)을 찾아서 find

보이지 않게 (hide) 처리하면 된다.

JQuery의 closest, find, hide를 활용한 코드로

자세한 설명은 찾아 보길 바란다.


마지막으로 현재 노드의 ID 값을 찾는 getNodeId 함수는

getDepth 함수와 같이 정규식을 사용해서

ID 값(숫자)만 남겨서 반환하는 함수이다.


sample4.html 파일을 실행한 후

expander(-)를 클릭해서

다음과 같이 Job2가 숨겨지는지 확인해 보자




앞서의 예제에서는 expander를 클릭하면

자식 노드가 숨겨지게 코드를 작성하였다.

여기서는 expander를 다시 클릭하면

감춰진 자식 노드가 보이는 기능을 구현한다.

즉, expander를 클릭하면 숨겨지고,

다시 클릭하면 나타나고

다시 클릭하면 숨겨지도록 구현한다.


개념부터 정리하면 모든 노드는 펼쳐져 있다.

이때 부모 노드의 expander 기호는 모두 – 이다.

부모 노드의 expander를 클릭하면

기호는 –에서 열수 있다는 의미인 + 기호로 바꾸고

자식 노드들을 보이지 않게(Hide) 처리한다.

다시 expander를 클릭하면

열렸다는 의미로 기호는 – 로 바꾸어 주고

자식 노드들을 보이게(show) 처리하면 된다.


간단한 개념이기 때문에

직접 구현해 본 후 다음 코드를 보는 것이 좋다.


기존 코드에 다음과 같이 몇 라인만 바꾸어 주면 된다.

            if (!parent.hasClass("treegrid9-expanded")){
                parent.addClass("treegrid9-expanded");
                var expander = $(parent[0].cells[0].firstChild).addClass("treegrid9-expander");
                expander.html("-");
                expander.click(function() {
                    var isexpanded = $(this).html() === "-";
                    if (isexpanded) {
                        $(this).html("+");
                    } else {
                        $(this).html("-");
                    }
                    var tr = $(this).closest("tr");
                    var id = getNodeId(tr);
                    table.find("tr.treegrid9-parent-" + id).each(function(i, e){
                        if (isexpanded)
                            $(this).hide();
                        else {
                            $(this).show();
                        }
                    });
                });
            }

sample5_treegrid9.js

expander를 클릭하면

expander의 기호가 무엇인지 판단해서 $(this).html() === "-"

자식 노드를 보일 것인지(show) 숨길 것인지(hide)를 처리한다.


자식노드를 찾는 것도 앞서의 코드와 동일하다.


실행해 보면 트리 라이브러리처럼

자식이 보였다 안보였다 하는 것을 확인 할 수 있다.


이제 데이터를 바꿔서 실행해 보자.

var data = [
                {id:"1", "task": "job1", "startdate": "2016-10-11", "enddate": "2016-11-11"},
                {id:"2", "task": "job2", "startdate": "2016-10-11", "enddate": "2016-10-30", parent: "1"},
                {id:"3", "task": "job3", "startdate": "2016-11-12", "enddate": "2016-12-11", parent: "2"}
            ];

sample5.html

이전 예제에서는 계층 구조(depth)가 1 단계였는데

Job3을 Job2의 자식으로 변경하여 실행해 보자.

다음 그림과 같이 실행된다.

몇 가지 문제가 발생하는 것을 알 수 있다.

먼저, 계층이 많아지니 expander 기호의 위치가

모두 행의 맨 앞에 있어서 적절하지 않아 보인다.

그리고, 마우스로 클릭해보면

바로 아래의 자식은 숨겨지는데

자식의 자식은 숨겨지지 않는다.


어떻게 수정해야 하는지 생각해 보고

다음 예제에서 이 문제를 해결하는 방법을 정리한다.





다음과 같이 데이터를 구성하고 실행하면

var data = [
                {id:"1", "task": "job1", "startdate": "2016-10-11", "enddate": "2016-12-11"},
                {id:"2", "task": "job2", "startdate": "2016-10-11", "enddate": "2016-10-30", parent: "1"},
                {id:"3", "task": "job3", "startdate": "2016-11-12", "enddate": "2016-12-11", parent: "2"},
                {id:"4", "task": "job4", "startdate": "2016-12-12", "enddate": "2016-12-31"}
            ];

sample6.html

다음과 같이 2 가지 문제가 있는 것을 찾았다.

1. expander 기호의 위치가 행의 맨 앞에 있어서 적절하지 않아 보인다.

2. job1의 expander 기호를 클릭하면 job2만 숨겨진다.

  즉, 손자(job3)은 숨겨지지 않는다.


이 버그도 쉽게 해결할 수 있으니

먼저 해보고 다음 설명을 읽는 것이 좋을 것 이다.


            if (!parent.hasClass("treegrid9-expanded")){
                parent.addClass("treegrid9-expanded");
                var expander = $(parent[0].cells[0]).find("span").last().addClass("treegrid9-expander");
                expander.html("-");
                expander.click(function() {
                    var isexpanded = $(this).html() === "-";
                    if (isexpanded) {
                        $(this).html("+");
                    } else {
                        $(this).html("-");
                    }
                    var tr = $(this).closest("tr");
                    getChildren(getNodeId(tr), isexpanded);
                    function getChildren(id, isexpanded) {
                            table.find("tr.treegrid9-parent-" + id).each(function(i, e){
                                 if (isexpanded)
                                    $(this).hide();
                                 else {
                                     $(this).show();
                                 }
                                id = getNodeId($(this));
                            if (table.find("tr.treegrid9-parent-" + id).length>0){
                                getChildren(id, isexpanded);
                            } ;
                        });
                    }
                });
            }

sample6_treegrid9.js


먼저 expander 기호의 위치 문제는

노드의 맨 앞에 있는 기호를

뒤로 보내서 task 필드의 값 바로 앞에 두면 된다.

들여쓰기와 expander 기호 표시를 위해

task 필드 앞에 span을 생성하였는데,

기존에는 expander 기호를 맨 앞 span에 넣었다.

이것을 맨뒤 span에 넣으면 된다.


따라서 다음과 같이 코드를 수정한다.

기존    var expander = $(parent[0].cells[0].firstChild).addClass("treegrid9-expander");

변경    var expander = $(parent[0].cells[0]).find("span").last().addClass("treegrid9-expander");

기존에는 첫 번째 TD(task 필드)의

첫번째 자식(span)에 기호를 넣었고,

수정된 코드는 첫 번째 TD(task 필드)의자식 중

span을 찾아서 마지막(last) 자식에 기호를 넣게 작성하였다.

그러면 다음 그림과 같이 task 필드의 값 바로 앞에

expander 기호가 위치하게 된다.


두번째 문제는

job1의 expander 기호를 클릭하면 job2만 숨겨지고

손자(job3)은 숨겨지지 않는 것이다.


이것은 자식 찾기를 한번만 했기 때문에 발생한다.

앞서 코드 구성에서

바로 위 부모만(직계 존손) 자신의

자신의 노드 클래스명에 표시하도록 했고(treegrid9-parent-)

부모는 자신을 부모로 표기한 이 값만을 찾도록 하고 있다. table.find("tr.treegrid9-parent-" + id).each(function(i, e)

즉, 모든 자식을 찾아야 하는데

모든 자식을 찾으려면

나(클릭한 노드)를 중심으로

나를 부모로 하는 자식 노드를 찾아서 숨기거나 보이도록 처리한다.

그리고, 각 자식 노드를

부모로 하는 노드가 있는 지 확인해서 if (table.find("tr.treegrid9-parent-" + id).length>0)

있으면 다시 자식 노드를 찾아서 숨기거나 보이도록 처리한다.

그리고, 다시 각 자식 노드를

부모로 하는 노드가 있는 지 확인해서 if (table.find("tr.treegrid9-parent-" + id).length>0)

이렇게 무한 반복으로

자식 노드를 찾아서 처리해야 한다.

복잡해 보이지만

이것은 재귀 함수를 사용하면 간단하게 처리된다.

기존 코드를 getChildren 함수에 넣고

이 함수를 자식이 있으면 호출하도록 작성하였다.


또, getChildren를 함수내 내장 함수로 작성했다.

개인적으로 JS의 재미있는 기능이라고 생각하며

자세한 내용은 찾아보길 바란다.


지금까지 간단한 TreeGrid를 제작하는 예제를 통해서

자바스크립트, JQuery, JSon 등의 사용법을 익혀보았다.

노드 선택, Ajax 처리 등의 기능을 추가하면

제법 그럴듯한 TreeGrid를 만들 수 있을 것이다.

누군가 제대로 된 오픈 소스 라이브러리를 개발하길 기대하며……


과제 관리 시스템 PMS9 (Project Management System)은

기업내의 과제를 온라인으로 관리하기 위한 도구로,

Project9 (Spring 4 + MyBatis 3 + MariaDB 기반으로 제작한 웹 프로젝트 템플릿)을 기반으로,

Project9을 응용하는 방법을 표현하기 위해 제작하였다.

즉, 쉽고 빠르게 개발하는 방법을 정리하기 위한 예제로 제작하였다.

핵심기능 위주로 제작하여 공개하고,

시간 나는데로 확장할 계획이다.


데모는 여기에서 확인할 수 있고

소스는 github에서 다운 받을 수 있다.

설치 방법은 Project9과 동일하기 때문에 Project9 설치를 참고하길 바라고

설치시 guthub 주소를 다음과 같이 바꾸어 줘야 한다.

        https://github.com/gujc71/pms9.git


설치를 완료한 뒤,

웹 브라우저에서 http://localhost:8080/pms9/로 접속하여 확인한다.



다음은 간단한 화면 설계서 이다.


마지막으로 테이블 구성(ERD)는 다음과 같다.

AmaterasERD로 작성되었고

파일명이 pms9.erd로 github에서 같이 배포되고 있다.

대부분의 테이블 구성이 Project9의 ERD와 동일하지만

과제와 관련된 4개의 테이블이 추가되었다.

프로젝트, 프로젝트 업무(task), 업무 담당자, 첨부파일의 4개로

ERD왼쪽에 배치되어 있다.

Logical mode

Physical mode


'Java > 기타' 카테고리의 다른 글

그룹웨어 (Java) - 설치  (27) 2018.04.15
Project9  (8) 2016.09.18
Project9 설치  (5) 2016.09.18
데이터를 Excel 파일로 다운로드  (2) 2016.09.18

실력 향상을 위해서는

다른 사람의 코드를 많이 보는 것이 중요한데

괜찮은 프로그램 소스를 얻는 것이 쉽지가 않다.

인터넷에서 찾을 수 있는 것들 중

Mybatis에서 Spring 기반으로 제작한 샘플인 JPetStore가

작지만 많은 것을 포함하고 있어

얻는 것이 많을 것 같아서 정리해 봤다.

가장 유명한 샘플이라고 생각한다.


JPetStore는 Spring 4와 Mybatis 3, HSQLDB를 이용하여

애완동물을 파는 기본적인 쇼핑몰을 구축한 샘플로

Mybatis에서 제공하는 샘플이 있고(jpetstore),

다양한 기술들을 소개하기 위해 변형해서 만든 샘플들이 있다.

 약어

주소

 DBMS

 특징

jpetstore (기본)

https://github.com/mybatis/jpetstore-6

 HSQLDB

MyBatis 3, Spring 4 and Stripes

spring-jpetstore

https://github.com/making/spring-jpetstore

 H2

Spring, Spring MVC, MyBatis 3, and Spring Security

vaadin-jpetstore

https://github.com/igor-baiborodine/jpetstore-6-vaadin-spring-boot

 HSQLDB

Vaadin and Spring Boot with JavaConfig

gradle-jpetstore

https://github.com/hanmomhanda/Spring4-jpetstore6-MyBatis3

 HSQLDB

Spring4, MyBatis3, gradle

tomee-jpetstore

https://github.com/AndyGee/jpetstore-6-tomee

 HSQLDB

jpetstore + tomee


표에 있는 것을 모두 상세하게 정리할 수는 없고

몇 가지 기능과 구조를 중심으로 정리했다.

먼저, 기본적인 설명은 http://www.mybatis.org/jpetstore-6/ko/에 잘 정리되어 있다.

Spring과 Mybatis에 대한 기본적인 설명은 다 있는 것 같다.

기본적인 사이트 구성을 알고 싶은 경우

데모 사이트에서 확인할 수 있다.


먼저, 기본인 jpetstore(표의 약어)는

소형 DBMS인 HSQLDB를 대상으로

국내에서는 잘 사용하지 않는 Stripes를 이용한 예제이다.

Stripes를 사용했기 때문에 일반적인 Spring 코드와 많은 차이가 있다.


spring-jpetstore는 Spring MVC를 기반으로 하고,

Spring Security를 사용하여 jpetstore의 보안 문제를 해결하였다.


vaadin-jpetstore은 기본 기능에 vaadin을 적용한 것으로

데모 사이트가 운영되어 있어 설치 없이 사용해 볼 수 있다.

개인적으로 Java로 웹 페이지를 컨트롤 한다는 개념이 욕심이라는 생각에

사용해 보지 않았지만,

자바스크립트 프론트엔드 애플리케이션을 자바로 만들고 싶다면 확인해 보는 것도 좋을 것 같다.


gradle-jpetstore과 tomee-jpetstore는 기본 jpetstore에

gradle과 tomee를 적용한 것으로

소스에 큰 차이가 없다.

gradle-jpetstore는 gradle를 설치하고 실행하면 문제가 없고,

tomee-jpetstore도 일종의 WAS인 tomee만 설치하면 될 것 같은데

일이 커지는 것 같아서 테스트 해보지 않았다.


HSQLDB는 Java 기반의 소형 RDBMS이고,

H2는 HSQLDB의 후속 버전이다.

위 예제들은 내장되어 작동하기 때문에

별도의 설치나 접속 정보 설정없이 웹 실행과 동시에 사용할 수 있다.


이상의 5가지 외에도 다양한 버전이 있을 수 있다.

(개인적으로 몇 시간 검색해서 찾은 결과일 뿐이다)

이러한 유사한 기능을 제공하는 소스 코드를 살펴보고

실행해 보는 것도 공부가 되는 것 같아서 정리해 봤다.


나아가 자신의 버전을 만든다면 실력 향상에 도움이 될 것 같다.

JQuery나 Bootstrap 등으로 개발해 보거나

제품 등록 기능, 판매 현황 등의 기능이 없는데

기능을 보강해 보는 것도 재미 있을 것 같다.


'Java > JPetStore' 카테고리의 다른 글

2. JPetStore 설치  (2) 2016.11.06
3. JPetStore - 쇼핑몰 구조  (0) 2016.11.06
4. JPetStore - 코드 둘러보기  (1) 2016.11.06

앞서 정리한 내용 중

기본 jpetstore와 spring-jpetstore, gradle-jpetstore를 설치해 본다.


MyBatis에서 제공하는 JPetStore의 기본 예제를 설치한다.

설치법이 https://github.com/mybatis/jpetstore-6에 제공되지만

이 설치법은 git 명령어를 이용하여 설치하는 것으로 다소 어렵게 느껴진다.

개인적으로 Eclipse가 더 사용하기 쉬워

Eclipse Neon에 JDK 1.8로 설치했다.

Eclipse Neon부터는 Maven이 포함되어 있지만

다른 버전은 Maven을 설치해야 한다.

이하의 내용은 일반적 github import와 거의 유사하다.

다만, 일반 프로젝트로 받아서 Maven Project로 변환(6, 8번)하는 차이가 있어

변환 부분만 참고하고 import 해도 된다.


1. Eclipse에서 파일 메뉴나 Project Explorer에서 마우스 오른쪽 버튼을 눌러 import를 실행한다.

- Git의 [Projects from Git]을 선택하고 다음을 진행한다.

2. [Clone URI]을 선택한다.

3. JpetStore의 기본 프로젝트 주소를 입력한다.

- https://github.com/mybatis/jpetstore-6.git

4. 그냥 다음을 진행한다.

5. [Browse]버튼을 눌러 현재 Eclipse의 workspace를 지정한다.

- 여기서는 D 드라이브에 jpetstore 폴더를 지정했다.

6. [import as general project]를 선택한다.

- JPetStore가 일반 프로젝트로 등록되어 있기 때문이다.

7. [Finish]버튼을 눌러 종료 한다.

8. GitHub에서 소스를 다운 받고 나면, jpetstore-6 프로젝트가 생성되지만

   src와 travis폴더만 생성된 일반 프로젝트이다.

   따라서, 다음 그림과 같이 프로젝트를 선택하고 마우스 오른쪽 버튼을 눌러

   Configure > Convert to Maven Project를 실행한다.

9. 그림과 같이 Maven 웹 프로젝트로 변환된다.

- bin 폴더는 필요없는 폴더라 삭제해도 된다.

10. 프로젝트를 선택하고, 마우스 오른쪽 버튼을 눌러 Properties창을 실행한다.

- 오른쪽 트리에서 [Java Build Path]을 선택하고, Libraries 탭을 선택한다.

- JRE System Libaray [JavaSE-16]을 선택하고 [Edit]버튼을 선택한다.

11. Alternate JRE를 선택해서 PC에 설치된 Java를 지정한다.

- 여기서는 JDK 1.8

12. Java Compiler > Compiler Compiance level에 PC에 설치된 Java를 지정한다.

13. Project Facets > Java에 PC에 설치된 Java를 지정한다.

14. 프로젝트를 선택하고, 마우스 오른쪽 버튼을 눌러 Run As의 [Maven clean]을 실행한다.

- 다시 프로젝트에서 [Maven Install]을 실행한다.

- PC 성능에 따라 다르지만 제법 많은 시간이 소요 될 수 있다.

- 오류가 발생할 수 있는데, 오류 메시지를 확인하고 해당 파일을 찾아서 지운 후

  다시 Maven Clean / Install을 실행하면 된다.

다음 그림과 같이 Maven Install 후 [BUILD SUCCESS]가 되어야 한다.

그렇지 않은 경우 오류 메시지를 확인해서 처리해야 하다.

오류 중 tools.jar 가 없다는 오류가 발생할 경우에는 여기를 참조하면 된다.

그 외에 특정 jar파일이 없다고 하는 경우에는

Maven의 레파지토리(.m2)에 가서 해당 jar가 있는 폴더를 삭제하고

clean / install을 진행하면 된다.


15. Tomcat 서버에 프로젝트를 추가하고 실행한다.

16. 웹 브라우저에서 [http://localhost:8080/jpetstore/] 입력해서 접속한다.


spring-jpetstore는 GitHub 주소만 바꿔서 위와 같은 방식으로 실행하면 된다.

gradle-jpetstore는 Maven 프로젝트로 제공되기 때문에

Maven Project로 변환(6, 8번)하는 과정을 진행할 필요가 없다.

그림과 같이 Eclipse프로젝트로 다운 받으면 된다.

Gradle는 당연이 설치되어 있어야 한다.


'Java > JPetStore' 카테고리의 다른 글

1. JPetStore  (2) 2016.11.06
3. JPetStore - 쇼핑몰 구조  (0) 2016.11.06
4. JPetStore - 코드 둘러보기  (1) 2016.11.06

개발에 있어서 가장 중요한 문서는

프로세스 명세서, 화면 설계서, 데이터 베이스 구조(ERD)라고 생각한다.

최소한 이 3가지는 있어야 개발 할 수 있다고 생각하고

이 3가지를 정리하면 웬만한 프로그램도 이해할 수 있다고 생각한다.

화면 설계서는 웹에서 실행시켜보는 것으로 대체하고

나머지 2가지에 대해서 정리했다.


JPetStore는 다음 그림에서 보는 것 같이

일반적인 쇼핑몰과 동일한 기능을 제공하고 있다.


먼저, 쇼핑몰에서 동물에 대한 분류(개, 고양이 등)를 선택하고,

세부 설정에 따라 원하는 동물을 찾아들어 간다.

(검색을 이용 할 수도 있다.)

맘에 드는 동물에 대한 정보를 본 후 ,

카트에 담기(Add to Cart)를 눌러 장바구니(cart)에 담는다.

(화면과 다음 흐름을 맞춰서 보면 이해 하기 쉽다.)


장바구니에서 주문을 하면(Proceed to Checkout)

로그인 창이 나타난다.

로그인 정보는 ID와 비밀번호를 j2ee와 j2ee로 동일하게 입력하면 된다.

(또는 ACID / ACID)

로그인을 하고 결제(주문) 정보를 입력하면

완료 메시지와 구매 리스트를 볼 수 있는 구조로 개발 되었다.

이 흐름은 다음 그림과 같은 URL로 되어 있다.

위 그림과 같이 클릭해 보면

웹 브라우저의 주소 입력창에 나타나는

URL (파마메터 포함)이 다음 그림과 같은 것을 알수 있다.

이 구조로 소스를 찾아보면

개발 흐름을 쉽게 파악할 수 있을 것이다.


다음으로 사용된 데이터 베이스 구조를 살펴 보면(ERD)

다음 그림과 같다.

resource/database/jpetstore-hsqldb-schema.sql파일의 내용을

AmaterasERD로 정리했다.

왼쪽에는 동물 분류와 세부 정보, 오른쪽에는 구매자 정보, 중앙에 주문 정보를 배치했다.

위 그림은 논리모드(Logical Mode),

아래 그림은 물리 모드(Physical Mode)를 갈무리 한 것이다.

주문 테이블(order)에서 ship(ship addr, shipcity...)은

직역으로 선적자로 적었지만 의미상 수신자,

bill(billaddr, billcidy...는 계산자로 번역했지만

주문자로 이해하는 것이 맞다.

전체적인 테이블 구조는 일반적이라

관계(Relation) 중심으로 기억해 두면 될 것 같다.

jpetstore.erd

 

다만, 로그인 관리(signon) 테이블을

계정(account)테이블과 별도로 구성하고

사용자 아이디를

로그인 테이블에서는 username,

계정 테이블에서는 userid로 다르게 명명하고

길이(length)도 25와 80으로 다르게 부여한 것은 이상하다.

같게 기능을 하고 같은 값을 사용하는 것을

왜 다르게 구현했는지...







'Java > JPetStore' 카테고리의 다른 글

1. JPetStore  (2) 2016.11.06
2. JPetStore 설치  (2) 2016.11.06
4. JPetStore - 코드 둘러보기  (1) 2016.11.06

먼저, 기본적인 설명은 http://www.mybatis.org/jpetstore-6/ko/에 잘 정리되어 있다.

여기서는 jpetstore와 spring-jpetstore의 차이를

두 가지로 정리하면서 코드를 둘러봤다.


먼저, JPetStore 예제들은 기본적으로

다음 그림과 같이 MVC(Model–View–Controller)를 지키고 있다.


사용자에 의해 Action(Control)이 호출되고,

데이터 가공이 필요하면 서비스를 통해 DBMS를 제어하고

적절한 가공을 해서 JSP(HTML)로 변환해서

클라이언트(웹브라우저)에게 전송하는 구조를 가지고 있다.


이상의 그림은 MVC에 맞춰서 그렸다기 보다는

jpetstore의 구성 클래스가 존재하는 디렉토리 명 중심으로 작성하였다.


예로, 위 그림에 맞추어

jpetstore의 동물에 대한 분류 화면을 대상으로 함수들을 정리했다.

분류의 첫 화면(http://localhost:8080/jpetstore/actions/Catalog.action)은

HTML로 구성되어 서비스와 매퍼가 사용되지 않아서 넘어가고

상세 분류(Catalog.action?viewCategory=&categoryId=FISH)화면을 대상으로 한다.


Stripes로 인해 URL이 다소 특이하지만

Stripes에 대해서는 찾아보길 바라고 여기서는 간단하게 정리한다.

Spring MVC에서는 viewCategory.action으로 호출하게 되는데

viewCategory 컨트롤이 작성된 클래스의 파라메터처럼 호출한다.

쇼핑몰 구조에서 정리한 동물분류와 관련된 모든 기능은

CatalogActionBean.java에 각각의 함수로 구현되고,

상세 분류(viewCategory)처럼 파라메터로 호출된다.

위 그림에 맞추어 실제 코드를 확인해 보길 바라고,

사용자가 동물 분류중 하나를 선택하면

CatalogActionBean.java에 있는 viewCategory Action이 실행된다.

viewCategory Action에서는 파라메터로

사용자가 선택한 동물 분류(categoryId)가 넘어 오고,

이 분류를 파라메터로 분류별 동물 리스트(getProductListByCategory)를

데이터 베이스에서 찾아온다(CatalogService).

(코드에서는 제품-product-로 사용했는데 동물을 제품으로 부르기...)


spring-jpetstore도 위 그림에 맞추어

다음과 같이 정리했다.

spring-jpetstore는 Spring MVC에 맞추어 개발되어

Action이 아닌 Controller란 말을 사용한다.

viewCategory에 흔히 보는 @RequestMapping도 사용되었다.

웹사이트에서 접속된 URL을 확인하면

다음과 같이 직접 호출된다.

http://localhost:8080/spring-jpetstore/catalog/viewCategory?categoryId=FISH


jpetstore와 spring-jpetstore의 두번째 차이는 로그인 처리이다.


jpetstore에서 원하는 동물을 카트에 넣고

결제를 하려고 하면 다음과 같은 로그인 창이 나타난다.

로그인을 하고 결제를 한뒤,

페이지 상단에 있는 Sign Out 버튼을 이용하여 로그아웃한다.

즉, 로그인 되지 않은 상태에서

자신의 정보를 수정하거나(http://localhost:8080/jpetstore/actions/Account.action?editAccountForm=)

주문 내역을(http://localhost:8080/jpetstore/actions/Order.action?listOrders=)

직접 접속할 경우 빈 값이나 오류 페이지를 보게 된다.

jpetstore는로그인 체크를 하지 않은 것이다.

이처럼 사용자가 로그인을 하지 않은 경우 로그인 페이지로 이동하도록 해야 한다.

다음 코드와 같이 결제를 하려고 할때(newOrderForm)에만

로그인 체크를 하고 있다.

  public Resolution newOrderForm() {
    HttpSession session = context.getRequest().getSession();
    AccountActionBean accountBean = (AccountActionBean) session.getAttribute("/actions/Account.action");
    CartActionBean cartBean = (CartActionBean) session.getAttribute("/actions/Cart.action");

    clear();
    if (accountBean == null || !accountBean.isAuthenticated()) {
      setMessage("You must sign on before attempting to check out.  Please sign on and try checking out again.");
      return new ForwardResolution(AccountActionBean.class);

    } else if (cartBean != null) {
      order.initOrder(accountBean.getAccount(), cartBean.getCart());
      return new ForwardResolution(NEW_ORDER);
    } else {
      setMessage("An order could not be created because a cart could not be found.");
      return new ForwardResolution(ERROR);
    }
  }


반면, spring-jpetstore는 다음 코드에 나타난 것과 같이

로그인 체크를 하고 있지 않다.

    @RequestMapping("newOrderForm")
    public String newOrderForm(OrderForm orderForm, Model model) {
        UserDetails userDetails = (UserDetails) SecurityContextHolder
                .getContext().getAuthentication().getPrincipal();
        Account account = userDetails.getAccount();

        Order order = new Order();
        order.initOrder(account, cart);
        beanMapper.map(order, orderForm);
        model.addAttribute(order);

        return "order/NewOrderForm";
    }

spring-jpetstore는 Spring Security를 적용하여

spring-security.xml에 로그인이 필요한 페이지를 지정하고 있다.

    <sec:http auto-config="true" use-expressions="true">
        <sec:form-login login-page="/account/signonForm"
            login-processing-url="/account/signon"
            authentication-failure-url="/account/signonForm?error=true" />
        <sec:logout delete-cookies="JSESSIONID" logout-url="/account/signoff"
            logout-success-url="/" />
        <sec:intercept-url pattern="/account/editAccount*"
            access="isAuthenticated()" />
        <sec:intercept-url pattern="/order/**" access="isAuthenticated()" />
    </sec:http>

따라서 계정정보 수정(editAccount)이나 주문(order) 등의 페이지를 실행할 때,

로그인을 하지 않은 경우 로그인 창이 실행된다.


이외에 jpetstore에는 JUnit을 이용하여

데이터를 가지고 오는 서비스와 관련된 부분에

단위 테스트가 적용되어 있다.

그림과 같이 테스트할 파일을 선택하고

Run As > JUnit Test로 간단하게 실행해서

정상 실행 되는지 확인해 보자 (그림 중앙 하단).

'Java > JPetStore' 카테고리의 다른 글

1. JPetStore  (2) 2016.11.06
2. JPetStore 설치  (2) 2016.11.06
3. JPetStore - 쇼핑몰 구조  (0) 2016.11.06

+ Recent posts