가희의자기개발블로그

Mapper XML파일 본문

백엔드/myBatis

Mapper XML파일

가희gahui 2020. 7. 4. 12:54
반응형

마이바티스의 가장 큰 장점은 매핑구문이다. SQL Map XML 파일은 상대적으로 간단하다. 더군다나 동일한 기능의  JDBC 코드와 비교하면  95퍼센트 이상 코드수가 감소하기도 한다. 마이바티스는 SQL을 작성하는데 집중하도록 만들어졌다.

 

01_SELECT

SELECT 구문은 마이바티스에서 가장 흔히 사용할 엘리먼트이다. 데이터베이스에서 데이터를 가져온다. 아마도 대부분의 애플리케이션은 데이터를 수정하기보다는 조회하는 기능이 많다. 그래서 마이바티스는 데이터를 조회하고 그 결과를 매핑하는데 집중하고 있다. 조회는 다음 예제처럼 단순한 경우에는 단순하게 설정된다. 

<select id="selectPerson" parameterType="int" resultType="hashmap">
	SELECT * FROM PERSON WHERE ID =#{id}
</select>

이 구문의 이름은 selectPerson이고 int 타입의 파라미터를 가진다. 그리고 결과 데이터는 HashMap에 저장된다. 파라미터 표기법을 보자.

#{id}

이 표기법은 마이바티스에게 PreparedStatement파라미터를 만들도록 지시한다. JDBC를 사용할 때 PreparedStatement에는 "?" 형태로 파라미터가 전달 도니다. 즉 결과적으로 위 설정은 아래와 같이 작동한다.

String selectPerson = "select * from person where id=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

 물론 JDBC 를 사용하면 결과를 가져와서 객체의 인스턴스에 매핑하기 위한 많은 코드가 필요하겠지만, 마이바티스는 그 코드들을 작성하지 않아도 되게 해준다. 

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
속성 설명
id 구문을 찾기 위해 사용 될 수 있는 네임스페이스내 유일한 구분자
parameterType 구문에 전달될 파라미터의 패키지 경로를 포함한 전체 클래스명이나 별칭
resultType 이 구문에 의해 리턴되는 기대타입의 패키지 경로를 포함한 전체 클래스명이나 별칭. collection 인경우 collection 타입 자체가 아닌 collection이 포함된 타입이 될 수 있다. resultType이나 resultMap을 사용
resultMap 외부 resultMap 의 참조명. 결과맵은 마이바티스의 가장 강력한 기능이다. resultType이나 resultMap을 사용
flushCache  
useCache  
timeOut  
fetchSize  
statementType  
resultSetType  
detabaseId  
resultOrdered  

02_INSERT, UPDATE AND DELETE

데이터를 변경하는 구문인 insert update delete는 매우 간단하다.

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

03_sql

이 엘리먼트는 다른 구문에서 재사용 가능한 sql구문을 정의할 때 사용된다. 로딩시점에 정적으로 파라미터처럼 사용 할 수 있다. 다른 프로퍼티값은 포함된 인스턴스에서 달라질 수 있다.

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

 

SQL 조각은 다른 구문에 포함시킬 수 있다.

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

프로퍼티값은 다음처럼 refid속성이나 include절 내부에서 프로퍼티값으로 사용할 수 있다.

 

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

04_Parameters

앞서 본 구문들에서 간단한 파라미터들의 예를 보았을 것이다. Parameters는 마이바티스에서 매우 중요한 엘리먼트이다. 대략 90%정도 간단한 경우 이러한 형태로 설정할 것이다.

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

위 예제는 마우 간단한 명명된 파라미터 매핑을 보여준다. parameterType은 int로 설정되어 있다. Integer과 String과 같은 원시타입이나 간단한 데이터타입은 프로퍼티를 가지지 않는다. 그래서 파라미터 전체가 값을 대체하게 된다. 하지만 복잡한 객체를 전달하고자 한다면 다의의 에제처럼 상황은 조금 다르게 된다. 

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

User 타입의 파라미터 객체가 구문으로 전달되면 id, username, password프로퍼티느 찾아서 PreparedStatement파라미터로 전달된다.

05_문자열 대체(String substitution)

#{} 문법은 마이바티스로 하여금 PreparedStatement프로퍼티를 만들어서 PreparedStatement파라미터에 값을 셋팅하도록 할 것이다. 이 방법이 안전하기는 하지만 빠른 방법이 선호되기도 한다. 가끔은 SQL 구문에 변하지 않는 값으로 삽입하길 원하기도 한다. 예를 등면 ORDER BY와 같은 구문들이다.

ORDER BY ${columnName}

 

