교재 실습/자바 웹 개발 워크북

108. SQL 맵퍼 파일 (2)

Jint 2022. 9. 3. 12:09

# <resultMap> 엘리먼트

컬럼에 별명을 붙이는 대신 <resultMap> 태그를 이용하면 컬럼 이름과 셋터 이름의 불일치 문제를 손쉽게 해결할 수 있다. 다음과 같이 <resultMap> 태그에 컬럼과 연결될 셋터 메서드를 정의한다.

<resultMap type="project" id="projectResultMap">
    <id column="PNO" property="no"/>
    <result column="PNAME"    property="title"/>
    <result column="CONTENT"  property="content"/>
    <result column="STA_DATE" property="startDate" javaType="java.sql.Date"/>
    <result column="END_DATE" property="endDate" javaType="java.sql.Date"/>
    <result column="STATE"    property="state"/>
    <result column="CRE_DATE" property="createdDate" javaType="java.sql.Date"/>
    <result column="TAGS"     property="tags"/>
</resultMap>

<resultMap> 태그의 type 값은 컬럼 데이터를 저장할 클래스 이름 또는 클래스의 별명이다. 클래스에 대한 별명은 mybatis 설정 파일에 정의된다. 앞의 소스에서는 type 값을 project라고 하였는데, spms.vo.Project 클래스를 가리키는 별명이다. 아직 작성하지 않았지만, mybatis 설정 파일에 정의할 것이다.

 

# <result> 엘리먼트

<resultMap> 태그의 자식 태그로서 컬럼과 셋터 메서드의 연결을 정의한다. <result> 태그를 살펴보면 column 속성에는 컬럼 이름을 지정하고, property 속성에는 객체의 프로퍼티 이름을 지정한다. 즉 셋터 메서드의 이름에서 set을 빼고, 첫 알파벳은 소문자로 바꾼 이름이다.

다음은 result 태그를 사용하여 PNAME 컬럼 값을 setTitle() 메서드와 연결시킨 예이다.

<result column="PNAME"    property="title"/>

 

- javaType 속성

<result> 태그에서 javaType을 사용하면, 컬럼의 값을 특정 자바 객체로 변환할 수 있다. 다음과 같이 'STA_DATE' 컬럼에 대해 javaType을 java.sql.Date 객체로 설정하면, 컬럼 값을 꺼낼 때 그 객체로 변환된다.

<result column="STA_DATE" property="startDate" javaType="java.sql.Date"/>

'STA_DATE' 컬럼의 javaType을 java.sql.Date로 한 이유는 출력 형식을 yyyy-mm-dd 형태로 만들기 위해서 이다. 만약 javaType 속성을 지정하지 않는다면, 셋터의 매개변수 타입에 맞추어 컬럼 값이 변환된다. 즉 setStartDate() 메서드의 매개변수가 java.util.Date 타입이기 때문에, STA_DATE 컬럼 값은 java.util.Date 객체로 변환된다. 문제는 java.util.Date 타입의 값은 외국 날짜 형식으로 출력된다는 것이다. 그래서 javaType 속성을 java.sql.Date 타입으로 지정한 것이다.

이렇게 컬럼에 대해 호출될 셋터 메서드를 미리 지정해 두면, SQL문을 작성할 때 매우 편리하다. resultType 속성을 사용할 때는 SELECT문의 각 컬럼에 대해 별명을 붙여야 했지만, resultMap을 사용하면 그럴 필요가 없어서 좋다.

 

# <id> 엘리먼트

<id> 태그는 <result> 태그와 작성 방법이 같다. 다만, <id> 태그에서 지정한 프로퍼티는 객체 식별자로 사용된다. SELECT문을 실행하면 레코드 값을 저장하기 위해 결과 객체가 생성되는데, SELECT문을 실행할 때마다 매번 결과 객체를 생성한다면 실행 성능이 나빠질 것이다. 이를 해결하기 위해 SELECT를 통해 생성된 결과 객체들은 별도의 보관소에 저장(캐싱, caching)해두고, 다음 SELECT를 실행할 때 재사용한다. 이 때 보관소에 저장된 객체를 구분하는 값으로 <id> 태그에서 지정한 프로퍼티를 사용한다.

