ol3-map_modal-sample
지도서비스

Modal Dialog에서의 ol3-map 처리

입력필드에 좌표값을 지도를 이용해 받고 싶을 경우 Modal Dialog를 사용할 수 있다.
* Modal Dialog는 페이지를 이동하지 않고 특정 동작을 수행한후 닫을 수 있다

이 경우 OL3 지도는 size가 [0,0]인 display:none 상태가 된다.
그러므로 Modal Dialog가 show 상태가 될 때 OL3 지도를 updateSize()  해주어야 한다.

예제에 필요한 Modal Dialog를 핸들링 하는 방법은 Modals in Bootstrap 3 문서를 참조한다.
* Modal Dialog의 기본 설정과 위치, 사이즈 조정 등을 설명

<샘플> ol3-map_modal.html

ol3-map_modal-sample

예제의 작동내용은 다음과 같다.

  • ‘좌표검색’ 버튼을 누르면, Modal Dialog 를 띄운다
  • Modal Dialog가 오픈되는 ‘shown.bs.modal’ 이벤트에서 map.updateSize()를 수행
  • 초기 위치에 대해 ol.layer.Vector로 마커(point)를 띄우고 마우스로 움질일 수 있게 핸들링
  • ‘좌표입력’ 버튼을 누르면 마커의 위치에 대해 Daum Map API로 주소검색을 수행해
    좌표입력 text-input 박스에 좌표값을 넣는다. (EPSG:3857 –> WGS84 좌표변환 필요)
  • ‘주소로 찾기’ text-input 박스에 주소 또는 키워드를 넣으면 Daum Map API로 해당 주소 또는 장소에 대한 좌표를 받아 마커와 지도의 중심을 변경
  • 최초 좌표입력 text-input 박스에 좌표값이 있는 경우 마커를 위치시키고 Modal Dialog를 연다

코드 주요 부분에 대해 설명한다.

코드1) Modal의 open시에 map.updateSize() 하기


// 모달 다이알로그가 열리면 지도 크기 재조정
$("#myModal").on('shown.bs.modal', function(e){
e.preventDefault();
//console.log("Map Modal is shown!");

map.updateSize();
});

 

코드2) Daum Map API: 좌표를 주소로 출력


// 지도에 지도 중심좌표에 대한 주소정보를 표출하는 함수
function displayCenterInfo( coorWGS84 ) {
var latlon = new daum.maps.LatLng( coorWGS84[1], coorWGS84[0] );
// 좌표로 법정동 상세 주소 정보를 요청합니다
geocoder.coord2detailaddr( latlon, function(status, result){
if (status === daum.maps.services.Status.OK) {
var detailAddr = result[0].roadAddress.name ? result[0].roadAddress.name : result[0].jibunAddress.name;
document.getElementById('locateAddrText').value = detailAddr;
}
});
}

코드3) Daum Map API: 주소 또는 키워드에 대한 좌표를 얻어 마커와 지도를 이동


// 지도와 마커 이동
function moveCenter( coorWGS84, point ){
// 제주도 안에 있는지 체크, 아니면 false 리턴
var square = ol.geom.Polygon.fromExtent( boundWGS84 );
if( square.intersectsCoordinate( coorWGS84 ) ){
var coor = ol.proj.transform( coorWGS84, 'EPSG:4326', srsName);
// 지도와 마크 이동
viewMain.setCenter( coor );
if( typeof point !== 'undefined' && point.getGeometry().getType() === 'Point' ){
point.getGeometry().setCoordinates( coor );
}
return true;
}
return false;
}

// 주소로 지도&마크 이동
function moveCenterByAddr( addr ) {
var lonlat, rtn;
// 주소로 좌표를 검색합니다
geocoder.addr2coord( addr, function(status, result) {
if (status === daum.maps.services.Status.OK) {
lonlat = [ parseFloat(result.addr[0].lng), parseFloat(result.addr[0].lat) ];
console.log( addr + ' ==> [' + lonlat.join(', ') + ']' );
rtn = moveCenter( lonlat, pointFeature );
}
// 좌표 실패시 키워드로 장소를 검색합니다.
if( !rtn ){
geoplaces.keywordSearch( '제주특별자치도 '+addr, function(status, data, pagination){
if (status === daum.maps.services.Status.OK) {
lonlat = [ parseFloat(data.places[0].longitude), parseFloat(data.places[0].latitude) ];
rtn = moveCenter( lonlat, pointFeature );
if( !rtn && data.places.length > 1 ){
// 제주도가 아닐 경우, 두번째 시도
lonlat = [ parseFloat(data.places[1].longitude), parseFloat(data.places[1].latitude) ];
moveCenter( lonlat, pointFeature );
}
console.log( addr + ' ==> [' + lonlat.join(', ') + ']' );
}
});
}
});
}

