본문 바로가기

학원/복기

[MyBatis] 자동매핑&수동매핑 / resultMap

회원 정보를 저장하기 위한 [MYUSER] 테이블을 생성해주자
 

create table myuser(user_id varchar2(50) primary key, user_name varchar2(50));

 
 
SQL 명령은 대소문자를 구분하지 않기 때문에 식별자를 선언할 때 스네이크 표기법을 사용한다.
- 스네이크 표기법(SnakeCase) : 단어와 단어를 구분하기 위해 _ 기호를 사용하여 식별자를 선언하는 방법 
 


DTO 클래스 [MyUser.java] 를 선언해보자.
 
Java 자료형(Class, Interface, Enum)을 선언할 경우에는 파스칼 표기법을 사용한다.
- 파스칼 표기법(PascalCase) : 모든 단어의 첫문자를 대문자로 표현하여 식별자를 선언하는 방법 
 
Java 자료형을 제외한 식별자는 카멜 표기법을 사용한다.
- 카멜 표기법(CamelCase) : 첫단어를 제외한 나머지 단어의 첫문자를 대문자로 표현하여 식별자를 선언하는 방법
 

package xyz.itwill.dto;

/*
MYUSER 테이블 : 회원정보를 저장하기 위한 테이블
 => SQL 명령은 대소문자를 구분하기 않기 때문에 식별자를 선언할 때 스네이크 표기법 사용
 => 스네이크 표기법(SnakeCase) : 단어와 단어를 구분하기 위해 _ 기호를 사용하여 식별자를 선언하는 방법
 
create table myuser(user_id varchar2(50) primary key, user_name varchar2(50));

이름        널?       유형           
--------- -------- ------------ 
USER_ID   NOT NULL VARCHAR2(50) - 아이디 
USER_NAME          VARCHAR2(50) - 이름
*/

//Java 자료형(Class, Interface, Enum)을 선언할 경우에는 파스칼 표기법 사용
// => 파스칼 표기법(PascalCase) : 모든 단어의 첫문자를 대문자로 표현하여 식별자를 선언하는 방법
public class MyUser {
	//Java 자료형을 제외한 식별자는 카멜 표기법 사용
	// => 카멜 표기법(CamelCase) : 첫단어를 제외한 나머지 단어의 첫문을 대문자로 표현하여 식별자를 선언하는 방법
	private String userId;
	private String userName;
	
	public MyUser() {
		// TODO Auto-generated constructor stub
	}

	public MyUser(String userId, String userName) {
		super();
		this.userId = userId;
		this.userName = userName;
	}

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
}

 


 
 
매퍼를 만들어보자. 
package 엘리먼트를 사용했기 때문에 매퍼를 별도로 등록할 필요가 없다.

 

[mybatis-config.xml]

<mappers>
	<package name="xyz.itwill.mapper"/>
</mappers>

 