다음은 캐시에 보관된 객체들을 찾을 때 no 프로퍼티를 사용하도록 설정하는 코드이다.

<id column="PNO" property="no"/>

 

2. mybatis의 SELECT 결과 캐싱

SELECT를 실행할 때마다 결과 레코드에 대해 매번 객체를 생성한다면, 속도도 느리고 메모리도 낭비된다. 이를 개선하기 위해 mybatis는 객체 캐싱을 제공한다. 즉 한 번 생성된 객체는 버리지 않고 보관해 두었다가, 다음 SELECT를 실행할 때 재사용하는 것이다.

<resultMap> 태그의 <id> 태그를 사용하여 식별자로 사용할 프로퍼티를 지정하면, 캐싱된 객체를 더욱 빨리 찾을 수 있다. 그림 1은 SELECT 결과를 캐싱하고 재사용하는 것을 보여주고 있다.

첫 번째 질의를 수행할 때 생성된 결과 객체는 풀(pool)에 보관해 둔다. 두 번째 질의에서는 질의 결과에 대해 새로 객체를 생성하기 전에, 객체 풀에 보관된 객체 중에서 PNO 컬럼의 값과 일치하는 객체를 먼저 찾는다. 있다면 기존 객체를 사용하고 없다면 새로 객체를 생성한다. 이것이 mybatis의 객체 캐싱 기법이다(그림 1).

그림 1 (mybatis의 객체 캐싱)

 

# <select> 태그에 resultMap 적용

SELECT 결과에 대해 <resultMap> 태그에 정의된 대로 자바 객체를 생성하고 싶다면, <select> 태그의 resultMap 속성에 <resultMap> 태그 id를 지정한다.

<select id="selectList" resultMap="projectResultMap">
    SELECT PNO, PNAME, STA_DATE, END_DATE, STATE
    FROM PROJECTS
    ORDER BY PNO DESC
</select>

<select id="selectOne" resultMap="projectResultMap" parameterType="int">
    SELECT PNO, PNAME, CONTENT, STA_DATE, END_DATE, STATE, CRE_DATE, TAGS
    FROM PROJECTS
    WHERE PNO = #{value}
</select>

resultMap을 사용하기 때문에 SELECT문의 컬럼에는 더 이상 별명을 붙이지 않는다.

 

3. SQL문의 입력 매개변수 처리

SELECT나 INSERT, UPDATE, DELETE문을 작성하다 보면 외부에서 값을 주입할 수 있도록 입력 매개변수를 지정하는 경우가 있다. 특히 INSERT문의 경우 데이터베이스에 입력할 값을 매개변수로 지정한다. SELECT문에서도 검색 조건에 대해 매개변수로 지정하기도 한다. 입력 매개변수를 사용하는 경우를 살펴본다.

 

- JDBC의 입력 매개변수

다음은 이전에(제 6장 미니 MVC 프레임워크 만들기 > 97. 실력 향상 훈련 (3)) 작성한 MySqlProjectDao 클래스의 일부분이다. INSERT문을 보면 값을 입력하는 자리에 '?' 문자가 있다. JDBC에서는 입력 매개변수를 표시할 때 '?' 문자를 사용한다. 입력 매개변수에 값을 넣을 때는 PreparedStatement 객체의 setXXX() 메서드를 호출한다.

connection = ds.getConnection(); //커넥션 객체 가져오기
stmt = connection.prepareStatement(
    "INSERT INTO PROJECTS (PNAME, CONTENT, STA_DATE, END_DATE, STATE, CRE_DATE, TAGS) "
  + "VALUES (?, ?, ?, ?, 0, NOW(), ?)");
stmt.setString(1, project.getTitle());
stmt.setString(2, project.getContent());
stmt.setDate(3, new java.sql.Date(project.getStartDate().getTime()));
stmt.setDate(4, new java.sql.Date(project.getEndDate().getTime()));
stmt.setString(5, project.getTags());
return stmt.executeUpdate();

 

- mybatis의 입력 매개변수

mybatis에서는 입력 매개변수를 '#{프로퍼티명}'으로 표시한다.