여기서 마이바티스는 문자열을 변경하거나 이스케이프 처리하지 않는다. 

 

 사용자로부터 받은 값을 이 방법으로 변경하지 않고 구문에 전달하는 건 안전하지 않다. 이건 잠재적으로 SQL 주입 공격에 노출된다. 그러므로 사용자 입력값에 대해서는 이 방법을 사용하면 안된다. 사용자 입력값에 대해서는 언제나 자체적으로 이스케이프 처리하고 체크해야 한다.

 

06_Result Maps

resultMap엘리먼트는 마이바티스에서 가장 중요하고 강력한 엘리먼트이다. ResultSet에서 데이터를 가져올때 작성되는 JDBC코드를 대부분 줄여주는 역할을 담당한다. 사실 JOIN매핑과 같은 복잡한 코드는 굉장히 많은 코드가 필요하다. ResultMap은 간단한 구문에서는 매핑이 필요하지 않고 복잡한 구문에서 관계를 서술하기 위해 필요하다. 

 

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

모든 칼럼의 값이 결과가 되는 간단한 구문에서는 HashMap에서 키 형태로 자동으로 매핑된다. 하지만 대부분의 경우 HashMap은 매우 좋은 도메인 모델이 되지는 못한다. 그래서 대부분 도메인 모델로는 자바빈이나 POJO를 사용할 것이다. 마이바티스는 둘다 지원한다. 자바빈의 경우를 보자

package com.someapp.model;
public class User {
  private int id;
  private String username;
  private String hashedPassword;

  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getHashedPassword() {
    return hashedPassword;
  }
  public void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }
}

자바빈 스펙에 기반하여 위 클래스는 3개의 프로퍼티(id, username, hashedPassword)를 가진다. 이 프로퍼티는 select 구문에서 칼럼명과 정확히 일치한다. 

그래서 자바빈은 HashMap과 마찬가지로 마우 쉽게 ResultSet에 매핑될 수 있다.

그리고 TypeAliases 가 편리한 기능임을 기억해두는게 좋다. TypeAliases를 사용하면 타이핑 수를 줄일 수 있다. 예를 들면,

<!-- XML설정파일에서 -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL매핑 XML파일에서 -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

이 경우 마이바티스는 칼럼을 자바빈에 이름 기준으로 매핑하여 ResultMap을 자동 생성할 것이다. 만약 칼럼명이 프로퍼티명과 다르다면 SQL구문에 별칭을 지정할 수 있다. 

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

ResultMap에 대한 중요한 내용은 다 보았다. 하지만 다 본건 아니다. 칼럼명과 프로퍼티명이 다른 경우에 대해 데이터베이스 별칭을 사용하는 것과 다른 방법으로 명시적인 resultMap을 선언하는 방법이 있다.

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="username"/>
  <result property="password" column="password"/>
</resultMap>

구문에서는 resultMap 속성에 이를 지정하여 참조한다. 예를들면

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

06_01 복잡한 결과매핑

마이바티스는 한가지 기준으로 만들어졌다. 데이터베이스는 당신이 원하거나 필요로 하는 것이 아니다. 정규화 개념 중 3NF나 BCNF가 완벽히 되도록 하는게 좋지만 실제로는 그렇지 않다. 그래서 하나의 데이터베이스를 모든 애플리케이션에 완벽히 매핑하는 것이 가능하다면 그것이 가장 좋겠지만 그렇지도 않다. 마이바티스가 이 문제를 해결하기 위해 제공하는 답은 결과매핑이다. 

<!-- 매우 복잡한 구문 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>

아마 Author에 의해 작성되고 Comments이나 태그를 가지는 많은 포스트를 가진 Blog를 구성하는 괜찮은 객체 모델에 매핑하고 싶을 것이다. 

<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

 resultMap엘리먼트는 많은 하위 엘리먼트를 가진다. resultMap엘리먼트의 개념적인 뷰이다. 

  • constructor - 인스턴스화되는 클래스의 생성자에 결과를 삽입하기 위해 사용됨
  • idArg - ID인자. ID와 같은 결과는 전반적으로 성능을 향상시킨다.
  • arg - 생성자에 삽입되는 일반적인 결과
  • id - ID결과. ID와 같은 결과는 전반적으로 성능을 향상시킨다.
  • result - 필드나 자바빈 프로퍼티에 삽입되는 일반적인 결과
  • association - 복잡한 타입의 연관관계. 많은 결과는 타입으로 나타난다.
  • collection - 복잡한 타입의 컬렉션
  • discriminator - 사용할 resultMap 을 판단하기 위한 결과값을 사용
반응형

'백엔드 > myBatis' 카테고리의 다른 글

자바 API  (0) 2020.07.06
동적SQL  (0) 2020.07.04
매퍼 설정  (0) 2020.07.01
myBatis 시작하기  (0) 2020.06.30
myBatis 개념  (0) 2020.06.22
Comments