마지막으로 중요한 TIP!!

Daum Map API에서 services 라이브러리들을 사용하기 위해서는 API-Key 뒤에
‘&libraries=services’를 반드시 붙여야 한다. 아니면 정의 안된 객체 사용으로 오류가 발생한다.

 

__ 끝 __

olle-road-map-cource01_postgres
개발일반지도서비스

GeoServer에 PostGIS 연결하기

Geoserver에 DB를 연결시키면 대용량의 공간데이터를 저장 관리하는데 많은 이점이 있다.
공간데이터를 관리하는 대표적인 데이터베이스로는 Oracle, PostGresql 등이 있고, 최근에 MySQL 확장버전과 H2 확장버전이 있다. Oracle은 오래된 상용DB로서 ArcGrid 등의 상용솔루션과 궁합을 맞춰왔다. 이 외에 공개소프트웨어의 무료버전으로 사용할 수 있는 도구로는 PostGresql 기반에 PostGIS가 있다.

PostGresql에 대한 소개는 한눈에 살펴보는 PostgreSQL을 살펴보시고

PostGIS는 확장기능으로 PostGresql 설치 위치에 추가로 설치된다.

설치방법은 두가지이다.
1) PostGresql 설치 후 이어지는 ‘Stack Builder’ 마법사로 PostGIS 확장판을 추가 설치
2) PostGresql 설치 후 PostGIS install 페이지에서 별도로 다운받아 설치

설치된 후 PostGresql의 GUI 관리자인 ‘pgAdmin III’를 실행시킨다.
* 위치는 윈도우 메뉴의 PostGresql 아래에 있다.
postgresql_win_menu

pgAdmin 관리자에서는 DB 객체들을 관리한다. (생성/삭제/수정)

postgresql_gui_client

쿼리편집기는 메뉴 Tool 아래에 ‘Query tool’이란 항목을 클릭하면 된다.

postgresql_sql_client

테스트를 위해 올레1코스에 대한 Point를 입력해 보겠다.
* PostGIS 쿼리 작성은 loading_geometry_data를 참조


create table roads(
road_name varchar,
road_desc varchar
);
select AddGeometryColumn('roads','road_geom',4326,'POINT',2);

-- truncate table roads;
-- select * from roads;

insert into roads values(
'1코스 시작점-시흥초등학교',
'0km (시작점 스탬프박스)',
ST_GeomFromText('POINT(126.8958600238 33.4772999771)', 4326)
);
insert into roads values(
'1코스 종점-광치기해변',
'15.1km (종점스탬프박스) [구급함]',
ST_GeomFromText('POINT(126.924261013 33.4520049859)', 4326)
);
insert into roads values(
'1코스 중간지점-목화휴게소',
'8.1km (중간스탬프박스)',
ST_GeomFromText('POINT(126.9017019589 33.4818899911)', 4326)
);
insert into roads values(
'4.3묘지석',
'14.3km',
ST_GeomFromText('POINT(126.9287689682 33.4578439873)', 4326)
);
insert into roads values(
'공중화장실',
'13.4km',
ST_GeomFromText('POINT(126.9355240278 33.4616310149)', 4326)
);
insert into roads values(
'간세',
'2.2km (남은거리 13.5km)',
ST_GeomFromText('POINT(126.8872760329 33.4778120276)', 4326)
);
insert into roads values(
'간세',
'4.4km (남은거리 10.5km)',
ST_GeomFromText('POINT(126.8816409633 33.4880019911)', 4326)
);

이것을 Geoserver의 Data Importer로 가져와 Layer로 발행을 하면 다음과 같이 표시된다.
* Data Importer는 Geoserver의 확장기능으로 추가설치를 해야한다.

olle-road-map-cource01_postgres
* 빨간점을 클릭하면 아래에 featureInfo가 표시된다.

Layer 발행을 위한 작업순서는 다음과 같다.

1) 작업공간 ‘postgis’ 생성
2) 저장소 ‘postgis_sample’ 생성
3) 레이어 메뉴에서 새로운 리소스 생성을 하여 ‘postgis_sample’ 저장소를 설정하면
4) geometry 데이터가 있는 테이블을 보여준다. 그중 해당 테이블을 선택해 ‘발행’하면 된다.

레이어는 Geometry 타입에 따라 분리되어 생성하도록 되어 있다.
* Point는 Point대로, LineString은 LineString 대로 따로따로..

olle-road-map-cource01_vworld
Javascript

Mobile 체크하여 OL3 선택도구 변경하기

OL3(OpenLayers 3)에서 ol.interaction.Select는 강력한 도구다.
별다른 코드를 넣지 않고도 선택시 스타일 변화와 함께 select 이벤트를 제공한다.

OL3 선택도구에는 아래의 종류가 있다.

  • Mouse – Pointer Hover
  • Mouse – Click
  • Mouse – Drag Box
  • Mouse – Draw Circle or Polygon

사용상의 편의로 PC/Desktop 환경에서는 ‘Pointer Hover’를, Mobile 환경에서는 ‘Click’을 선택도구로 주로 사용한다.

우선 Javascript에서 Mobile 환경 체크 방법은 이렇다.
** 코드 참고:
Detecting a mobile browser
모바일 기기용 개발


var isMobile = {
    Android: function() {
        return navigator.userAgent.match(/Android/i);
    },
    BlackBerry: function() {
        return navigator.userAgent.match(/BlackBerry/i);
    },
    iOS: function() {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    },
    Opera: function() {
        return navigator.userAgent.match(/Opera Mini/i);
    },
    Windows: function() {
        return navigator.userAgent.match(/IEMobile/i) || navigator.userAgent.match(/WPDesktop/i);
    },
    any: function() {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};

이를 이용해 선택자(ol.interaction.Select)를 정의한다.
** 옵션 condition에 대한 설명은 OL3 API 가이드를 참고


var selectPointer;
//Mobile 환경에서는 선택자를 'Click'으로
if( isMobile.any() ){
    alert('Mobile');
    selectPointer = new ol.interaction.Select({
        condition: ol.events.condition.singleClick
    });
}
//PC 환경에서는 선택자를 'Hover'로
else{
    selectPointer = new ol.interaction.Select({
        condition: ol.events.condition.pointerMove
    });
}
mapMain.addInteraction(selectPointer);

// HTML의 info 영역에 관련 정보를 표시한다
var infoSelect = document.getElementById('info');
selectPointer.on('select', function( e ){

    infoSelect.innerHTML = '&nbsp;';

    var selectedFeatures = e.selected;  //   e.target.getFeatures();
    if( selectedFeatures.length === 0 ){
        overlayHint.setPosition( undefined );   // hide hint
        return;
    }

    var feature = selectedFeatures[0];
    var dispText = feature.get('name');
    if( typeof dispText !== 'undefined' ){
        var lonlat = feature.getGeometry().getCoordinates();
        var element = overlayHint.getElement();

        element.setAttribute('data-hint', dispText);
        overlayHint.setPosition( lonlat );      // show hint
        infoSelect.innerHTML = dispText;
    }

});

샘플페이지(바탕지도:VWorld-BaseMap)

모바일 환경에서 접속할 경우 “Mobile”이라는 alter가 뜨고
선택자 click으로 마커의 태그를 확인할 수 있게된다.
데스크탑 환경에서는 alter 없이 마우스의 Pointer Hover만으로 태그 확인이 가능하다.

olle-road-map-cource01_daum
Javascript

OpenLayers 3에서 Daum Map 사용하기

지도 개발시 Google 지도도 만들고 Daum 지도도 만드는 등 다양한 요구가 있을 경우, OL3 하나만 개발하면 Overlay 시켜 개발공수를 절약할 수 있다는 장점이 있다. 유지보수도 편해진다.

Daum Map을 개발하는 것도 동일하다.
앞에 OpenLayers 3에서 Google Map 사용하기’ 글에서 소개한 바와 같은 방법으로 작업하면 된다.

    작업 요령
  • HTML 상에 지도가 올라갈 div 객체 두개를 겹쳐 쌓는다
  • OL3를 바탕지도 없이 생성
  • Daum Map을 콘트롤 없이 생성
  • OL3 지도의 이벤트를 Daum Map의 setCenter와 setLevel에 연결시킨다

주요 코드는 다음과 같다.


<div id="printableArea" style="width: 100%; height: 90%; margin: 0; padding:0; position:relative;">
  
    <div id="map-daum" style="position: absolute; left:0px; top:0px; z-index: 1; width: 100%; height: 100%; margin: 0; padding:0;"></div>
    <div id="map-main" style="position: absolute; left:0px; top:0px; z-index: 2; width: 100%; height: 100%; margin: 0; padding:0;"></div>
	  
</div>


// 거의 모든 controller들을 비활성화 시킨다
var mapDaum = new daum.maps.Map(document.getElementById('map-daum'), {
    center: new daum.maps.LatLng( centerWGS84[1], centerWGS84[0] ),
    level: 20 - zoomDefault,
    draggable: false,
    scrollwheel: false,
    disableDoubleClick: true,
    disableDoubleClickZoom: true,
    tileAnimation: false,
    speed: 0	// '지도 이동 속도'라는데 뭔지 모르겠다
});

// OL3에서는 'EPSG:3857'을 사용하므로 좌표 변환이 필요함
viewMain.on('change:center', function() {
    var center = ol.proj.transform( viewMain.getCenter(), srsName, 'EPSG:4326');
    mapDaum.setCenter(new daum.maps.LatLng( center[1], center[0] ));
});

// 다음 지도는 Zoom 규격이 특이하다. 20으로부터 빼야 OL3와 맞음
viewMain.on('change:resolution', function() {
    mapDaum.setLevel( 20 - viewMain.getZoom() );
});

샘플페이지를 작성해 보았다. ==> Daum Map

  • 캡쳐화면
    olle-road-map-cource01_daum
Javascript

[Javascript] jsTree 사용법 및 Tree 자료구조

jsTree는 jquery 기반으로, HTML 또는 JSON 데이터를 Tree 콘트롤로 웹에 출력해 주는 자바스트립트 라이브러리이다.
홈페이지는 이곳 ==> https://www.jstree.com/

사용한 예는 다음과 같다.
캡처-jsTree-실행예

먼저 jsTree 라이브러리를 불러온다.


	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/themes/default/style.min.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/jstree.min.js"></script>

<!-- (...중략...) -->
<div id="layerTree"></div>

이후 HTML 또는 JSON 데이터를 생성해 jstree()를 실행시킨다.

// jsTree 생성
$('#layerTree').jstree({
	"plugins" : [ "wholerow", "checkbox", "changed" ],
	'core' : { 'data' : treeJson }
});

데이터 정의는 HTML과 JSON 두가지가 있는데, 위 코드는 JSON 방식이다.
HTML의 경우, ‘ul’과 ‘li’ 태그를 이용하여 계층을 표현한다.

예제를 따라서, 구현을 들어갔는데 HTML 출력이 원하는대로 되지 않아서, JSON 방식으로 구현했다.
오히려 더 깔끔하고, Tree 자료구조 그대로 삽입할 수 있어서 더 나은 선택이 되었다.

** JSON 자료구조
참고: https://www.jstree.com/docs/json/


// jsTree의 Node 자료구조
{
  id          : "string" // will be autogenerated if omitted
  text        : "string" // node text
  icon        : "string" // string for custom
  state       : {
    opened    : boolean  // is the node open
    disabled  : boolean  // is the node disabled
    selected  : boolean  // is the node selected
  },
  children    : []  // array of strings or objects
  li_attr     : {}  // attributes for the generated LI node
  a_attr      : {}  // attributes for the generated A node
}

// jsTree 사용예
$('#using_json').jstree({
  'core' : {
    'data' : [
       'Simple root node',           // 단독으로 text만 넣거나
       {                             // 자료구조를 지켜서 넣거나
         'text' : 'Root node 2',
         'state' : {
           'opened' : true,
           'selected' : true
         },
         'children' : [              // 자식노드들은 Array에 넣어야 한다
           { 'text' : 'Child 1' },
           'Child 2'
         ]
      }
    ]
} });

이를 위해 사용자가 정의한 데이터로부터 jsTree와 호환되는 Tree 자료구조를 구현해야 하는데
다음과 같이 코드를 작성하였다.


/**
 ** ****************************************************
 ** 레이어 리스트 Tree 생성
 ** ****************************************************
 */

var treeCtrl = treeCtrl || (function(){

	var Node = function( nodeName ){
		return {
			text: nodeName,
			state: { 'opened' : false, 'selected' : false },
			data: null,			// @type: ol.Layer
			children: []
		};
	};

	var _root = new Node('ROOT','group');

	var findChild = function( parent, nodeName ){

		for( var i=0; i<parent.children.length; i++ ){
			var child = parent.children[i];
			if( child.text == nodeName ){
				return child;
			}
		}

		return null;
	};

	// find parent which save item
	var getParent = function( parent, group, opt_make ){

		opt_make = ( typeof opt_make === 'undefined' ) ? true : false;

		for( var i=0; i<group.length; i++ ){
			var child = findChild( parent, group[i] );
			// 없으면 Node 생성
			if( !child ){
				if( !opt_make ) return null;

				child = new Node( group[i] );
				parent.children.push( child );
			}
			parent = child;
		}

		return parent;
	}

	// group에 layer 아이템 저장
	var _set = function( key, item ){
		var group = key.split('-');
		var parent = getParent( _root, group );

		var child = new Node( item.name, 'item' );
		child.data = item;

		parent.children.push( child );

		return child;
	};

	// item인 node들만 반환 (=layer)
	var _get = function( key ){
		var group = key.split('-');
		var parent = getParent( _root, group );

		return parent.children.filter(function( node ){
			return node.type == 'item'
		});
	};

	var _clear = function(){
		_root.children = [];
	};

	var _setState = function( key, state ){
		var group = key.split('-');
		var node = getParent( _root, group, false );
		if( !node ) return false;

		node['state'] = state;
		return true;
	};

	var _toTreeJson = function(){
		return _root.children;
	};

	return {
		'root'  	: _root,
		'set'   	: _set,
		'get'   	: _get,
		'setState'  : _setState,
		'toTreeJson': _toTreeJson,
		'clear' 	: _clear,
	};

})(treeCtrl);

// wfsData 읽어들이기
for( var key in wfsData ){
	var node = treeCtrl.set( wfsData[key].group, wfsData[key] );
}
// 법정경계 펼치고, 선택하기
treeCtrl.setState( '주제도-법정경계', { 'opened' : true, 'selected' : true } );
var treeJson = treeCtrl.toTreeJson();
//console.log( treeJson );

Node의 트리 위치는 set() 함수의 ‘key’로 정의되는데, ‘-‘ 분리자로 계층을 표현하였다.
예를 들어, ‘주제도-법정경계’라면 ‘주제도’ 아래에 ‘법정경계’ 노드를 가리킨다.

이에 대한 트리 생성과 이벤트 처리는 다음과 같이 한다.

** 생성 옵션: “wholerow”, “checkbox”, “changed”
** 이벤트 : ‘changed’에 대해 ‘selected’와 ‘deselected’를 처리


// jsTree 생성
$('#layerTree').jstree({
	"plugins" : [ "wholerow", "checkbox", "changed" ],
	'core' : { 'data' : treeJson }
});

// jsTree 클릭 이벤트 : 레이어 setVisible
$('#layerTree').on("changed.jstree", function (e, data) {
	// 선택된 레이어 보이기
	var i, j, r = [];
	for(i = 0, j = data.changed.selected.length; i < j; i++) {
		var node = data.instance.get_node( data.changed.selected[i] );
		if( node.data !== null && node.data.hasOwnProperty('layer') ){
			wfsLayers[ node.data.layer ].setVisible( true );
			r.push( node.data.layer );
		}
	}
	console.log( 'selected Layer: ' + r.join(', ') );

	// 선택해제된 레이어 감추기
	r = [];
	for(i = 0, j = data.changed.deselected.length; i < j; i++) {
		var node = data.instance.get_node( data.changed.deselected[i] );
		if( node.data !== null && node.data.hasOwnProperty('layer') ){
			wfsLayers[ node.data.layer ].setVisible( false );
			r.push( node.data.layer );
		}
	}
	console.log( 'deselected Layer: ' + r.join(', ') );
});

jsTree 선택에 의한 조작 대상은 OpenLayer3의 layer의 setVisible() 이다.

더 자세한 설정은 jsTree 홈페이지를 참조하시길..
구글링으로도 예제코드들이 잘 나온다. (마음에 드는게 안나와서 기록하는 거지만)

Javascript

[펌] ‘this’를 위한 문맥 지정에 call 사용

** 참고문서
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call

익명 함수 호출에 call 사용

순수하게 생성된 이 예에서, 익명 함수를 만들고 배열 내 모든 객체에서 이를 호출하기 위해 call을 사용합니다. 여기서 익명 함수의 주목적은 모든 객체에 print 함수를 추가하는 겁니다, 배열 내 객체의 정확한 인덱스를 출력할 수 있는 this 값으로 객체 전달이 반드시 필요하지는 않았지만 설명 목적으로 했습니다.

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}
스프링4-마이바티스-예제08_케이스7
Spring MVC