<insert id="insert" parameterType="project">
    INSERT INTO PROJECTS (PNAME, CONTENT, STA_DATE, END_DATE, STATE, CRE_DATE, TAGS)
    VALUES (#{title}, #{content}, #{startDate}, #{endDate}, 0, now(), #{tags})
</insert>

'#{프로퍼티명}'이 가리키는 값은 <insert> 태그의 parameterType에 지정한 객체의 프로퍼티 값(겟터 메서드의 반환값)이다. 즉 #{title} 자리에는 Project 객체의 getTitle() 메서드 반환값이 놓이고, #{content}는 getContent() 메서드, #{startDate}는 getStartDate() 메서드, #{endDate}는 getEndDate() 메서드, #{tags}는 getTags() 메서드의 반환값이 놓인다.

 

- 입력 매개변수에 값 공급

mybatis에서 입력 매개변수에 값을 공급하는 방법은 SqlSession 객체의 메서드를 호출할 때 값 객체를 전달하는 것이다. 다음은 SqlSession 객체의 insert() 메서드를 호출하는 자바 코드이다.

/**
 * 프로젝트 등록
 * @param project
 * @return count
 */
public int insert(Project project) throws Exception  {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //자동 커밋(Auto-commit)을 수행하는 sqlSession 객체 반환
    //SqlSession sqlSession = sqlSessionFactory.openSession(true);
    try {
        //insert() : 두 번째 매개변수로 프로젝트 정보 담은 값 객체 전달
        int count = sqlSession.insert("spms.dao.ProjectDao.insert", project);
        //임시 데이터베이스에 보관된 작업 결과를 운영 데이터베이스에 적용
        sqlSession.commit();
        //임시 데이터베이스에 보관된 작업 결과를 운영 데이터베이스에 적용하지 않고 취소
        //sqlSession.rollback();
        return count;
    } finally {
        sqlSession.close();
    }
}

sqlSession.insert() 메서드를 호출하면 SQL 맵퍼 파일에서 'spms.dao.ProjectDao.insert' 아이디를 가진 SQL문을 찾아 실행한다. 물론 'spms.dao.ProjectDao'는 SQL 맵퍼 파일의 네임스페이스 이름을 가리키고, 'insert'는 SQL 아이디를 가리킨다. project는 INSERT문을 실행할 때 입력 매개변수에 값을 공급할 객체이다.

 

- 값을 공급하는 객체가 기본 타입인 경우

다음과 같이 값을 공급하는 객체가 기본 타입 객체(랩퍼 클래스, wrapper class)인 경우, SQL에서 입력 매개변수 이름은 무엇으로 할까?

/**
 * 프로젝트 삭제
 * @param no
 * @return count
 */
public int delete(int no) throws Exception {  
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        //delete() : 두 번째 매개변수로 int 값 전달(컴파일 시 Integer 객체로 자동 포장(Auto-boxing) : new Integer(no))
        int count = sqlSession.delete("spms.dao.ProjectDao.delete", no);
        //임시 데이터베이스에 보관된 작업 결과를 운영 데이터베이스에 적용
        sqlSession.commit();
        return count;
    } finally {
        sqlSession.close();
    }
}

SqlSession 객체의 delete() 메서드는 두 번째 매개변수에 객체를 요구한다. 자바 컴파일러는 int에 대응하는 랩퍼 객체(Integer)를 생성하여 no 값을 포장(Auto-boxing)한다. 즉 컴파일할 때 다음 코드로 바뀐다.

int count = sqlSession.delete("spms.dao.ProjectDao.delete", new Integer(no));

 

- 입력 매개변수에서 랩퍼 객체 사용

값 객체가 랩퍼 타입일 때 입력 매개변수의 이름은 어떤 이름을 사용해도 무방하다. sqlSession.delete() 메서드의 no 값을 사용하기 위해 예제에서는 'value' 이름을 사용하였지만, #{okok}라고 해도 괜찮다.

<delete id="delete" parameterType="int">
    DELETE FROM PROJECTS
    WHERE PNO = #{value}
</delete>

 

※ 랩퍼 클래스

랩퍼 클래스(Wrapper Class)란, 자바의 기본 데이터형 값을 객체로 다루기 위해 기본으로 제공되는 클래스이다. 다음 표는 자바 기본 데이터형과 랩퍼 클래스를 보여준다.

데이터형 클래스
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
boolean java.lang.Boolean
char java.lang.Character

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr