databaseId 속성 : 데이터베이스 밴더에 맞춰 구문을 실행시키고 싶을 때 사용하는 속성 데이터베이스 밴더란 Mysql, Mssql, Oracle 등을 뜻함 거의 사용되지 않는 속성임
lang 속성 : 동적 쿼리를 작성할 때 사용할 언어를 지정하는 속성으로 xml, raw 두 개가 있음 lang 속성의 기본값은 xml임 lang 속성을 raw로 지정하면 MyBatis는 동적 쿼리에 최소한의 처리 ( 파라미터 치환 )만 한 후 SQL을 데이터베이스 드라이버 ( HikariCP, Commons DBCP2 등 ) 에 전달함
태그명
include
설명
sql 태그로 저장해둔 SQL 문장을 불러오는 태그
속성
refid 속성 : 불러올 sql 태그의 id 속성 값
닉네임 또는 연락처로 회원 정보를 조회하는 쿼리를 보자
<?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.study.chapter04.MemberDao">
<select id="getMemberByNickname" resultType="com.study.chapter04.MemberDto">
SELECT id, pw, nickname, tel FROM member WHERE nickname = #{nickname}
</select>
<select id="getMemberByTel" resultType="com.study.chapter04.MemberDto">
SELECT id, pw, nickname, tel FROM member WHERE tel = #{tel}
</select>
</mapper>
여기서 반복적으로 사용된 부분을 보면 다음과 같음
SELECT id, pw, nickname, tel FROM member
이 부분을 sql 태그를 사용해서 저장해 둘 수 있음
그리고 include 태그를 사용해 불러올 수 있음
(1). sql 태그를 사용해 반복적으로 사용된 SQL 문장을 저장함
이때 id 속성을 select_from 으로 했음
(2). 필요한 부분을 include 태그를 사용해 불러오고 있음
sql 태그 안에서 전달 받은 DTO를 활용할 수도 있음
다음 sql 태그를 보자
<?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.study.chapter04.MemberDao">
<sql id="where">
<where>
<if test="nickname != null">
AND nickname = #{nickname}
</if>
<if test="tel != null">
AND tel = #{tel}
</if>
</where>
</sql>
<select id="getMember" resultType="com.study.chapter04.MemberDto" parameterType="com.study.chapter04.MemberDto">
SELECT id, pw, nickname, tel FROM member
<include refid="where" />
</select>
</mapper>
<< 코드 설명 >>
(1), (2). include 태그로 불러온 sql 태그 내 문장은 물리적으로는 떨어져있지만 실제 동작할 때는 하나의 문장으로 동작하기 때문에 아래와 같이 생각하고 활용해야함
마지막으로 property 태그가 남았는데 property 태그는 활용도가 떨어지니 간단하게 태그명, 설명, 속성만 알아보자
나중에 동적 쿼리를 만들어야 하는데 지금 아는 것만으로는 뭔가 부족하다 싶으면 property 태그를 떠올리고 그때 property 태그에 대해서 자세히 찾아봐도 됨
이번에 배울 bind, resultMap 태그는 정적 쿼리든 동적 쿼리든 어디서든 활용할 수 있으므로 사용할 수 있는 상황을 제한하지 말자
태그명
bind
설명
쿼리에서 사용할 변수를 선언하는 태그
속성
name 속성 : 변수의 이름
value 속성 : 변수의 값
SELECT 쿼리를 작성할 때 LIKE 가 필요하면 다음과 같이 LIKE 를 구성함
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname LIKE "'%' + #{nickname} + '%'";
</select>
bind 태그를 사용하면 좀 더 가독성 있는 코드를 만들 수 있음
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
<bind name="pattern" value="'%' + #{nickname} + '%'"/>
SELECT * FROM member WHERE nickname LIKE #{pattern};
</select>
bind 태그를 사용할 때 주의할 점은 select, insert, update, delete 태그 안에 사용해야한다는 점임
만약 여러 태그에서 똑같은 bind 태그를 여러번 사용한다면 아직까지는 그때 마다 bind 태그를 복사 해서 사용해야함
다음에 배울 sql, includes 를 배우면 똑같은 bind 태그를 여러번 사용해야할 때 bind 태그를 재사용하는 방법을 알 수 있을 것
태그명
resultMap
설명
DB의 AS ( Alias ) 와 같은 역할을 하는 태그
또는 SELECT 결과가 계층적 데이터 구조로 되어있는 경우 결과를 저장하기 위해 사용하는 태그
속성
type 속성 : 결괏값을 저장할 DTO의 데이터 타입
id 속성 : 이 구조의 이름
resultMap 태그는 어떻게 활용하느냐에 따라서 활용 방식이 배우 달라지는데 먼저, 가장 간단한 형태로 AS ( Alias ) 와 같은 역할을 하는 것으로 활용해보자
- AS 대신 사용
resultMap 태그 안에는 자식 태그로 id, result, association, collection, discriminator, constructor 태그를 가질 수 있음
AS 대신 resultMap 태그를 사용한다면 id, result 자식 태그만 사용하면 됨
(4). resultMap 태그로 선언한 매칭 정보를 활용하기 위해서 resultMap 속성을 사용함
여기서 주의할 점 ! DTO를 그대로 사용할 때는 resultType 속성을 사용했지만 resultMap 태그를 사용할 때는 resultMap 속성을 사용함
resultMap 태그의 javaType, jdbcType은 생략 가능함
다음은 javaType, jdbcType 을 나열한 테이블임
자바의 데이터 타입
javaType
DB의 데이터 타입
jdbcType
int
int
int
INTEGER
double
double
float(실수)
DOUBLE
char
String
char
String
String
String
String
String
이 표를 보는 방법은 (자바의 데이터 타입, javaType) , (DB의 데이터 타입, jdbcType) 임
자바의 데이터 타입에 맞게 javaType 속성을 사용하면 됨
DB의 데이터 타입에 맞게 jdbcType 속성을 사용하면 됨
javaType, jdbcType 속성을 생략해도 MyBatis가 적절히 매칭시켜줌
그러나 일반적으로 테이블의 칼럼명과 DTO의 멤버 변수명은 맞춰거나 DBMS의 AS 키워드를 사용하므로 resultMap을 AS 대신 사용하는 일은 없을 것
- SELECT 결과가 계층적 데이터 구조로 되어있는 경우 결과를 저장하기 위해 사용하는 태그
계층적 데이터 구조란 한 테이블 안에 상위 데이터와 하위 데이터가 함께 있는 구조를 말함
쇼핑몰의 카테고리가 대표적인 계층적 구조임
쿠팡의 카테고리를 보자
카테고리는 카테고리명, 차수(depth) 로 이뤄져있음
(1)인 패션의류/잡화, 뷰티, 출산/유아동 등에서 패션의류/잡화를 보면 카테고리명은 패선의류/잡화이고 차수는 1차임
뷰티의 카테고리명은 뷰티이고 차수는 역시 1차임
(2)인 여성패션, 남성패션, 남녀 공용 의류, 유아동패션은 1차 카테고리인 패션의류/잡화의 하위 카테고리임
그리고 카테고리이기 때문에 카테고리명과 차수로 이뤄져있음
또한 상위 카테고리가 패션의류/잡화임
따라서 여성패션의 카테고리명은 여성패션이고 차수는 2차, 상위 카테고리는 패션의류/잡화임
남성패션의 카테고리명은 남성패션이고 차수는 2차, 상위 카테고리는 패션의류/잡화임
마지막으로 (3)인 의류, 속옷/잠옷, 신발, 가방/잡화는 2차 카테고리인 여성패션의 하위 카테고리임
그리고 카테고리기 때문에 카테고리명과 차수로 이루어져있음
또한 상위 카테고리가 여성패션임
따라서 의류의 카테고리명은 의류이고 차수는 3차, 상위 카테고리는 여성패션임
속옷/잠옷의 카테고리명은 속옷/잠옷이고 차수는 3차, 상위 카테고리는 여성패션임
카테고리의 구조를 좀 더 시각적으로 나타내면 다음과 같음
이러한 구조의 데이터를 저장할 때는 아래와 같이 한 테이블에 저장할 수 있도록 테이블 구조를 설계함
<< 설명 >>
테이블명은 category 이고 4개의 칼럼을 가지고 있음
idx, name, order 칼럼은 특별한 점이 없지만 parent_idx 칼럼은 특별한 카테고리
parent_idx 카테고리가 참조하는 테이블은 category, 참조하는 칼럼은 idx
이 테이블의 ERD를 그리면 다음과 같음
category테이블에 데이터를 저장하면 다음과 같이 저장됨
1차 카테고리를 찾는 쿼리
-> SELECT * FROM category WHERE parent_idx IS NULL;
2차 카테고리를 찾는 쿼리
-> SELECT * FROM category WHERE parent_idx = 1;
-> SELECT * FROM category WHERE parent_idx = 2;
-> ...
3차 카테고리를 찾는 쿼리
-> SELECT * FROM category WHERE parent_idx = 4;
-> SELECT * FROM category WHERE parent_idx = 5;
-> SELECT * FROM category WHERE parent_idx = 6;
-> ...
1 ~ 3차 카테고리까지 한번에 찾는 쿼리
-> SELECT depth1.name, depth2.name, depth3.name FROM category depth1 LEFT JOIN category depth2 ON depth1.idx = depth2.parent_idx LEFT JOIN category depth3 ON depth2.idx = depth3.parent_idx WHERE depth1.parent_idx IS NULL;
( DBMS와 관련된 카테고리가 아니기 때문에 더 이상 자세한 설명은 생략함 )
1 ~ 3차 카테고리까지 한번에 찾는 쿼리를 사용하면 아래와 같은 데이터가 조회됨
이를 MyBatis, (DTO, DAO), 컨트롤러 로 가져오면 한 행에 하나씩 DTO에 저장됨
그리고 DTO를 사용하기가 어려울 수 있음
DTO를 사용하기 어려운 상황 중 하나를 재현해보자
컨트롤러에 접근했을 때 다음과 같이 각 차수별 카테고리와 그 하위 카테고리를 출력해보자
우선 카테고리 하나의 정보를 담을 수 있는 CategoryDto 를 추가하자
package com.study.chapter04;
import lombok.Data;
@Data
public class CategoryDto {
// 1차 카테고리 정보
private int depth1CategoryIdx;
private String depth1CategoryName;
// 2차 카테고리 정보
private int depth2CategoryIdx;
private String depth2CategoryName;
// 3차 카테고리 정보
private int depth3CategoryIdx;
private String depth3CategoryName;
}
<< 코드 설명 >>
카테고리가 최대 3차 카테고리까지 있으므로 3차 카테고리 정보까지 담을 수 있도록 멤버 변수를 선언해둠
이제 카테고리 정보를 조회해 DTO에 담아줄 쿼리를 만들자
프로젝트 -> src/main/resources -> mapper -> Category.xml 파일을 추가하고 아래 코드를 추가하자
<?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.study.chapter04.CategoryDao">
<select id="selectAllCategory" resultType="com.study.chapter04.CategoryDto">
SELECT
depth1.idx AS depth1CategoryIdx, depth1.name AS depth1CategoryName,
depth2.idx AS depth2CategoryIdx, depth2.name AS depth2CategoryName,
depth3.idx AS depth3CategoryIdx, depth3.name AS depth3CategoryName
FROM category depth1
LEFT JOIN category depth2
ON depth1.idx = depth2.parent_idx
LEFT JOIN category depth3
ON depth2.idx = depth3.parent_idx
WHERE depth1.parent_idx IS NULL;
</select>
</mapper>
-> UPDATE memberSET nickname = #{nickname} WHERE id = #{id}
4. 연락처만 전달 받았을 경우
-> UPDATE memberSET tel = #{tel} WHERE id = #{id}
5. 비밀번호와 닉네임을 전달 받았을 경우
-> UPDATE member SET pw = #{pw}, nickname = #{nickname} WHERE id = #{id}
6. 비밀번호와 연락처를 전달 받았을 경우
-> UPDATE member SET pw = #{pw}, tel = #{tel} WHERE id = #{id}
7. 비밀번호, 닉네임, 연락처 모두 전달 받았을 경우
-> UPDATE member SET pw = #{pw}, nickname = #{nickname}, tel = #{tel} WHERE id = #{id}
8. 닉네임과 연락처를 전달 받았을 경우
-> UPDATE member SET nickname = #{nickname},tel = #{tel} WHERE id = #{id}
여기서 특징적인 점을 보자
set 태그 안에 if의 조건을 만족하면 [ pw = #{pw}, ] 또는 [ nickname = #{nickname} ] 또는 [ tel = #{tel} ] 이 추가되는데 각 상황에서 만들어지는 쿼리를 보면 SET의 마지막에 ( 또는 WHERE 가 시작 되기 전에 ) , 가 없음
<?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.study.chapter04.MemberDao">
// ...
<select id="selectMember" parameterType="com.study.chapter04.SelectMemberDto" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname IN
<foreach collection="selectConditions" item="condition" open="(" close=")" separator=", ">
#{condition.nickname}
</foreach>
;
</select>
// ...
</mapper>
이 쿼리는 DAO가 쿼리쪽으로 데이터들을 전달해준다 라는 전제조건 하에 짜여진 동적 쿼리임
DAO가 쿼리쪽으로 데이터들을 전달해주지 않으면 어떻게 될까?
더 정확하게 이해를 하기 위해 컨트롤러부터 보자
<< 컨트롤러 >>
package com.study.chapter04;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyBatisController {
@Autowired
private MemberDao memberDao;
// ...
@GetMapping("/chapter04/mybatis/member")
public void getMemberByNickname(String nickname) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
// 닉네임으로 회원 정보 조회
List<SelectMemberDto> member = memberDao.selectMember(null);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
// ...
}
id가 joinStudygroupMember 인 insert 태그를 보면 if문이 적용되 동적 쿼리를 만들었음
1. <if test="idx == 0"> ==> 전달 받은 DTO의 idx 멤버 변수 값이 0 이라면 if문 안에 있는 INSERT 쿼리가 실행됨
2. <if test="idx != 0"> ==> 전달 받은 DTO의 idx 멤버 변수 값이 0 이 아니라면 if문 안에 있는 INSERT 쿼리가 실행됨
전달 받은 DTO의 idx 멤버 변수 값이 0 이냐 아니냐에 따라 서로 다른 구조의 INSERT 문이 실행됨
전달 받은 DTO의 idx 멤버 변수 값이 0 이라면 studygroupIdx, memberIdx 칼럼만 활용하는 INSERT 쿼리문이 실행되고 전달 받은 DTO의 idx 멤버 변수 값이 0 이 아니라면 idx, studygroupIdx, memberIdx 칼럼을 활용하는 INSERT 쿼리문이 실행됨
MyBatis는 이런식으로 쿼리문에 자바처럼 조건문, 반복문 등을 활용해 동적 쿼리를 구성할 수 있음
동적 쿼리는 DML문 ( SELECT, INSERT, UPDATE, DELETE ) 에 사용할 수 있음
여기서 동적 쿼리를 구성하는 방법만 잘 익혀두면 DML문 모두 사용하는 방법은 똑같음
동적쿼리에 사용하는 태그는 아래와 같음
태그명
설명
trim
접두사(prefix), 접두어(suffix) 를 붙여주거나 지우는 태그
if
자바의 if와 같은 태그로 조건식이 참(true)인 경우 안에 있는 구문을 추가해주는 태그
choose
자바의 if ~ else if ~ else와 같은 태그로 if ~ else if ~ else 의 영역을 나타내는 태그
when
자바의 if ~ else 와 같은 태그로 choose 안에 사용하며 조건식이 참(true)인 경우 안에 있는 구문을 추가해주는 태그
otherwise
자바의 else와 같은 태그로 choose 안에 사용하며 모든 when의 조건식이 거짓(false)인 경우 otherwise 안에 있는 구문을 추가해주는 태그
foreach
자바의 for 또는 향상된 for문과 같은 태그로 반복적으로 구문을 추가해주는 태그
태그명
trim
설명
접두사(prefix), 접두어(suffix) 를 붙여주거나 지우는 태그
! 주의 ! 일반적으로 trim은 앞, 뒤 공백을 "지워주는" 함수로 많이 사용함 이때문에 MyBatis의 trim도 무언가를 지워준다고 오해할 수 있는데 MyBatis의 trim은 지워주기도 하지만 concat 함수처럼 덧붙여줄 수도 있음
속성
1. prefix : trim 태그 내에 있는 구문의 맨 앞에 덧붙일 문자열
2. suffix : trim 태그 내에 있는 구문의 맨 뒤에 덧붙일 문자열
3. prefixOverrides : trim 태그 내 구문의 맨 앞에 해당 문자열이 있다면 지움
4. suffixOverrides : trim 태그 내 구문의 맨 뒤에 해당 하는 문자열이 있다면 지움
가끔 이런 예시를 들면 "왜 저렇게 사용하나요?" 라는 의문을 갖는 분들이 있는데 저렇게 할 수도 있다는거지 반드시 저렇게 해야한다는게 아님
저렇게 할 수 있다는걸 알고 있으면 나중에 여러분의 상황에 맞게 바꿔서 사용할 수 있을 것
- prefixOverrides, suffixOverrides 속성 사용 예시
<delete id="deleteMember" parameterType="com.study.chapter04.DeleteMemberDto">
DELETE FROM member WHERE id = #{id} AND pw = #{pw}
</delete>
위와 같은 쿼리가 있을 때 prefixOverrides, suffixOverrides 를 사용해 아래와 같이 바꿔도 실행되는 쿼리는 동일함
<delete id="deleteMember" parameterType="com.study.chapter04.DeleteMemberDto">
DELETE FROM member WHERE
<trim prefixOverrides="AND" suffixOverrides="OR">
AND id = #{id} AND pw = #{pw} OR
</trim>
</delete>
prefixOverrides 는 맨 앞에 있는 해당 문자열을 지워주므로 prefixOverrides 속성으로 인해 [ id = #{id} AND pw = #{pw} OR ] 가 됨
suffixOverrides 는 맨 뒤에 있는 해당 문자열을 지워주므로 suffixOverrides 속성으로 인해 [ id = #{id} AND pw = #{pw} ] 가 됨
prefixOverrides, suffixOverrides 속성은 | ( or ) 연산자를 사용할 수 있음
아래와 같이 | 연산자를 사용해서 ( 맨 앞에 있는 AND 또는 OR ) 를 지우고 ( 맨 뒤에 있는 AND 또는 OR ) 를 지울 수 있음
<delete id="deleteMember" parameterType="com.study.chapter04.DeleteMemberDto">
DELETE FROM member WHERE
<trim prefixOverrides="AND | OR" suffixOverrides="AND | OR">
AND id = #{id} AND pw = #{pw} OR
</trim>
</delete>
아직 배우진 않았지만 trim 태그를 잘 사용하면 where, set 태그를 대신할 수 있음
태그명
if
설명
자바의 if와 같은 태그로 조건식이 참(true)인 경우 안에 있는 구문을 추가해주는 태그
사용할 수 있는 연산자는 논리 연산자, 비교 연산자를 사용할 수 있음
논리 연산자의 && 는 and 로 사용하고 || 는 or 로 사용함 같다는 == , 다르다는 != 로 자바의 비교 연산자 문법과 동일함
그외에 문자와 문자열을 비교하는 방법은 MyBatis 만의 별도 문법이 있음
속성
1. test : 조건식이 들어가는 속성으로 이 속성에 들어있는 조건식의 결과가 true일 경우 if문 안에 있는 구문이 추가됨
- if 태그 사용 예시
<select id="selectMember" parameterType="String" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member
<if test="nickname != null">
WHERE nickname = #{nickname}
</if>
;
</select>
<< 코드 설명 >>
if의 test 속성에 사용한 nickname은 파라미터로 전달 받은 DTO가 가지고 있는 nickname 멤버 변수의 값임
지금까지 INSERT, UPDATE, DELETE, SELECT 쿼리를 작성하며 파라미터로 전달 받은 DTO가 가지고 있는 멤버 변수에 접근할 때는 #{멤버변수} 를 사용했지만 test 속성에는 #{멤버변수} 가 아닌 멤버 변수명을 바로 쓴다는 점을 주의하자
- nickname 속성이 없을 경우 : SELECT * FROM member;
- nikcname 속성이 있을 경우 : SELECT * FROM WHERE nickname = "닉네임속성값";
으로 상황에 따라 서로 다른 구조의 쿼리가 만들어지므로 동적 쿼리 라고 함
그외에 몇 가지 예시를 더 보자
<select id="selectMember" parameterType="String" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname}
<if test="start >= 0">
LIMIT #{start}
<if test="end != 0">
, #{end}
</if>
</if>
;
</select>
이 상황에서는 만들어질 수 있는 쿼리의 경우가 3가지임
- start가 0 미만인 경우 : SELECT * FROM member WHERE nickname = #{nickname}
- start가 0 이상이고 end가 0인 경우 : SELECT * FROM member WHERE nickname = #{ncikname} LIMIT #{start}
- start가 0 이상이고 end가 0이 아닌 경우 : SELECT * FROM member WHERE nickname = #{nickname} LIMIT #{start}, #{end}
이번에는 문자열을 비교하는 방법을 알아보자
MyBatis는 문자와 문자열을 구분하지 않고 모두 문자열로 처리함
따라서 문자를 표현할 때는 문자열 ( " ) 로 표현해야함
<select id="selectMember" parameterType="String" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname}
<if test='orderby != null and orderby == "오름차순"'>
ORDER BY idx ASC
</if>
<if test='orderby != null and orderby == "내림차순"'>
ORDER BY idx DESC
</if>
<if test="start >= 0">
LIMIT #{start}
<if test="end != 0">
, #{end}
</if>
</if>
;
</select>
<< 코드 설명 >>
(1). 문자열 값을 표현할 때 " 를 사용해야하므로 test 속성의 값을 ' (홑따옴표) 로 감쌌음
프로젝트의 규모가 커지면 하나의 처리를 하기 위해서 쿼리를 여러번 실행해야하는 경우가 자주 발생함
우리는 지금까지 단편적인 기술만 배웠으므로 그런일이 없었지만 곧 지금까지 배운 것들을 종합적으로 활용해서 하나의 프로젝트를 만들어볼 예정임
이렇게 하나의 처리를 하기 위해서 쿼리를 여러 번 실행해야할 때 INSERT 후 INSERT 한 데이터의 PK 값을 사용해 그 다음 쿼리들이 동작해야하는 경우가 있음
좀 더 구체적인 예를 들어보자
[ 스터디 그룹 모집 사이트 개발 하기 ] 프로젝트를 하는 상황인데 스터디 그룹 모집 글을 작성하고 다른 사용자들이 이 모집 글에 그룹원 신청을 하는 기능이 있다고 상상하자
이를 위해서 DB를 다음과 같이 구성했음
StudygroupInfo 테이블 : 스터디 그룹 모집 글 정보가 저장되는 테이블
StudygroupMember 테이블 : 스터디 그룹원의 정보가 저장되는 테이블
한 회원이 스터디 그룹 모집 글을 작성하면 StudygroupInfo 테이블에 모집 글 정보가 저장됨
모집 글을 작성한 사람은 스터디 그룹원(StudygroupMember)이므로 모집 글을 StudygroupInfo 테이블에 INSERT 후 바로 이어서 INSERT 한 모집 글의 idx를 사용해 StudygroupMember 테이블에 글을 작성한 회원의 정보를 저장해야함
이 처리를 구현한 쿼리, DAO, 컨트롤러를 보자
코드를 입력할 필요는 없고 코드만 보면 됨
<< 쿼리 >>
<?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.study.chapter04.StudygroupDao">
<insert id="saveStudygroupInfo" parameterType="com.study.chapter04.StudygroupInfo">
INSERT INTO studygroupInfo(title, contents, regDate) VALUES(#{title}, #{contents}, CURRENT_TIMESTAMP());
</insert>
<select id="getStudygroupIdx" parameterType="com.study.chapter04.StudygroupInfo" resultType="_int">
SELECT idx FROM studygroupInfo WHERE id = #{id} AND pw = #{pw};
</select>
<insert id="joinStudygroupMember" parameterType="com.study.chapter04.StudygroupMember">
INSERT INTO studygroupMember(studygroupIdx, memberIdx) VALUES(#{studygroupIdx}, #{memberIdx});
</insert>
</mapper>
<< DAO >>
package com.study.chapter04;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StudygroupDao {
void saveStudygroupInfo(StudygroupInfo studygroupInfo);
int getStudygroupIdx(StudygroupInfo studygroupInfo);
void joinStudygroupMember(StudygroupMember studygroupMember);
}
<< 컨트롤러 >>
package com.study.chapter04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import jakarta.servlet.http.HttpSession;
@Controller
public class StudygroupController {
@Autowired
private StudygroupDao studygroupDao;
@PostMapping()
public void saveStudygroupInfo(StudygroupInfo newStudygroupInfo, HttpSession session) {
// 스터디 그룹 모집 글 저장
studygroupDao.saveStudygroupInfo(newStudygroupInfo);
// 로그인한 사용자 번호
int loginUserIdx = (int) session.getAttribute("loginUserIdx");
// 스터디 그룹 번호
int studygroupIdx = studygroupDao.getStudygroupIdx(newStudygroupInfo);
// 모집글을 작성한 사용자는 작성한 모집글의 스터디 그룹원이므로
// 작성한 모집글의 스터디 그룹원으로 등록하기 위한 정보 생성
StudygroupMember studygroupMember = new StudygroupMember();
studygroupMember.setMemberIdx(loginUserIdx);
studygroupMember.setMemberIdx(studygroupIdx);
// 모집글을 작성한 사용자를 작성한 모집글의 스터디 그룹원으로 등록
studygroupDao.joinStudygroupMember(studygroupMember);
}
}
앞서 언급했듯 [ 스터디 그룹 모집글 작성 ] 이라는 하나의 처리를 하기 위해서 INSERT, SELECT, INSERT 쿼리가 순차적으로 동작해야함
이때, INSERT한 스터디 그룹 번호를 찾기 위해 SELECT를 하는데 이런 경우 여러 가지 문제가 생길 수 있는데 대표적으로 우연찮게 똑같은 제목, 내용의 모집 글이 있을 경우 올바른 스터디 그룹 번호를 조회할 수 없음
이렇게 방금 INSERT 한 데이터의 PK 값이 필요할 때 얻는 방법을 알아보자
방금 INSERT 한 데이터의 PK 값이 필요할 때는 insert 태그에 useGeneratedKeys 속성과 keyColumn 속성을 사용하면됨
아래와 같이 useGeneratedKeys 속성 값은 true 로 주고 keyColumn 속성은 PK 칼럼명을 주면 됨
이렇게 insert 태그를 구성하면 INSERT 후에 INSERT 한 데이터의 PK 값을 INSERT 할 때 매개변수로 전달한 DTO에 담아줌
바뀐 컨트롤러, DAO, 쿼리와 흐름을 한번에 보자
특히, 컨트롤러에서 INSERT 한 스터디 그룹 번호를 가져오기 위해 select가 빠졌다는 점에 주목하자
<?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.study.chapter04.MemberDao6">
<select id="getMemberCount" resultType="_int">
SELECT COUNT(*) AS count FROM member;
</select>
</mapper>
<< 코드 설명 >>
(1). 이 쿼리를 실행 하고 나면 결과로 가입된 전체 회원의 수(정수 한 개)가 반환됨 (2). resultType 속성은 이 쿼리를 실행 하고 나면 반환될 값의 타입을 쓰는 것 (1)에서 설명했듯 이 쿼리를 실행 하고 나면 정수가 반환 될 것이기에 정수(int)의 별칭인 _int 로 명시했음 주의 할 점은 resultType="int" 로 쓰면 안됨 int는 Integer의 별칭이고 _int는 int의 별칭임 만약 Integer와 int에 대해서 잘모른다면 자바의 래퍼(Wrapper) 클래스에 대해서 다시 봐야함
이번에는 쿼리와 매칭될 DAO에 메서드를 선언하자
package com.study.chapter04;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberDao6 {
int getMemberCount();
}
<?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.study.chapter04.MemberDao6">
// ...
<select id="getMemberByNickname" parameterType="string" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname};
</select>
// ...
</mapper>
( 쿼리가 점점 길어지므로 지금 상황에 필요한 쿼리만 가져왔음 )
<< 코드 설명 >>
(1). 이 쿼리는 해당 닉네임을 사용하는 회원 정보를 조회하므로 이 쿼리의 결과는 회원 정보 하나(한 행)임 (2). 이 쿼리에 사용할 값이 닉네임(문자열)이므로 parameterType 속성 값은 String의 별칭인 string으로 명시 (3). 이 쿼리의 결과가 회원 정보이므로 회원 정보를 받을 수 있는 DTO를 resultType 속성 값으로 명시
SELECT에서는 쿼리 결과가 정수 하나 라면 resultType 속성 값을 _int 로 명시해야했음
INSERT 쿼리 결과는 정수 1 이라고 했으므로 insert 태그에 resultType 속성 값을 _int 로 명시해야함
그러나 insert 태그에는 resultType 속성을 쓸 수 없음
insert 태그에는 resultType을 쓰지 않아도 insert 쿼리의 결과로 정수 1을 반환함
이번에는 INSERT 쿼리와 매치될 DAO 메서드를 추가하자
package com.study.chapter04;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberDao6 {
// ...
int joinMember(InsertMemberDto newMember);
// ...
}
<< 코드 설명 >>
알고 있듯이 이 메서드를 호출했을 때 실행되는 쿼리가 정수 1을 반환하므로 정수를 받을 수 있게 반환 타입을 int로 지정했음
만약, INSERT 쿼리가 반환하는 정수 1이 필요 없으면 메서드의 반환 타입을 void 로 해도됨
이번에는 컨트롤러를 추가하자
package com.study.chapter04;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController6 {
@Autowired
private MemberDao6 memberDao;
// ...
@PostMapping("/chapter04/mybatis/v6/member")
public void joinMember(InsertMemberDto newMember) {
int result = memberDao.joinMember(newMember);
if(result == 1) {
System.out.println("회원 가입 성공");
} else {
System.out.println("회원 가입 실패");
}
}
// ...
}
( 코드 설명이 필요 없을 정도로 간단하므로 설명은 생략함 )
3. UPDATE 쿼리를 실행했을 때는 UPDATE 쿼리로 인해 값이 바뀐 행의 개수(정수)를 얻을 수 있음
4. DELETE 쿼리를 실행했을 때는 DELETE 쿼리로 인해 삭제된 행의 개수(정수)를 얻을 수 있음
이번에 배울건 설명이 필요 없을 정도로 간단하니 코드만 첨부하겠음
<< 회원 정보 수정(UPDATE 쿼리), 삭제(DELETE 쿼리) 쿼리 >>
<?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.study.chapter04.MemberDao6">
// ...
<update id="updateMember" parameterType="com.study.chapter04.InsertMemberDto">
UPDATE member SET pw = #{pw}, nickname = #{nickname}, tel = #{tel} WHERE id = #{id}
</update>
<delete id="deleteMember" parameterType="com.study.chapter04.DeleteMemberDto">
DELETE FROM member WHERE id = #{id} AND pw = #{pw}
</delete>
// ...
</mapper>
<< DAO >>
package com.study.chapter04;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberDao6 {
// ...
int updateMember(InsertMemberDto newMember);
int deleteMember(DeleteMemberDto targetMember);
// ...
}
<< 컨트롤러 >>
package com.study.chapter04;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@Controller
public class MyBatisController6 {
@Autowired
private MemberDao6 memberDao;
// ...
@PutMapping("/chapter04/mybatis/v6/member")
public void updateMember(InsertMemberDto newMember) {
int result = memberDao.updateMember(newMember);
if(result == 1) {
System.out.println("회원 정보 수정 성공");
} else {
System.out.println("회원 정보 수정 실패");
}
}
@DeleteMapping("/chapter04/mybatis/v6/member")
public void deleteMember(DeleteMemberDto targetMember) {
int result = memberDao.deleteMember(targetMember);
if(result == 1) {
System.out.println("회원 정보 삭제 성공");
} else {
System.out.println("회원 정보 삭제 실패");
}
}
// ...
}
여기서 주의할 점은 INSERT 쿼리는 무조건 정수 1만 반환하지만 UPDATE, DELETE는 쿼리로 영향을 받은 행의 수를 반환함
INSERT 쿼리와 공통점은 UPDATE, DELETE 쿼리로 영향 받은 행의 수를 사용하지 않는다면 DAO 메서드의 반환 타입을 void 로 지정하면 됨
- insertMember 메서드 : Member1.xml 파일 내 id 속성이 insertMember인 쿼리로 넘겨주는 파라미터는 총 4개임
Member1.xml 파일 내 id 속성이 insertMember인 쿼리에서는 DAO가 전달하는 id 매개변수를 #{param1} 로 접근할 수 있고 pw 매개변수는 #{param2} 로 접근할 수 있음
나머지 nickname, tel도 이와 같은 방식으로 접근할 수 있음
- selectMember 메서드 : Member1.xml 파일 내 id 속성이 selectMember인 쿼리로 넘겨주는 파라미터는 총 1개임
Member1.xml 파일 내 id 속성이 selectMember인 쿼리에서는 DAO가 전달하는 nickname 매개변수를 #{param1} 로 접근할 수 있음 ( 그림은 생략 )
3. DAO를 통해 DB와 통신할 컨트롤러를 추가하자
컨트롤러 역시 간단하게 복사하자
com.study.chapter04 패키지 내 MyBatisController를 복사하고 컨트롤러명은 MyBatisController1 로 지정하자
MyBatisController1 내 코드를 아래와 같이 바꾸자
package com.study.chapter04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController1 {
@Autowired
private MemberDao1 memberDao;
@GetMapping("/chapter04/mybatis/v1/member")
public void getMemberByNickname(String nickname) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
// 닉네임으로 회원 정보 조회
SelectMemberDto member = memberDao.selectMember(nickname);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
@PostMapping("/chapter04/mybatis/v1/member")
public void insertMember(InsertMemberDto newMember) {
System.out.println("<< 회원 가입 시작 >>");
String id = newMember.getId();
String pw = newMember.getPw();
String nickname = newMember.getNickname();
String tel = newMember.getTel();
// 회원 가입
memberDao.insertMember(id, pw, nickname, tel);
System.out.println("<< 회원 가입 성공 >>");
}
}
<< 코드 설명 >>
컨트롤러에서 큰 변화는 없으니 주의만 하면 됨
(1). 사용할 DAO 인터페이스명을 MemberDao1 으로 바꾸자
(2). 컨트롤러간 URL이 충돌하지 않도록 URL의 중간에 v1 경로를 추가하자
(3). 이제 회원 가입을 할 때 DTO로 가입할 회원의 정보를 넘기는 방식이 아니므로 지금의 방식에 맞게 가입할 회원의 정보를 넘겨주기 위해서 id ~ tel까지 모두 꺼냄
(4). 가입할 회원의 정보를 DAO로 전달, DAO는 쿼리로 전달, 쿼리는 전달 받은 가입할 회원의 정보를 #{param1} ~ #{param4} 로 접근
이제 서버를 시작하고 회원 가입, 회원 정보 조회를 해보며 #{param1}, #{param2}, #{param3} ... 처럼 #{param숫자} 로 파라미터를 사용하는 방식이 제대로 동작하는지 확인해보자
방법2. #{0}, #{1}, #{2} ... 처럼 #{숫자}
1. Member1.xml 쿼리 파일을 복사하고 파일명을 Member2.xml 로 지정하자
<?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.study.chapter04.MemberDao2">
<insert id="insertMember">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{0}, #{1}, #{2}, #{3});
</insert>
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{0};
</select>
</mapper>
3. MyBatisController1 컨트롤러를 복사하고 컨트롤러명을 MyBatisController2 로 지정하자
package com.study.chapter04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController2 {
@Autowired
private MemberDao1 memberDao;
@GetMapping("/chapter04/mybatis/v2/member")
public void getMemberByNickname(String nickname) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
// 닉네임으로 회원 정보 조회
SelectMemberDto member = memberDao.selectMember(nickname);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
@PostMapping("/chapter04/mybatis/v2/member")
public void insertMember(InsertMemberDto newMember) {
System.out.println("<< 회원 가입 시작 >>");
String id = newMember.getId();
String pw = newMember.getPw();
String nickname = newMember.getNickname();
String tel = newMember.getTel();
// 회원 가입
memberDao.insertMember(id, pw, nickname, tel);
System.out.println("<< 회원 가입 성공 >>");
}
}
쿼리 파일, DAO, 컨트롤러 모두 설명이 필요 없을 정도로 간단하므로 여러분이 직접 방법1과 방법2를 비교해 공통점과 차이점을 찾아 익혀보자
방법3. @Param 어노테이션
1. Member1.xml 쿼리 파일을 복사하고 파일명을 Member3.xml 로 지정하자
<?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.study.chapter04.MemberDao3">
<insert id="insertMember">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{newMemberId}, #{newMemberPw}, #{_nickname}, #{TEL});
</insert>
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname};
</select>
</mapper>
3. MyBatisController1 컨트롤러를 복사하고 컨트롤러명을 MyBatisController3 로 지정하자
package com.study.chapter04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController3 {
@Autowired
private MemberDao3 memberDao;
@GetMapping("/chapter04/mybatis/v3/member")
public void getMemberByNickname(String nickname) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
// 닉네임으로 회원 정보 조회
SelectMemberDto member = memberDao.selectMember(nickname);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
@PostMapping("/chapter04/mybatis/v3/member")
public void insertMember(InsertMemberDto newMember) {
System.out.println("<< 회원 가입 시작 >>");
String id = newMember.getId();
String pw = newMember.getPw();
String nickname = newMember.getNickname();
String tel = newMember.getTel();
// 회원 가입
memberDao.insertMember(id, pw, nickname, tel);
System.out.println("<< 회원 가입 성공 >>");
}
}
방법4. 해시맵
1. Member1.xml 쿼리 파일을 복사하고 파일명을 Member4.xml 로 지정하자
<?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.study.chapter04.MemberDao4">
<insert id="insertMember">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{item1}, #{item2}, #{nickname}, #{tel});
</insert>
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{key};
</select>
</mapper>
3. MyBatisController1 컨트롤러를 복사하고 컨트롤러명을 MyBatisController4 로 지정하자
package com.study.chapter04;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController4 {
@Autowired
private MemberDao4 memberDao;
@GetMapping("/chapter04/mybatis/v4/member")
public void getMemberByNickname(String nickname) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
Map<String, String> filter = new HashMap<>();
filter.put("key", nickname);
// 닉네임으로 회원 정보 조회
SelectMemberDto member = memberDao.selectMember(filter);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
@PostMapping("/chapter04/mybatis/v4/member")
public void insertMember(InsertMemberDto newMember) {
System.out.println("<< 회원 가입 시작 >>");
Map<String, String> filter = new HashMap<>();
filter.put("item1", newMember.getId());
filter.put("item2", newMember.getPw());
filter.put("nickname", newMember.getNickname());
filter.put("tel", newMember.getTel());
// 회원 가입
memberDao.insertMember(filter);
System.out.println("<< 회원 가입 성공 >>");
}
}
방법5. DTO
1. Member1.xml 쿼리 파일을 복사하고 파일명을 Membe5.xml 로 지정하자
<?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.study.chapter04.MemberDao5">
<insert id="insertMember">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{id}, #{pw}, #{nickname}, #{tel});
</insert>
<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname};
</select>
</mapper>
3. MyBatisController1 컨트롤러를 복사하고 컨트롤러명을 MyBatisController5 로 지정하자
package com.study.chapter04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MyBatisController5 {
@Autowired
private MemberDao5 memberDao;
@GetMapping("/chapter04/mybatis/v5/member")
public void getMemberByNickname(SelectMemberDto selectMemberDto) {
System.out.println("<< 닉네임으로 회원 정보 조회 시작 >>");
// 닉네임으로 회원 정보 조회
SelectMemberDto member = memberDao.selectMember(selectMemberDto);
// 조회한 회원 정보 출력
System.out.println(member);
System.out.println("<< 닉네임으로 회원 정보 조회 종료 >>");
}
@PostMapping("/chapter04/mybatis/v5/member")
public void insertMember(InsertMemberDto newMember) {
System.out.println("<< 회원 가입 시작 >>");
// 회원 가입
memberDao.insertMember(newMember);
System.out.println("<< 회원 가입 성공 >>");
}
}
여기까지 쿼리가 사용할 값으로 다양한 형태의 값을 보내고 사용하는 방법을 알아봤음
쿼리가 값을 사용할 때 쿼리를 감싸고 있는 태그에 parameterType을 명시해줘야함
그러나 우리는 parameterType을 명시하지 않았음
parameterType을 명시하지 않으면 MyBatis가 쿼리와 연결될 DAO의 메서드의 매개변수 타입을 보고 알아서 채워넣음
이는 좋지 못함
이 글의 마지막으로 상황별 적절한 parameterType을 지정하는 방법을 알아보자
방법1. 쿼리에서 #{param1}, #{param2}, #{param3} ... 처럼 #{param숫자} 로 값을 사용할 때는 쿼리를 감싸고 있는 태그에 parameterType을 명시할 수 없음
방법2. 쿼리에서 방법2. #{0}, #{1}, #{2} ... 처럼 #{숫자} 로 값을 사용할 때 역시 쿼리를 감싸고 있는 태그에 parameterType을 명시할 수 없음
방법3. 쿼리에서 @Param 어노테이션 으로 값을 사용할 때 역시 쿼리를 감싸고 있는 태그에 paramterType을 명시할 수 없음
방법1 ~ 방법3까지는 쿼리에서 값을 사용할 때 parameterType을 명시할 수 없음
방법1 ~ 방법3까지는 실제 개발에서 아예 라고 할 수 있을 정도로 거의 사용하지 않는 방법임
우리는 파라미터를 사용하는 방법을 배우고 있기 때문에 쿼리가 단순해서 방법1 ~ 방법3 을 사용해 쿼리를 만든다고 해도 가독성이 떨어지지 않지만 실무에서는 방법1 ~ 방법3 을 사용해 쿼리를 만들면 가독성이 심하게 떨어지기 때문...
실무에서는 방법4 또는 방법5를 많이 사용하며 특히, 방법5가 일반적임
방법4. 쿼리에서 해시맵 으로 값을 사용할 때는 parameterType을 hashmap 으로 명시해야함
Member4.xml 파일에 들어있는 쿼리들이 해시맵을 사용하므로 Member4.xml 내 insert, select 태그를 정확하게 사용하려면 다음과 같이 사용해야함
<mapper namespace="com.study.chapter04.MemberDao4">
<insert id="insertMember" parameterType="HashMap">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{item1}, #{item2}, #{nickname}, #{tel});
</insert>
<select id="selectMember" parameterType="hashmap" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{key};
</select>
</mapper>
프로그래밍은 대소문자를 엄격히 구분하기 때문에 parameterType을 쓸 때 hashmap 은 대소문자를 정확히 맞춰 입력해야함
방법5. 쿼리에서 DTO 로 값을 사용할 때는 parameterType을 DTO의 전체 경로인 패키지명.클래스명 으로 명시해야함
Member5.xml 파일에 들어있는 쿼리들이 DTO를 사용하므로 Member5.xml 내 insert, select 태그를 정확하게 사용하려면 다음과 같이 사용해야함
<?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.study.chapter04.MemberDao5">
<insert id="insertMember" parameterType="com.study.chapter04.InsertMemberDto">
INSERT INTO member(id, pw, nickname, tel) VALUES(#{id}, #{pw}, #{nickname}, #{tel});
</insert>
<select id="selectMember" parameterType="com.study.chapter04.SelectMemberDto" resultType="com.study.chapter04.SelectMemberDto">
SELECT * FROM member WHERE nickname = #{nickname};
</select>
</mapper>
이외에도 쿼리에서 다양한 타입의 값을 사용할 수 있음
쿼리에 사용할 수 있는 타입명은 아래 표를 참고하자
데이터 타입명
쿼리에서 사용할 값이 [ boolean ] 이라면 paramterType은 [ _boolean ] 으로 명시
쿼리에서 사용할 값이[byte] 라면 paramterType은 [_byte] 로명시
쿼리에서 사용할 값이[short] 이라면 paramterType은 [_short]로명시
쿼리에서 사용할 값이[int] 라면 paramterType은[_int]로명시
쿼리에서 사용할 값이[long] 이라면 paramterType은[_long]으로명시
쿼리에서 사용할 값이[float] 이라면 paramterType은[_float]으로명시
쿼리에서 사용할 값이[double] 이라면 paramterType은[_double]으로명시
쿼리에서 사용할 값이[Boolean] 이라면 paramterType은[boolean]으로명시
쿼리에서 사용할 값이[Byte] 라면 paramterType은[byte]로명시
쿼리에서 사용할 값이[Short] 이라면 paramterType은[short]로명시
쿼리에서 사용할 값이[Integer] 라면 paramterType은[int]로명시
쿼리에서 사용할 값이[Long] 이라면 paramterType은[long]으로명시
쿼리에서 사용할 값이[ Float ]이라면 paramterType은[ float ]으로명시
쿼리에서 사용할 값이[ Double]이라면 paramterType은[ double ]으로명시
쿼리에서 사용할 값이[ Object]라면 paramterType은[ object]로명시
쿼리에서 사용할 값이[ String]이라면 paramterType은[ string ]으로명시
쿼리에서 사용할 값이[ Date]라면 paramterType은[ date ]로명시
쿼리에서 사용할 값이[ Map]이라면 paramterType은[ map ]으로명시
쿼리에서 사용할 값이[ HashMap]이라면 paramterType은[ hashmap ]으로명시
쿼리에서 사용할 값이[ List]라면 paramterType은[list]로명시
쿼리에서 사용할 값이[ ArrayList]라면 paramterType은[ arraylist ]로명시
쿼리에서 사용할 값이[ Collection]이라면 paramterType은[ collection ]으로명시
쿼리에서 사용할 값이[ Iterator]라면 paramterType은[ iterator ]로명시
쿼리에서 사용할 값이[ ResultSet]이라면 paramterType은[ ResultSet ] 으로명시