$("#alertWindow").dialog({
resizable: false, // 사이즈 조절 가능 여부modal: true, // 배경색 어둡게:true, 밝게:falseautoOpen:false,
minWidth:400,
height:"auto",
zIndex:9000,
title : title, // 다이얼로그 제목buttons: [
{
text : message_common.CM0015,
click:function(){
$( this ).dialog( "destroy" );
if( callback ){
// 확인 눌렀을 때 실행할 콜백 함수
callback();
}
}
},
{
text : message_common.CM0028,
click:function(){
$( this ).dialog( "destroy" );
if( callback ){
// 취소 눌렀을 때 실행할 콜백 함수
callback();
}
}
}
],
open:function () {
var html2 = "<div class=\"button_info\">보안메일로 발송하겠습니까?</div>";
$(".ui-dialog .ui-dialog-buttonpane").prepend(html2);
}
});
여기서 append 대신 prepend 를 사용한 이유는 확인/취소 버튼 앞에 메시지를 놓고 싶었기 때문이다.
<divclass="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><!-- ui-dialog-buttonpane 의 첫번째 자식 --><divclass="button_info">보안메일로 발송하겠습니까?</div><!-- ui-dialog-buttonpane 의 두번째 자식 --><divclass="ui-dialog-buttonset"><buttontype="button"class="ui-button ui-corner-all ui-widget">확인</button><buttontype="button"class="ui-button ui-corner-all ui-widget">취소</button></div></div>
myBatis에서 제공하는 자동 매핑으로 해결이 어려운 경우를 위해 구조를 설계할 수 있도록 만들어진 도구이다.
ResultMap이 필요한 경우
ResultMap은 다음과 같은 데이터 구조를 불러올 때 적합하다.
계층형 데이터 구조
객관식 시험과 관련된 정보들을 데이터베이스에 추가한다면 다음과 같은 구조를 가지게 된다.
- 1번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
- 2번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
시험이라는 상위 entity와 보기라는 하위 entity로 나누어 생각해본다면 다음과 같이 이해할 수 있다.
물리 테이블은 다음과 같이 구성될 것이다.
문제 테이블(quiz)
문제번호(no)문제내용(text)
1
1번 문제
2
2번 문제
보기 테이블(choice)
보기번호(no)보기내용(text)문제번호(quiz)
1
1번 보기
1
2
2번 보기
1
3
3번 보기
1
4
4번 보기
1
5
1번 보기
2
6
2번 보기
2
7
3번 보기
2
8
4번 보기
2
위의 테이블 구조를 만약 join으로 불러온다면 어떻게 될까?
select
문제번호, 문제내용, 보기번호, 보기내용
from
문제 inner join 보기 on 문제.문제번호 = 보기.문제번호;
다음과 같은 결과가 나올 것이다. 문제번호문제내용보기번호보기내용
1
1번 문제
1
1번 보기
1
1번 문제
2
2번 보기
1
1번 문제
3
3번 보기
1
1번 문제
4
4번 보기
2
2번 문제
5
1번 보기
2
2번 문제
6
2번 보기
2
2번 문제
7
3번 보기
2
2번 문제
8
4번 보기
위의 결과를 가지고 다음 화면을 출력할 수 있는지 따져봐야 한다. join을 이용해서 위의 화면을 출력하는 것은 가능은 하지만 어렵다. 조건들이 많이 등장할 것이며, 코드는 지저분하게 표시된다. 다음 화면은 만들 수 있겠지만 우리가 원하는 화면은 아니다. 따라서 데이터를 화면과 같은 구조로 불러오고 싶은 경우 join 대신 ResultMap을 이용하면 좋다.
ResultMap을 위한 클래스 구성
- 1번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
- 2번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
위와 같은 구조에서 시험 문제의 입장으로 바라본다면 다음과 같이 구성되어 있다고 생각할 수 있다.
- 1번 시험문제
- List<보기>
- 2번 시험문제
- List<보기>
따라서 시험 문제와 보기의 관점에서 각각 저장할 클래스를 만들어 myBatis가 구조를 이해할 수 있도록 구성한다.
문제 클래스는 Quiz라는 이름으로 작성하였으며, 퀴즈 번호와 내용, 그리고 보기 목록(List<Choice>)을 가질 수 있도록 구성하였다. 이 Quiz라는 형태를 myBatis에 ResultMap으로 등록하여 구문과 같이 연결해두면 계층형 데이터를 쉽게 불러올 수 있다.
ResultMap 설정
ResultMap은 mapper에 설정한다. quiz-mapper.xml이라는 파일을 만들고 내부에 다음과 같이 작성한다.
<!-- 기본 설정 생략 -->
<mapper namespace="quiz">
<resultMap type="com.hakademy.vo.Quiz" id="quiz">
<result column="no" property="no"/>
<result column="text" property="text"/>
<collection column="no" property="choices" javaType="java.util.List" ofType="com.hakademy.vo.Choice" select="getChoiceList">
</collection>
</resultMap>
<select id="getQuizList" resultMap="quiz">
select * from quiz order by no asc
</select>
<select id="getChoiceList" parameterType="int" resultType="com.hakademy.vo.Choice">
select * from choice where quiz = #{quiz}
</select>
</mapper>
해당 구문을 호출할 때에는 다음과 같이 작성한다.
List<Quiz> list = sqlSession.selectList("quiz.getQuizList");
Controller 메소드에 @ModelAttribute UserForm userForm 을 파라미터로 선언하고, jsp 단에 <form:spring modelAttribute="userForm"/> 스프링 폼태그를 사용해 본적이 있는가?
이때 컨트롤러단으로 요청이 들어오면 jsp에서 입력한 값이 modelAttribute 로 지정된 객체의 필드값에 매핑 저장되어 파라미터로 넘어오는 것을 볼 수 있다. 바로 여기서 들어온 요청에 대해 modelAttribute 로 선언된 객체의 필드값이 어떻게 매핑되는지 그 과정을 생각해 본 적이 있는 분!
.
.
.
.
.
메소드에 @ModelAttribute 를 선언했을 때 처리되는 과정은 다음과 같다.
1. 파라미터 타입의 객체를 하나 생성한다.
2. HTTP 요청에서 가져온 객체의 프로퍼티에 바인딩(값을 매긴다) 해준다. 이 과정에서 각 프로퍼티에 맞게 타입을 변환해준다. 만약 타입 변환 오류가 생기면 BindingResult 객체에 error 를 저장하여 컨트롤러로 넘겨준다.
(아~~ 너무 추상적이다..)
파라미터로 넘어온 친구들에 대해 프로퍼티 바인딩을 해준다. 즉, 각 프로퍼티에 맞게 타입을 변환해준다는 거군.
(그럼 '프로퍼티에 바인딩'한다는 건 머선 말이지..)
.
.
.
.
.
프로퍼티에 바인딩한다? - 2가지 방법
프로퍼티 바인딩이란 오브젝트의 프로퍼티(필드값)에 값을 넣는 행위를 말하는 것이다.
각각의 필드에 맞게 타입을 적절히 변환하고 프로퍼티의 커스텀메소드를 호출하는 것이다.
스프링에선 크게 두가지 프로퍼티 바인딩을 지원한다.
1. applicationContext.xml 의 설정파일로 Bean 을 정의할 때 사용한 <property> 태그이다.
PropertyEditor 는 스프링 API가 아니라 자바빈 표준에 정의된 API이다. GUI 환경에서 비주얼 컴포넌트를 만들 때 사용하도록 설계되었고, 기본적인 기능은 문자열과 자바빈 프로퍼티 사이의 타입 변환이다. 스프링은 이 PropertyEditor 를 문자열-오브젝트 상호변환이 필요한 XML 설정이나 HTTP 파라미터 변환에 유용하게 사용할 수 있다고 판단하여 이를 일찍부터 사용해왔다.
핵심은 이 디폴트 PropertyEditor들은 바인딩 과정에서 파라미터 타입에 맞게 자동으로 선정되어 사용된다는 것이다.
디폴트 프로퍼티 데이터에 등록되지 않은 타입을 파라미터로 사용하고 싶다면 직접 PropertyEditor 를 구현하여 등록하고 사용할 수 있다.
WebDataBinder
직접 구현한 에디터를 구현하기 전에 Controller 에서 메소드 바인딩하는 과정을 먼저 살펴보자.
AnnotationMethodHandlerAdapter 는 @RequestParam, @PathVariable, @ModelAttribute 와 같이 HTTP 요청을 변수에 바인딩하는 어노테이션을 만나면 먼저 WebDataBinder 라는 것을 만든다.
WebDataBinder 는 여기서 HTTP 요청 문자열을 파라미터로 변환하는 기능을 한다.
-> WebDataBinder (이 친구) 때문에 바로 이 글을 시작할 때 언급했던 ModelAttribute 를 선언한 메소드의 객체 필드에 매핑이 되는 것이다.
이때, 직접 구현한 PropertyEditor 를 사용하려면 이 WebDataBinder 에 직접 등록해줘야 한다.
근데 WebDataBinder 의 변환 과정이 외부로 노출되지 않으므로, 직접 등록해줄 방법은 없다.
그래서 스프링이 제공하는 WebDataBinder 초기화 메서드를 사용해야 한다.
.
.
.
.
@InitBinder
Controller 클래스에 아래와 같이 @InitBinder 어노테이션이 부여되고, WebDataBinder 를 매개변수로 받는 메소드를 하나 생성해 봅니다.
@ControllerpublicclassController{
// 모든 요청이 들어올때마다 해당 method 를 거친다.// 모든 컨트롤러 내에서 변환 하려면 ConfigurableWebBindingInitializer 를 설정해서 사용해야 한다.// 특정 컨트롤러 내에서만 변환 하려면 컨트롤러에 @InitBinder가 붙은 메서드를 작성하여 사용하면 된다.@InitBinderprivatevoidinitBinder(WebDataBinder binder){
// , 구분자로 배열화하는 것을 방지한다.
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
.
.
@RequestMapping("json/binder.do")public String binder(@RequestParam(value = "param", required = false) String[] param){
//...
}
}
initBinder 메서드는 클래스내의 모든 메서드에 대해서 파라미터를 바인딩하기 전에 자동으로 호출된다. 바인딩 적용 대상은 @RequestParam, @PathVariable, @CookieValue, @RequestHeader, @ModelAttribute의 프로퍼티 이다.
기본적으로 PropertyEditor는 지정한 타입과 일치하면 항상 적용된다. 여기에 프로퍼티 이름을 추가 조건으로 주고, 프로퍼티 이름까지 일치해야만 적용되게 할 수 있다. 이러한 타입의 PropertyEditor는 이미 PropertyEditor가 존재할 경우 사용한다. WebDataBinder는 바인딩 시 커스텀 PropertyEditor가 있을 경우 이를 선적용하고, 없을 경우 디폴트 PropertyEditor를 적용하기 때문이다.
.
.
.
.
.
+) 추가 예시 설명
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
해당 customEditor 를 등록해주지 않은 경우
@RequestParam 에 배열타입으로(String[ ]) 들어오는 파라미터에 쉼표(,) 구분자가 있는 경우 디폴트(PropertyEditor)가 적용되어 무조건 쉼표(,) 구분자(쉼표가 default인 듯)에 의해 배열화 된다.
registerCustomEditor 인자로 String[].class , new StringArrayPropertyEditor(null) 값을 넣으면,
String [] 배열 타입에 대해 어떤 구분자로도 배열화하지 않겠다고 선언하는 것이다. (null 자리의 인자값은 seperator임)
필자가 원하는 값은 파라미터에 쉼표가 존재하더라도 하나의 문자열로 보고 구분하지 않도록 하는 것이었기 때문에 위와 같이 적용해주었다.
[registerCustomEditor 사용전]
@InitBinderprivatevoidinitBinder(WebDataBinder binder){
//binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
@RequestParam(value="to") String[] to
입력 값
"테, 스트"<test01@ttestt.kr>
기대 값
to[0] = ""테, 스트"<test01@ttestt.kr>"
결과
to[0]=""테" to[1]="스트"<test01@ttestt.kr>"
, 쉼표 구분값으로 배열화된다.
[registerCustomEditor 사용후]
@InitBinderprivatevoidinitBinder(WebDataBinder binder){
binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor(null));
}
그러니까 본디 그 뜻을 보아하니.. 뭔가 연결시키고, 연관시키고, 하나로 꽉 묶고, 결합시키고 하는 건가보네?
개발적 언어의 의미
속성과 개체 사이 또는 연산과 기호 사이와 같은 연관이다. ( -_- 무슨 말? ) 즉, 바인딩(binding) 이란 프로그램의 어떤 기본 단위가 가질 수 있는 구성요소의 구체적인 값, 속성을 확정하는 것(줄로 꽉 묶는다는 뜻 연상)을 말한다. ( 오케이, 앞에거 모르겠고. 일단 느낌은 "값을 매긴다, 확정한다" 요거 구먼. )
2. 예를 들어보자면~
프로그램의 기본 단위인 변수를 예로 들면,
int num = 123;
여기서 int 는 변수의 자료형, num 은 변수 이름, 123 은 변수의 자료값이다.
즉, 데이터 타입이 int 라는 것으로 바인딩되고, a 라는 변수명에 바인딩 되고, 1 이라는 값이 바인딩 되는 것이다.
(아하 ~~ 너낌 와쒀. 한마디로 정리해서)
이름, 자료형, 자료값에 각각 num, int, 123 이라는 구체적인 타입, 이름, 값이 정해지고 메모리 할당하는 것 각각의 과정을 바. 인. 딩. 이라고 한다.
3. 두가지 바인딩
조금 더 깊이 들어가자면, 일반적으로 바인딩은 일어나는 시간에 따라 크게 정적 바인딩, 동적 바인딩으로 분류한다.
정적 바인딩(Static Binding)
컴파일 시간에 일어나며, 실행 중 변하지 않고 유지된다.
- 함수의 정적 바인딩은 컴파일 시간에 호출될 해당 함수의 주소가 결정되어 바인딩 된다. 즉, 실행 파일에 호출할 함수가 위치한 메모리 주소가 이미 확정 기록된 것이다.
동적 바인딩(Dynamic Binding)
- 실행시간 (run time) 중에 일어나며, 프로그램 실행 도중에 변경이 가능하다.
- 말그대로, 실행 파일을 만들때 호출할 함수의 메모리 주소가 확정되지 않고, 이후 실제로 실행되는 그 시간에 호출할 함수의 주소가 결정된다.
- 그렇기 때문에 이 주소를 저장할 공간을 미리 확보해둔다.
- 실행될지 안될지 확정되지 않았기에 일단, 해당 함수를 위해 저장공간을 할당해야한다는 점에서 메모리 관리에 비효율적이다.
(쏼라~쏼라~~ 메모리 관리에 효율적이냐 비효율적이냐를 말한것이군.. 아무튼 한줄 요약하자면~~)
실행 이전(컴파일될때)에 값이 확정되면 정적 바인딩 , 이후(진짜 코드가 실행될때)에 값이 확정되면 동적 바인딩인 것이다.
4. 결론
아주 쉽고 간단하게 말해서, 개발에서 말하는 바인딩은 값이 확정되어 최종적으로 값이 매겨지는 것을 말하는 것이다.