스프링4 HaspMap으로 DB select 하기

몇달전 스프링을 접한 후로 DB 연결 설정과 JSON 출력 방법을 드디어 알아내었다.
책대로 따라도 해보고 검색으로 이것저것 공부했지만 이해하기 어려웠는데
간절하면 이루어진다는 말처럼 결국은 반복 학습이 해결을 해 주었다.
(스프링 책 앞부분만 5~6번은 반복해 따라해 본 듯 하다)

참고서적: 코드로 배우는 스프링 웹 프로젝트구멍가게 코딩단

스프링4에서 DB를 연결해 HashMap과 JSON으로 출력하기 위해서는
아래 순서의 작업 과정이 필요하다.

  1. 모듈 선언
    – pom.xml 작성
    – maven을 통해 jdbc와 mybatis, dbms 관련 모듈의 dependency 등록
  2. MyBatis 설정
    – src/main/webapp/WEB-INF/spring/root-context.xml 작성
    – db 접속정보와 MyBatis의 sqlSessionFactory, sqlSession 설정
  3. (optional) 추가 설정파일 생성
    – mybatis-config.xml: SQL 등록시 resultType의 namespace 참고용
    – log4jdbc.log4j2.properties 와 logback.xml: DB 로깅용 Log4j2 용 설정
  4. mapper SQL 작성
    – root-context의 ‘mapperLocations’에 정의한 위치에 *_SQL.xml 생성
    – DAO에서 호출할 namespace를 설정하고 select, insert/update/delete 등을 포함
    select 구문에 resultType=”java.util.HashMap” 사용
  5. DAO 작성 (interface & implement)
    return type으로 List< HashMap > 사용
    Java 기본객체를 사용할 경우 VO 클래스가 필요없어진다
    @Repository 선언하고 @Inject로 MyBatis의 SqlSession을 Inject한다
    – MyBatis의 SqlSession으로 mapper SQL의 namespace + ID를 호출한다
  6. Service 작성 (interface & implement)
    @Service 선언하고 @Inject로 dao를 Inject한다
  7. Controller 작성 (RestController)
    @RestController를 선언 (클래스 전체)
    – RequestMapping 개별로 REST 출력을 사용하려면 함수 출력부에 ‘@ResponseBody’를 선언