[MyUserMapper.xml]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill.mapper.MyUserMapper">
	<insert id="insertUser" parameterType="MyUser">
		insert into myuser values(#{userId}, #{userName})
	</insert>
	
	<select id="selectUserList" resultType="MyUser">
		select * from myuser order by user_id
	</select>
</mapper>

 


MyUserMapper 인터페이스 선언

package xyz.itwill.mapper;

import java.util.List;

import xyz.itwill.dto.MyUser;

public interface MyUserMapper {
	int insertUser(MyUser user);
	List<MyUser> selectUserList();
}

 


DAO 선언
 
[MyUserDAO]
 

//SqlSessionFactory 객체를 생성하여 반환하는 메소드
	private SqlSessionFactory getSqlSessionFactory() {
		String resource="mybatis-config.xml";
		
		InputStream inputStream=null;
		try {
			inputStream=Resources.getResourceAsStream(resource);
		} catch (IOException e) {
			throw new IllegalArgumentException(e);
		}
		
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

 
이를 DAO 생성때마다 만들기 귀찮으니까 별도의 클래스로 만들어놓자.
 
[AbstractSession.java]
 
SqlSessionFactory 객체를 생성하여 반환하는 기능을 제공하는 클래스 
→ 매퍼를 사용하는 모든 DAO 클래스가 상속받기 위한 부모클래스
→ 상속을 목적으로 작성된 클래스이므로 추상클래스로 선언하는 것을 권장한다,
 

package xyz.itwill.dao;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

//SqlSessionFactory 객체를 생성하여 반환하는 기능을 제공하는 클래스
public abstract class AbstractSession {
	private static SqlSessionFactory sqlSessionFactory;
	
	static {
		String resource="mybatis-config.xml";
		
		InputStream inputStream=null;
		try {
			inputStream=Resources.getResourceAsStream(resource);
		} catch (IOException e) {
			throw new IllegalArgumentException(e);
		}
		
		sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	protected SqlSessionFactory getSqlSessionFactory() {
		return sqlSessionFactory;
	}
}

 
AbstractSession 클래스를 상속받아 MyUserDAO 를 마저 작성해보자.

package xyz.itwill.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import xyz.itwill.dto.MyUser;
import xyz.itwill.mapper.MyUserMapper;

public class MyUserDAO extends AbstractSession {
	private static MyUserDAO _dao;

	private MyUserDAO() {
		// TODO Auto-generated constructor stub
	}
	
	static {
		_dao=new MyUserDAO();
	}
	
	public static MyUserDAO getDAO() {
		return _dao;
	}
	
	public int insertUser(MyUser user) {
		SqlSession sqlSession=getSqlSessionFactory().openSession(true);
		try {
			return sqlSession.getMapper(MyUserMapper.class).insertUser(user);
		} finally {
			sqlSession.close();
		}
	}
	
	public List<MyUser> selectUserList() {
		SqlSession sqlSession=getSqlSessionFactory().openSession(true);
		try {
			return sqlSession.getMapper(MyUserMapper.class).selectUserList();
		} finally {
			sqlSession.close();
		}
	}
}

 
 


jsp 작성해서 확인해보자
 
 
 


[MyUserMapper.xml]
 
resultType 속성을 사용하면 resultType 속성값으로 설정된 클래스로 객체를 생성하여 검색행의 컬럼값을 같은 이름의 객체 필드값으로 자동 저장하여 제공한다. (자동 매핑)
 
하지만 자동매핑할 때 검색행의 컬럼명과 resultType 속성값으로 설정된 클래스의 필드명이 모두 다른 경우 resultType 속성값으로 설정된 클래스의 객체를 제공하지 않고 NULL을 제공한다는 문제점이 발생한다.
 
예제에서도  MyUser 테이블의 컬럼명과 MyUser 클래스의 필드명이 다르기 때문에 같은 이유로 문제가 생긴다.
 
컬럼명 - user_id, user_name
필드명 - userId, userName
 
 
이를 해결해보자
 
해결방법1) 검색행의 컬럼명을 resultType 속성값으로 설정된 클래스의 필드명과 같도록 검색한다.
→ SELECT 명령에서 Column Alias 기능을 이용하여 검색대상의 별칭을 필드명과 같도록 검색한다.

<select id="selectUserList" resultType="MyUser">
	select user_id as userId, user_name as userName from myuser order by user_id
</select>

 
sql 엘리먼트와 include 엘리먼트를 이용해 SQL 명령을 더 간단하게 작성할 수 있다.
 
sql : SQL 명령을 구성하는 일부분의 문장을 등록하기 위한 엘리먼트 
 - id 속성 : sql 엘리먼트를 구분하는 식별자를 속성값으로 설정한다.
 
include : sql 엘리먼트에 등록된 문장을 제공받아 SQL 명령에 포함하는 엘리먼트
 - refid 속성 : sql 엘리먼트의 식별자(id 속성값)을 속성값으로 설정한다.

<sql id="myUserColumnAlias">
	user_id userId, user_name userName
</sql>
	
<select id="selectUserList" resultType="MyUser">
	select <include refid="myUserColumnAlias"/> from myuser order by user_id
</select>

 
이처럼 SQL을 구성하는 문장이 반복되는 경우, sql 엘리먼트를 이용해 반복되는 문장을 등록하고 include 엘리먼트로 문장을 불러다 포함시켜 사용할 수 있다.
 
 
 
해결방법2) mybatis 환경설정파일(mybatis-config.xml)의 setting 엘리먼트를 사용하여 스네이크 표기법으로 표현된 식별자를 카멜 표기법의 식별자로  자동 변환하는 기능을 사용한다.
 
mapUnderscoreToCamelCase 옵션을 [true]로 설정하면 SELECT 명령 실행시 스네이크 표기법으로 작성된 컬럼명을 자동으로 카멜 표기법의 컬럼명으로 변환하여 검색하는 기능을 제공한다.
 
[mybatis-config.xml]

<settings>
	<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

 
[MyUserMapper.xml]

<select id="selectUserList" resultType="MyUser">
	select user_id, user_name from myuser order by user_id
</select>

userListSelect.jsp 실행

 
 
첫번째 방법은 컬럼이 많을수록 관리하기 어렵다는 단점이 있다.
따라서 자동 매핑기능을 쓰지 않고 수동 매핑을 사용하는 방법도 존재한다.
 
 
해결방법3) resultMap 엘리먼트를 사용하여 검색행의 컬럼값이 객체의 필드에 저장되도록 설정한다. 즉, 수동매핑을 한다.
 
resultMap : 검색행을 Java 객체로 생성하여 제공하기 위한 엘리먼트 - 매핑 정보를 제공하는 엘리먼트 
→ 검색행의 컬럼값이 객체 필드에 저장되도록 처리하기 위해 하위 엘리먼트를 사용한다.
→ 하위 엘리먼트 : constructor, id, result, association, collection, discriminator
 
 - type 속성 : resultMap 엘리먼트로 제공될 객체의 Java 자료형을 속성값으로 설정한다.
   → Java 자료형 대신 typeAlias 엘리먼트로 설정된 별칭을 사용할 수 있다.
 - id 속성 : resultMap 엘리먼트를 구분하기 위한 식별자를 속성값으로 설정한다.
 
 
id : 검색행의 컬럼값을 필드에 저장하기 위한 엘리먼트 - Setter 메소드 자동 호출
PK 제약조건이 설정된 컬럼의 값을 제공받아 필드에 저장하기 위해 사용한다.
 - column 속성 : 검색행의 컬럼명을 속성값으로 설정 
 - property 속성 : 클래스의 필드명을 속성값으로 설정 
 
result : 검색행의 컬럼값을 필드에 저장하기 위한 엘리먼트 - Setter 메소드 자동 호출
→ PK 제약조건이 없는 컬럼의 경우 result 엘리먼트를 사용한다.
 
resultMap 속성 : resultMap 엘리먼트의 식별자를 속성값으로 설정 
→ resultMap 엘리먼트의 매핑정보를 제공받아 검색행을 Java 객체로 생성하여 제공한다. - 수동 매핑 처리
 

<resultMap type="MyUser" id="myUserResultMap">
	<!-- 검색행의 컬럼값을 필드에 저장 -->
	<id column="user_id" property="userId"/>
	<result column="user_name" property="userName"/>
</resultMap>
	
<select id="selectUserList" resultMap="myUserResultMap">
	 select user_id, user_name from myuser order by user_id
</select>

 
복잡한 매핑의 경우 수동매핑이 더 낫다.