개발환경으로는

  • sts-3.8.0
  • java 1.7
  • springframework 4.1.7.RELEASE

스프링4-마이바티스-예제02

우선 DB 연결없이 JSON 출력을 위한 콘트롤러단의 샘플을 먼저 살펴보자.

CASE1. VO 데이터를 JSP의 View로 넘기는 일반적인 방법

// 외부에서 /C.do 을 호출하면 연결됨
@RequestMapping("C.do")
public String doC(Model model){
    logger.info( "doC called......................." );

    // model은 view 단으로 데이터를 넘기기 위한 Map형 자료구조이다
    String msg = "9999년99월99일";
    model.addAttribute( "serverTime", msg );

    ProductVO product = new ProductVO( "Sample Product C", 19999 );
    model.addAttribute( "product", product );

    // views 아래의 'home.jsp'를 호출
    return "home";
}

View: home.jsp

<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<!doctype html>
<html lang="ko">
<head>
  <title>Home</title>
</head>
<body>
  <h1>Hello world!</h1>
  <P>The time on the server is <c:out value="${serverTime}"/>.</P>
  <P><c:out value="${product.name}"/> / <c:out value="${product.price}"/> </P>
</body>
</html>

스프링4-마이바티스-예제08_케이스1

CASE2. VO 데이터를 JSON으로 바로 출력하는 방법

// Annotation @ResponseBody 를 선언하면 클래스를 JSON으로 변환해 출력한다
@RequestMapping("D.do")
public @ResponseBody ProductVO doD(){
    logger.info( "doD called......................." );

    ProductVO product = new ProductVO( "Sample Product D", 59999 );

    return product;
}

스프링4-마이바티스-예제08_케이스2

CASE3. Map 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("E.do")
public @ResponseBody Map<String, Object> doE(){
    logger.info( "doE called......................." );

    // Map 데이터타입으로 출력 : 생성은 HashMap으로 하고
    Map<String, Object> product = new HashMap<String, Object>();
    product.put("name", new String("Map DATA2") );
    product.put("price", new Double(39999) );
    product.put("memo", new String("Rock, Pop") );

    return product;
}

스프링4-마이바티스-예제08_케이스3

CASE4. VO를 포함한 List 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("sendList")
public @ResponseBody List<SampleVO> sendList(){
    List<SampleVO> list = new ArrayList();
    for( int i=0; i<10; i++ ){
        SampleVO vo = new SampleVO();
        vo.setFirstName("길동"); vo.setLastName("홍"); vo.setMno( i );
        list.add( vo );
    }
    return list;
}

스프링4-마이바티스-예제08_케이스4

CASE5. VO를 포함한 Map 데이터를 직접 작성해 JSON으로 출력하는 방법

@RequestMapping("sendMap")
public @ResponseBody Map<Integer, SampleVO> sendMap(){

    Map<Integer, SampleVO> map = new HashMap<>();
    for( int i=0; i<10; i++ ){
        SampleVO vo = new SampleVO();
        vo.setFirstName("길동");	vo.setLastName("홍"); vo.setMno( i );
        map.put( i, vo );
    }
		
    return map;
}

스프링4-마이바티스-예제08_케이스5

CASE6. resultType으로 VO를 사용해 JSON으로 출력하는 일반적인 방법

@RequestMapping(value="/all/{bno}", method=RequestMethod.GET)
public ResponseEntity<List<ReplyVO>> list(@PathVariable("bno") Integer bno){
		
	ResponseEntity<List<ReplyVO>> entity = null;
	try{
		entity = new ResponseEntity<>(service.listReply(bno), HttpStatus.OK);
	} catch(Exception e){
		e.printStackTrace();
		entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
	}
		
	return entity;
}

스프링4-마이바티스-예제08_케이스6

CASE7. resultType으로 HashMap을 사용해 JSON으로 출력하는 간편한 방법

@RequestMapping(value="/listAny/{bno}", method=RequestMethod.GET)
public ResponseEntity<List<HashMap<String, String>>> listAny(@PathVariable("bno") Integer bno){
		
	ResponseEntity<List<HashMap<String, String>>> entity = null;
	try{
		entity = new ResponseEntity<>(service.listAny(bno), HttpStatus.OK);
	} catch(Exception e){
		e.printStackTrace();
		entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
	}
		
	return entity;
}

스프링4-마이바티스-예제08_케이스7


MyBatis 설정과 resultType=”HashMap” 사용하기

스프링4-마이바티스-예제01

1. pom.xml 설정

	<!-- for MySQL -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.36</version>
	</dependency>

	<!-- for MyBatis -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.2.8</version>
	</dependency>
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis-spring</artifactId>
		<version>1.2.2</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${org.springframework-version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>${org.springframework-version}</version>
	</dependency>
		
	<!-- for JSON -->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.5.4</version>
	</dependency>

	<!-- Test & logging -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.bgee.log4jdbc-log4j2</groupId>
		<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
		<version>1.16</version>
	</dependency>

2. src/main/webapp/WEB-INF/spring/root-context.xml 설정

먼저 관련 모듈별 namespace 를 체크해 포함시킨다. 이후 source 탭에서 내용 수정

스프링4-마이바티스-예제09_rootContext

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<!--
		/* log4jdbc 모듈 안쓸 때, 순수하게 JDBC 연결 설정만 하는 경우 */	
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://tonynedb.cnklczmjogww.ap-northeast-2.rds.amazonaws.com:3306/spring_exdb"></property> 
	-->
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="url" value="jdbc:log4jdbc:mysql://tonynedb.cnklczmjogww.ap-northeast-2.rds.amazonaws.com:3306/spring_exdb"></property>
		<property name="username" value="tonyne"></property>
		<property name="password" value="tonyne0909"></property>
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<property name="configLocation" value="classpath:/config/mybatis-config.xml"></property>
		<property name="mapperLocations" value="classpath:/mapper/*_SQL.xml"></property>
	</bean>

	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
	</bean>
	
	<context:component-scan base-package="com.tonyne.ex01.dao"></context:component-scan>
	<context:component-scan base-package="com.tonyne.ex01.service"></context:component-scan>

3. (optional) 기타 설정파일 : 로깅, MyBatis의 참조용 namespace 설정

3-1. src/main/resources/log4jdbc.log4j2.properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
3-2. src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml" />
	
	<!-- log4jdbc-log4j2 -->
	<logger name="jdbc.sqlonly"			level="DEBUG" />
	<logger name="jdbc.sqltiming"			level="INFO" />
	<logger name="jdbc.audit"			level="WARN" />
	<logger name="jdbc.resultset"			level="ERROR" />
	<logger name="jdbc.resultsettable"		level="ERROR" />
	<logger name="jdbc.connection"			level="INFO" />
</configuration>
3-2. src/main/resources/config/mybatis-config.xml
  • root-context.xml의 “configLocation” 항목으로 정의되어 있음
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
    	<typeAliases>
    		<!-- <typeAlias type="com.tonyne.ex01.domain.ReplyVO" alias="ReplyVO" /> -->
    		<package name="com.tonyne.ex01.domain" />
    	</typeAliases>
    </configuration>
    

4. mapper *_SQL.xml 작성

  • root-context.xml의 “mapperLocations” 항목으로 정의되어 있음
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.tonyne.ex01.mapper.ReplyMapper">
    	<!--
    	mybatis-config.xml : 긴 namespace를 typeAlias로 짧게 대체할 수 있음 
    	<select id="list" resultType="com.tonyne.ex01.domain.ReplyVO"> 
    	-->
    	<select id="listAny" resultType="java.util.HashMap">
    	select * from tbl_reply where bno = #{bno} order by rno desc
    	</select>
    
    	<select id="list" parameterType="Integer" resultType="ReplyVO">
    	select * from tbl_reply where bno = #{bno} order by rno desc
    	</select>
    </mapper>
    

5. DAO 작성

  • HashMap 사용때는 ReplyVO 클래스 기술이 필요없음
5-1. ReplyDAO.java
package com.tonyne.ex01.dao;

import ...;

public interface ReplyDAO {
	public List<HashMap<String, String>> listAny(Integer bno) throws Exception;
	public List<ReplyVO> list(Integer bno) throws Exception;
}

 

5-2. ReplyDAOImpl.java
package com.tonyne.ex01.dao;

import ...;

@Repository
public class ReplyDAOImpl implements ReplyDAO {
    @Inject
    private SqlSession session;

    private static String namespace = "com.tonyne.ex01.mapper.ReplyMapper";

    @Override
    public List<ReplyVO> list(Integer bno) throws Exception {
        return session.selectList(namespace+".list", bno);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<HashMap<String, String>> listAny(Integer bno) throws Exception {
        return session.selectList(namespace+".listAny", bno);
    }
}

6. Service 작성

6-1. ReplyService.java
package com.tonyne.ex01.service;

import ...;

public interface ReplyService {
	public List<HashMap<String, String>> listAny(Integer bno) throws Exception;
	public List<ReplyVO> listReply(Integer bno) throws Exception;
}

6-2. ReplyServiceImpl.java

package com.tonyne.ex01.service;

import ...;

@Service
public class ReplyServiceImpl implements ReplyService {
    @Inject
    private ReplyDAO dao;

    @Override
    public List<ReplyVO> listReply(Integer bno) throws Exception {
        return dao.list(bno);
    }

    @Override
    public List<HashMap<String, String>> listAny(Integer bno) throws Exception {
        return dao.listAny(bno);
    }
}

7. Controller 작성

package com.tonyne.ex01.web;

import ...;

@RestController
@RequestMapping("replies")
public class ReplyController {

    @Inject
    private ReplyService service;

	@RequestMapping(value="/all/{bno}", method=RequestMethod.GET)
	public ResponseEntity<List<ReplyVO>> list(@PathVariable("bno") Integer bno){
		
		ResponseEntity<List<ReplyVO>> entity = null;
		try{
			entity = new ResponseEntity<>(service.listReply(bno), HttpStatus.OK);
		} catch(Exception e){
			e.printStackTrace();
			entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
		}
		
		return entity;
	}

	@RequestMapping(value="/listAny/{bno}", method=RequestMethod.GET)
	public ResponseEntity<List<HashMap<String, String>>> listAny(@PathVariable("bno") Integer bno){
		
		ResponseEntity<List<HashMap<String, String>>> entity = null;
		try{
			entity = new ResponseEntity<>(service.listAny(bno), HttpStatus.OK);
		} catch(Exception e){
			e.printStackTrace();
			entity = new ResponseEntity<>( HttpStatus.BAD_REQUEST );
		}
		
		return entity;
	}
}

 

웹서버는 sts 개발도구의 기본 TC 웹서버를 이용했다. (Tomcat7 또는 Tomcat8 써도 OK)

Log4jdbc 모듈을 사용하게 되면 DB 관련 모든 로깅을 살펴볼 수 있다.
– MySQL에 접속해 ‘select now()’ 실행하는 테스트 코드의 로그

스프링4-마이바티스-예제05_DB로깅

개인 개발환경에 java를 여러버전 깔았다면, JRE 버전이 제대로 선택 안되었을 경우가 생긴다.
그러면 Java Library의 Build Path를 직접 확인할 것!

스프링4-마이바티스-예제06_자바빌드패스

이클립스(or STS) 개발시 탭보다는 공백이 좋다. (Window > Preference 의 Java 섹션 설정)

스프링4-마이바티스-예제07_탭공백설정

즐코딩~