<< 학습 목표 >>

1. SQL 로그를 콘솔에 출력할 수 있다.


로그 ( Log ) 란 흔적을 말함

 

클라이언트가 서버로 요청을 보내면 서버는 요청 정보를 로그에 남김

나중에 로그에 남은 이 요청 정보를 분석해서 사용자들이 어떤 서비스를 많이 이용하는지, 어느 시간대에 많이 이용하는지 등을 분석할 수 있음

이처럼 운영에 필요한 로그를 남길 수도 있지만 개발에 필요한 로그를 남길 수 있음

 

개발에 필요한 로그는 주로 디버깅을 위해 남김

클라이언트가 어떤 값을 보냈을 때 내가 만든 API가 어떻게 동작하고 어떤 결과를 응답하는지 등을 남겨 디버깅에 활용함

이때 중요하게 활용되는 로그 중 하나는 SQL 로그임

 

개발 직무 중 DBA 가 있을 정도로 DB, SQL를 제대로 다루기 위해서는 많은 공부가 필요함

그래서 백엔드 개발자들은 주로 당장에 필요한 DB, SQL과 관련된 최소한의 지식만 가지고 공부하고 취업하고 실무에서 개발함

이 때문에 개발 중 SQL이 의도치 않게 동작할 경우 이 SQL을 확인해야되는데 로그를 남기지 않으면 SQL을 확인하기 어렵거나 불가능할 수 있음


이번에는 SQL 로그를 남기는 방법을 배워보자

 

자바에는 로그를 남기는 방법이 많은데 일반적으로 별도의 로깅 프레임워크를 사용해서 로그를 남김

자바 로깅 프레임워크에는 log4j, log4j2, SLF4J, apache common logging, logback 등이 있음

Spring Boot 로 만든 프로젝트는 기본적으로 logback 로깅 프레임워크가 내장되어있음

 

 

우리 프로젝트에 logback 로깅 프레임워크를 사용해 SQL 로그를 남겨보자

 

logback 로깅 프레임워크를 사용하려면 먼저 xml 파일을 만들어 로깅 설정을 해야함

프로젝트 -> src/main/resources -> logback-spring.xml 파일을 추가하고 아래 설정 코드를 추가하자

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<Pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] [%thread] %-5level %logger{36} - %msg%n</Pattern>
		</encoder>
	</appender>
	
	<logger name="com.study.chapter04" level="debug" />
	
	<root level="info">
		<appender-ref ref="console" />
	</root>
</configuration>

<< 코드 설명 >>

우선 설정 파일 이름에 오타가 있으면 안됨

SpringFramework는 설정 파일을 찾을 때 설정 파일의 이름으로 찾기 때문에...

 

설정 코드를 이해할 필요는 없음

"SQL 로그를 출력하려면 이렇게 써야하는구나" 라고 생각하고 넘어가자

 

 

여기까지가 로그를 출력하기 위한 설정 끝임

이제 쿼리, DAO, 컨트롤러를 추가해 SQL 로그가 출력되는지 확인하자

 

<< 쿼리 >>

프로젝트 -> src/main/resources -> mapper -> Member7.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.MemberDao7">
	<select id="getMember" resultType="com.study.chapter04.MemberDto" parameterType="com.study.chapter04.MemberDto">
		SELECT id, pw, nickname, tel FROM member
		<where>
			<if test="nickname != null">
			AND nickname = #{nickname}
			</if>
			<if test="tel != null">
			AND tel = #{tel}
			</if>
		</where>
	</select>
</mapper>

 

<< DTO >>

프로젝트 -> com.study.chapter04 -> MemberDto 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import java.time.LocalDateTime;

import lombok.Data;

@Data
public class MemberDto {
	private int idx;
	private String id;
	private String pw;
	private String nickname;
	private String tel;
	private LocalDateTime joinDateTime;
	private boolean isDel;
}

 

<< DAO >>

프로젝트 -> com.study.chapter04 -> MemberDao7 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao7 {
	MemberDto getMember(MemberDto member);
}

 

<< 컨트롤러 >>

프로젝트 -> com.study.chapter04 -> MyBatisController7 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter04;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyBatisController7 {
	@Autowired
	private MemberDao7 memberDao;
	
	@GetMapping("/chapter04/mybatis/v7/member")
	public void getMemberByNickname(MemberDto parameter) {
		MemberDto member = memberDao.getMember(parameter);
		
		System.out.println("<< 조회 결과 >>");
		System.out.println(member);
	}
}

 

이제 서버를 실행시키고 컨트롤러를 호출해보자

이때 닉네임(nickname) 이나 연락처(tel) 파라미터를 보내야함

 

컨트롤러를 호출하면 DAO를 타고 SQL이 실행되며 콘솔에 로그가 남을 것

728x90
LIST

<< 학습 목표 >>

1. MyBatis에서 사용하는 두 가지 캐시에 대해 설명할 수 있다.


이번에 배울 cache, cache-ref 태그는 간단하게 이런게 있다 정도만 알아볼 것

 

MyBatis는 효율적으로 쿼리를 보내고 결과를 받기 위해 캐시(Cache)를 사용함

만약 캐시에 대해 처음 듣는다면 우선 인터넷이나 ChatGPT의 도움을 받아 캐시에 대해서 이해하고 오자

 

MyBatis가 사용하는 캐시

1. Local Session Cache

2. Second Level Cache

 

<< Local Session Cache >>

- 기본적으로 활성화 되어있는 캐시

- 당연히 조회(SELECT)에만 적용됨

  완전히 동일한 조회 쿼리를 여러번 보내면 매번 DB에서 조회해오지 않고 처음 한번만 DB에서 조회한 후 결과를 캐시에 저장함

  두번째부터는 캐시에서 결과를 꺼내 반환함

  캐시가 가득 차면 LRU ( Least Recently Used / 맨 처음 저장된 캐시 ) 를 제거하고 새 조회 결과를 저장함

 - 이 캐시는 비활성화 하는게 불가능함, 대신 캐시의 활동 범위를 좁힐 수 있음

   활동 범위 : SESSION, STATEMENT

     > 활동 범위는 SESSION이 기본값이며 UPDATE, COMMIT, ROLLBACK, CLOSE 시 마다 캐시를 비움

     > flushCache 옵션을 사용해 활동 범위를 STATEMENT 로 바꿀 수 있음

     > 활동 범위를 STATEMENT 로 바꾸면 SELECT, INSERT, UPDATE, DELETE 시 마다 캐시를 비우도록 할 수 있음

 

<< Second Level Cache >>

 - 기본적으로 비활성화 되어있는 캐시

   <mapper> 태그 내 <cache /> 태그를 사용해 활성화 시킬 수 있음

   <mapper> 태그 내 <cache /> 태그로 활성화 시키므로 매퍼별 캐시가 따로 관리됨

 - INSERT, UPDATE, DELETE 시 마다 이 캐시를 비움

 - 매퍼별로 캐시를 관리하므로 <mapper> 태그 안에서는 테이블 하나만 관리하는게 좋음

   이를 제대로 이해하려면 DDD (도메인 주도 개발)에 대해서 이해해야함


<< Local Session Cache의 적용 범위를 STATEMENT 로 바꾸는 예시 >>

insert, update, delete, select 등의 태그에 사용할 수 있는 flushCache 속성을 false로 지정하면 됨

flushCache 속성 : 이 속성의 값을 true로 설정하면 해당 SQL이 실행될 때 마다 Local Session Cache, Second Level Cache가 비워짐

  이 속성의 기본값은 false임


<< Second Level Cache를 활성화 시키는 예시 >>

단순히 <cache> 태그를 추가하기만 해도 Second Level Cache 를 활성화시킬 수 있음

이 태그는 여러 속성이 있는데 그 중 대표적인 몇 가지 속성만 알아보자

- eviction 속성 : 캐시 비움 정책

  기본 정책은 LRU ( Least Recently Used ) 임, LRU는 가장 먼저 추가된 캐시를 비우는 정책임

  그외에도 FIFO, SOFT, WEAK 가 있음

 - flushInterval 속성 : 캐시 비움 주기

  밀리초 단위로 설정하며 지정한 초가 지날 때 마다 주기적으로 캐시를 비움

  특정 시간을 지정해서 비우도록 할 수는 없음

  (예. "매일 새벽 4시에 캐시를 비워라" 안됨)

 - size 속성 : 캐시의 사이즈


마지막으로 cache-ref 태그는 다른 매퍼의 캐시를 같이 사용하고 싶을 때 사용하는 태그

아예 사용하지 않는다고 봐도 되므로 간단하게 예시 코드만 첨부함

<?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">
	<cache-ref namespace="mapper 태그를 갖고 있는 파일의 경로" />
    
	// ...
</mapper>

이렇게 MyBatis 캐시에 대해 알아봤음

그러나 실무에서는 MyBatis 가 내장하고 있는 캐시를 사용하지 않고 별도로 캐시 프레임워크를 붙여서 사용함

 

실무에서 많이 사용하는 캐시 프레임워크

 - EhCache

 - OsCache

 - Hazelcast

 

참고 : https://idea-sketch.tistory.com/31, https://12bme.tistory.com/352, https://jp1020.tistory.com/entry/mybatis-cache-%EC%84%A4%EC%A0%95, https://yunamom.tistory.com/40

728x90
LIST

<< 학습 목표 >>

1. sql, include 태그를 활용할 수 있다.


이번에 알아볼 동적 쿼리 태그는 sql, include, property 태그

 

태그명 sql
설명 자주 사용하는 SQL 문장을 저장하는 태그
속성 id속성 : 저장해둔 SQL 문장의 별칭, 속성명이 id 인것처럼 이 별칭은 고유해야함

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 태그에 대해서 자세히 찾아봐도 됨

태그명 property
설명 sql 태그 안에 있는 SQL 문장이 필요한 값을 전달할 수 있음
속성 name 속성 : 전달할 값의 이름

value 속성 : 전달할 값

 

728x90
LIST

<< 학습 목표 >>

1. bind 태그를 활용할 수 있다.

2. AS 키워드 대신 resultMap 태그를 활용할 수 있다.

3. 계층적 데이터 구조를 이해하고 설계할 수 있다.

4. 계층적 데이터 구조인 데이터를 가져올 때 resultMap 태그를 활용할 수 있다.


이번에 배울 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 자식 태그만 사용하면 됨

 

member ( 회원 정보 ) 테이블의 column이 다음과 같은 상황임

 

MyBatis를 사용해서 회원 정보를 조회하는 쿼리를 만들자

<?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" resultType="com.study.chapter04.ResultMapDto">
		SELECT * FROM member;
	</select>
	
	// ...
</mapper>

 

결과를 저장할 DTO는 ResultMapDto 임

ResultMapDto 는 아래와 같음

package com.study.chapter04;

import java.time.LocalDateTime;

import lombok.Data;

@Data
public class ResultMapDto {
	private int userIdx;
	private String userId;
	private String _pw;
	private String nickName;
	private String telNumber;
	private LocalDateTime joinDateTime;
	private boolean isDel;
}

 

이때 테이블의 칼럼명과 DTO의 멤버 변수명이 다르기 때문에 쿼리가 정상적으로 수행되도 결과를 받을 수 없음

 

결과를 제대로 받으려면 쿼리에 AS ( Alias ) 를 사용하면 됨

 

그러나 AS는 DBMS가 지원하는 키워드임

AS 대신 MyBatis가 지원하는 resultMap 태그를 사용해도 됨

<?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">
	// ...
	
	<resultMap type="com.study.chapter04.ResultMapDto" id="newResultMapDto">
		<id 	column="idx"			property="userIdx"		javaType="int" 		jdbcType="INTEGER" />
		<result column="id"				property="userId"		javaType="String" 	jdbcType="VARCHAR" />
		<result column="pw"				property="_pw"			javaType="String" 	jdbcType="VARCHAR" />
		<result column="nickname"		property="nickName"		javaType="String" 	jdbcType="VARCHAR" />
		<result column="tel"			property="telNumber"	javaType="String" 	jdbcType="VARCHAR" />
		<result column="joinDateTime"	property="joinDateTime"	javaType="String" 	jdbcType="VARCHAR" />
		<result column="isDel"			property="isDel"		javaType="boolean"	jdbcType="VARCHAR" />
	</resultMap>
	
	<select id="selectMember" resultMap="newResultMapDto">
		SELECT * FROM member;
	</select>
	
	// ...
</mapper>

<< 코드 설명 >>

(1). 칼럼명과 멤버 변수를 매치하기 위해 resultMap 태그 사용

(2). PK인 칼럼을 멤버 변수와 연결하기 위한 태그

  column속성은 테이블의 칼럼명

  property속성은 DTO의 멤버 변수명

  javaType속성은 DTO의 멤버 변수의 데이터 타입

  jdbcType속성은 테이블의 칼럼의 데이터 타입

(3). PK가 아닌 칼럼을 멤버 변수와 연결하기 위한 태그

  속성들은 (2) 와 동일함

(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>

<< 코드 설명 >>

조회 결과를 담기 위해 DTO의 멤버 변수 이름에 맞춰서 SELECT 하도록 지정함

 

 

이제 DAO 를 추가해 컨트롤러에서 쿼리를 실행하고 결과를 받아올 수 있도록 하자

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryDao {
	List<CategoryDto> selectAllCategory();
}

 

 

마지막으로 컨트롤러를 추가해 시작 하면서 보여줬던 출력 예시처럼 출력하도록 만들자

이건 보지말고 여러분이 직접 만들어보자

더보기

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 CategoryController {
	@Autowired
	CategoryDao categoryDao;
	
	@GetMapping("/chatper04/categories")
	public void printAllCategory() {
		List<CategoryDto> categories = categoryDao.selectAllCategory();

		String prevDepth1CategoryName = "";
		String prevDepth2CategoryName = "";
		String prevDepth3CategoryName = "";
		
		for(CategoryDto category : categories) {
			String nowDepth1CategoryName = category.getDepth1CategoryName();
			if(!nowDepth1CategoryName.equals(prevDepth1CategoryName)) {
				prevDepth1CategoryName = nowDepth1CategoryName;
				
				System.out.println(nowDepth1CategoryName);
			} // end if
			
			String nowDepth2CategoryName = category.getDepth2CategoryName();
			if(nowDepth2CategoryName != null) {
				if(!nowDepth2CategoryName.equals(prevDepth2CategoryName)) {
					prevDepth2CategoryName = nowDepth2CategoryName;
					
					System.out.println("\t" + nowDepth2CategoryName);
				}
				
				String nowDepth3CategoryName = category.getDepth3CategoryName();
				if(nowDepth3CategoryName != null) {
					if(!nowDepth3CategoryName.equals(prevDepth3CategoryName)) {
						prevDepth3CategoryName = nowDepth3CategoryName;
						
						System.out.println("\t\t" + nowDepth3CategoryName);
					} // end if
				} // end if
			} // end if
		} // end for
	}
}

이 컨트롤러를 완성했다고 해도 시간이 상당히 오래 걸렸을 것

그 이유는 카테고리는 우리가 본 형태는 계층적 데이터 구조이고 구현도 계층적 데이터 구조로 구현했음

 

그러나 자바로 가져올 때는 계층적 데이터 구조가 아닌 선형적 데이터 구조로 가져왔기 때문임

이렇게 계층적 데이터 구조를 그대로 가져오려면 resultMap 태그를 사용해야함

 

resultMap을 사용해 계층적 데이터 구조를 그대로 가져와보자

계층적 데이터 구조로 가져오기 위해 DTO를 새로 선언해야함

package com.study.chapter04;

import java.util.List;

import lombok.Data;

@Data
public class CategoriesDto {
	private int idx;
	private String name;
	
	List<CategoriesDto> childCategories;
}

<< 코드 설명 >>

(1). n차 카테고리의 번호와 이름을 저장하기 위한 멤버 변수

(2). n차 카테고리의 하위 카테고리인 n+1차 카테고리의 정보를 저장하기 위한 멤버 변수

 

 

 

 

 

 

이제 쿼리에서 CategoriesDto 를 사용해 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.CategoryDao">
	<resultMap type="com.study.chapter04.CategoriesDto" id="CategoriesDto">
		<id column="depth1CategoryIdx" property="idx" jdbcType="INTEGER" />
		<result column="depth1CategoryName" property="name" jdbcType="VARCHAR" />
		
		<collection property="childCategories" ofType="com.study.chapter04.CategoriesDto">
			<id column="depth2CategoryIdx" property="idx" jdbcType="INTEGER" />
			<result column="depth2CategoryName" property="name" jdbcType="VARCHAR" />
			
			<collection property="childCategories" ofType="com.study.chapter04.CategoriesDto">
				<id column="depth3CategoryIdx" property="idx" jdbcType="INTEGER" />
				<result column="depth3CategoryName" property="name" jdbcType="VARCHAR" />
			</collection>
		</collection>
	</resultMap>

	<select id="selectAllCategory" resultMap="CategoriesDto">
		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>

<< 코드 설명 >>

(1). SELECT 결과를 계층적 데이터 구조로 담기 위해 선언한 resultMap을 사용하도록 바꿈

(2). SELECT 결과를 계층적 데이터 구조로 담기 위해 resultMap 선언

  이 구조를 제대로 이해하려면 SELECT의 결과와 함께 대조해야함

 

SELECT 결과를 보면 중복을 제거하고 보면 1차 카테고리가 3개로 나뉨

resultMap은 중복을 제거하고 1차 카테고리 1개당 DTO ( type 속성으로 지정한 com.study.chapter04.CategoriesDto ) 하나에 담에 총 3개의 1차 카테고리 정보를 담고 있는 List가 만들어짐

 

 

그 다음 1차 카테고리가 같은 2차 카테고리의 정보를 1차 카테고리 정보를 담고 있는 DTO의 childCategories 멤버 변수 안에 담음

2차 카테고리 역시 중복을 제거한 후 2차 카테고리 1개당 DTO 하나에 담음

 

- 1차 카테고리 idx가 1

- 1차 카테고리 name이 패션의류/잡화

- 자식 카테고리 4개 ( 여성패션, 남성패션, 남녀 공용 의류, 유아동패션 )

 

- 1차 카테고리 idx가 2

- 1차 카테고리 name이 뷰티

- 자식 카테고리 3개 ( 명품뷰티, 스킨케어, 클린/비건뷰티 )

 

- 1차 카테고리 idx가 3

- 1차 카테고리 name이 출산/유아동

- 자식 카테고리 3개 ( 유아동패션, 기저귀, 물티슈 )

 

 

마지막으로 2차 카테고리가 같은 3차 카테고리의 정보를 2차 카테고리 정보를 담고 있는 DTO의 childCategories 멤버 변수 안에 담음

 

3차 카테고리 역시 중복을 제거한 후 3차 카테고리 1개당 DTO 하나에 담음

 

- 2차 카테고리 idx가 4

- 2차 카테고리 name이 여성패션

- 자식 카테고리 4개 ( 의류, 속옷/잠옷, 신발, 가방/잡화 )

 

- 2차 카테고리 idx가 5

- 2차 카테고리 name이 남성패션

- 2식 카테고리 3개 ( 의류, 속옷/잡화, 신발, 가방/잡화 )

 

- 2차 카테고리 idx가 6

- 2차 카테고리 name이 남녀 공용 의류

- 2식 카테고리 3개 ( 티셔츠, 맨투맨/후드티, 셔츠 )

 

...

 

 

이제 컨트롤러에서 쿼리를 사용해 결과를 받을 수 있도록 DAO 도 수정하자

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryDao {
	List<CategoriesDto> selectAllCategory();
}

 

 

마지막으로 계층적 구조로 담긴 모든 카테고리명을 출력하도록 컨트롤러를 수정하자

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 CategoryController {
	@Autowired
	CategoryDao categoryDao;
	
	private void printCategory(CategoriesDto category, int depth) {
		String tab = "";
		for(int i=1; i<depth; i++) {
			tab = tab + "\t";
		}
		
		System.out.println(tab + category.getName());

		List<CategoriesDto> childCategories = category.getChildCategories();
		if(childCategories != null) {
			for(CategoriesDto childCategory : childCategories) {
				printCategory(childCategory, depth+1);
			}
		}
	}
	
	@GetMapping("/chatper04/categories")
	public void printAllCategory() {
		List<CategoriesDto> allCategory = categoryDao.selectAllCategory();
		
		for(CategoriesDto category : allCategory) {
			printCategory(category, 1);
		}
	}
}

<< 코드 설명 >>

(1). 출력할 카테고리 정보

(2). 출력할 카테고리의 차수

(3). 카테고리 차수에 맞춰 tab 을 추가

(4). 현재 카테고리 명 출력

(5). 현재 카테고리의 자식 카테고리가 있다면 재귀호출 방식으로 자식 카테고리 정보 출력


계층적 데이터 구조의 경우 데이터를 가져오는 것 자체는 선형적 데이터 구조로 가져오는 방법이 쉽지만 활용할 때는 굉장히 고생함

그러나 계층적 데이터 구조로 가져오기 위해서는 어떻게 가져올 지 를 굉장히 고민해야함

 

경험상 계층적 데이터 구조는 그대로 계층적 데이터 구조로 가져오는게 전체적으로 드는 시간과 노력이 절약됨

728x90
LIST

<< 학습 목표 >>

1. set 태그를 활용할 수 있다.


set 태그로 동적 쿼리를 구성하는 방법은 where 태그와 동일함

 

그래서 여기서는 곧바로 간단한 예시 하나로 set 태그로 동적 쿼리를 구성하는 방법만 배우자

 

회원 정보를 수정하는 기능이 있는 상황임

회원 정보는 비밀번호, 닉네임, 연락처 중 하나만 수정할 수도 있고 모두 수정할 수도 있음

수정하지 않을 값은 전달 하지 않고 수정할 값믄 전달함

이런 상황에서 set 태그를 사용해 다음과 같이 동적 쿼리를 만들 수 있음

 

<?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">
	// ...
	
	<update id="updateMember" parameterType="com.study.chapter04.StudygroupMember">
		UPDATE member
		<set>
			<if test="pw != null">pw = #{pw},</if>
			<if test="nickname != null">nickname = #{nickname},</if>
			<if test="tel != null">tel = #{tel},</if>
		</set>
		WHERE id = #{id}
	</update>
	
	// ...
</mapper>

<< 코드 설명 >>

update 태그가 동작할 때 만들어질 수 있는 쿼리는 8가지임

1. 비밀번호, 닉네임, 연락처 모두 전달 받지 못했을 경우

 -> UPDATE member WHERE id = #{id}

2. 비밀번호만 전달 받았을 경우

 -> UPDATE member SET pw = #{pw} WHERE id = #{id}

3. 닉네임만 전달 받았을 경우

 -> UPDATE member SET nickname = #{nickname} WHERE id = #{id}

4. 연락처만 전달 받았을 경우

 -> UPDATE member SET 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 가 시작 되기 전에 ) , 가 없음

set 태그는 마지막에 , 를 지워줌

 

set 태그를 trim 태그로 대신 구현하면 아래와 같음

<update id="updateMember" parameterType="com.study.chapter04.StudygroupMember">
	UPDATE member
	<trim prefix="SET" suffixOverrides=",">
		<if test="pw != null">pw = #{pw},</if>
		<if test="nickname != null">nickname = #{nickname},</if>
		<if test="tel != null">tel = #{tel},</if>
	</trim>
	WHERE id = #{id}
</update>

 

728x90
LIST

<< 학습 목표 >>

1. where 태그를 활용할 수 있다.


전 글 ( https://codingaja.tistory.com/119 ) 에서 작성한 동적 쿼리 중에는 문제의 소지가 있는 쿼리가 있음

 

<?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("<< 닉네임으로 회원 정보 조회 종료 >>");
	}

	// ...
}

<< 코드 설명 >>

(1). 컨트롤러는 DAO 측으로 null 을 전달하고 있음

 

 

<< DAO >>

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao {
	// ...
	
	List<SelectMemberDto> selectMember(List<SelectMemberDto> selectConditions);
	
	// ...
}

<< 코드 설명 >>

(1). 컨트롤러가 DAO로 null을 전달했으므로 DAO도 쿼리쪽으로 selectionCondition을 null로 전달함

 

 

<< 쿼리 설명 >>

쿼리에서는 null 상태인 selectionConditions 를 사용해 반복문을 동작시키려 하므로 NullPointerException이 발생할 수 있음

 

이걸 해결하는 방법은 무엇일까?

 

방법1. foreach를 if문으로 감싼다.

그러나 이 방법은 잘못된 방법임

selectCondition의 값이 null 일 때 만들어지는 쿼리가 [ SELECT * FROM WHERE nickname IN ] 이와 같으므로 SQL 문법 오류가 생겨 SQLException이 발생할 수 있음

 

방법2. WHERE 를 if문으로 감싼다.

selectConditions가 null이 아니라면 WHERE 절이 붙고 in 에 들어갈 값은 foreach문을 통해 들어가게 됨


WHERE절을 동적으로 구성할 때 위와 같이 해도 되지만 프로그램의 규모가 커져 WHERE절이 점점 더 복잡해진다면 더 이상 위와 같은 동적 쿼리로는 WHERE 절을 구성할 수 없게됨

 

복잡한 WHERE 절을 동적 쿼리로 구성할 때는 where 태그를 사용함

태그명 where
설명 WHERE 절을 동적으로 구성하고 싶을 때 사용하는 태그

WHERE 태그 안에 어떤 구문이 들어있다면 WHERE절을 붙여 해당 구문을 쿼리에 추가함
WHERE 태그 안에 아무 구문도 들어있지 않다면 WHERE절이 붙지 않음

WHERE 절 안에 어떤 구문이 AND 또는 OR 등으로 시작한다면 SQL문법에 오류가 발생하므로 이런 부분을 방지하기 위해 어떤 구문이 AND 또는 OR 등으로 시작한다면 해당 AND, OR 등만 지워줌 
속성 없음

 

우선 간단하면서 직관적인 두 예를 보며 WHERE 절에 대해서 알아보자

 

1. WHERE 태그 안에 아무 구문도 들어있지 않는 경우

이런 경우 SELECT 쿼리에 WHERE 절이 붙지 않음

따라서 만들어지는 쿼리는 [ SELECT * FROM member; ] 임

 

2. WHERE 태그 안에 어떤 구문이 들어있는 경우

이런 경우 SELECT 쿼리에 WHERE 절이 붙음

따라서 만들어지는 쿼리는 [ SELECT * FROM member WHERE nickname IN ("홍길동", "김철수", "고영희"); ] 임


이제 where태그를 본격적으로 사용해보자

본격적이라고는 했지만 굉장히 간단함

 

1. where 태그와 if 태그를 함께 사용 하는 상황

회원 정보를 검색할 때 필터 기능이 있어 아이디, 닉네임, 연락처로 회원 정보를 검색할 수 있는 상황이라고 하자

검색 조건을 아무것도 설정하지 않아 전체 회원을 조회할 수도 있고 한 항목만 사용될 수도 있고 모든 항목이 다 사용될 수도 있음

여러 항목이 검색 조건으로 사용됐을 때는 검색 조건들이 모두 OR 로 묶여야 되는 상황임

이런 상황은 where 태그와 if 태그를 사용해 해결할 수 있을 것

<select id="selectMember" parameterType="com.study.chapter04.SelectMemberDto" resultType="com.study.chapter04.SelectMemberDto">
	SELECT * FROM member
	<where>
		<if test="id != null">
			id = #{id}
		</if>
		<if test="nickname != null">
			OR nickname = #{nickname}
		</if>
		<if test="tel != null">
			OR tel = #{tel}
		</if>
	</where>
	;
</select>

<< 코드 설명 >>

위 쿼리에서 만들어질 수 있는 쿼리는 7가지임

1. 아이디, 닉네임, 연락처 모두 없을 경우

  -> SELECT * FROM member;

2. 아이디만 있을 경우

 -> SELECT * FROM member WHERE id = #{id}

3. 닉네임만 있을 경우

 -> SELECT * FROM member WHERE nickname = #{nickname}

4. 연락처만 있을 경우

 -> SELECT * FROM member WHERE tel = #{tel}

5. 아이디, 닉네임만 있을 경우

 -> SELECT * FROM member WHERE id = #{id} OR nickname = #{nickname}

6. 아이디, 닉네임, 연락처 모두 있을 경우

 -> SELECT * FROM member WHERE id = #{id} OR nickname = #{nickname} OR tel = #{tel}

7. 닉네임, 연락처만 있을 경우

 -> SELECT * FROM member WHERE nickname = #{nickname} OR tel = #{tel}

 

아이디가 있는 상황 ( 2, 5, 6 ) 에서는 당연히 id = #{id} 로 시작함

그러나 닉네임 또는 연락처가 있는 상황 ( 3, 4, 7 ) 에서는 OR 가 먼저 와야 할 것같지만 맨 앞에 있는 OR 는 삭제 됐음

where 태그는 맨 앞에 AND 또는 OR 가 오면 삭제함

 

trim 태그를 사용해서 where 태그와 똑같이 표현할 수 있음

<select id="selectMember" parameterType="com.study.chapter04.SelectMemberDto" resultType="com.study.chapter04.SelectMemberDto">
	SELECT * FROM member
	<trim prefix="WHERE" prefixOverrides="AND | OR">
		<if test="id != null">
			id = #{id}
		</if>
		<if test="nickname != null">
			OR nickname = #{nickname}
		</if>
		<if test="tel != null">
			OR tel = #{tel}
		</if>
	</trim>
	;
</select>

 

 

2. where 태그와 foreach 태그를 함께 사용하는 상황

회원 정보를 검색할 때 필터 기능이 있어 닉네임들로 회원 정보를 검색할 수 있는 상황이라고 하자

닉네임을 입력하지 않고 전체 회원 정보를 검색할 수도 있고 닉네임을 하나만 입력해 한 회원의 정보만 검색할 수도 있으며 여러 닉네임을 입력해 여러 회원의 정보를 검색할 수도 있음

이런 상황은 where 태그와 if, foreach 태그를 함께 사용해 해결 할 수 있을 것

<select id="selectMember" parameterType="com.study.chapter04.SelectMemberDto" resultType="com.study.chapter04.SelectMemberDto">
	SELECT * FROM member
	<where>
		<if test="nicknames != null">
			nickname IN
			<foreach collection="nicknames" item="nickname" open="(" close=")" separator=", ">
				#{nickname}
			</foreach>
		</if>
	</where>
	;
</select>

여기까지 where 태그를 활용하는 방법을 알아봤음

여기서 알아본 방법 외에도 전 글 ( https://codingaja.tistory.com/119 ) 에서 배운 동적 쿼리와 함께 사용하면 만들고 싶은 모든 쿼리를 다 만들 수 있음

 

또한 여기서는 SELECT 쿼리에서만 where 태그 를 사용했지만

UPDATE, DELETE 역시 WHERE 절이 있으므로 UPDATE, DELETE 에서도 where 태그를 활용할 수 있음

728x90
LIST

<< 학습 목표 >>

1. 정적 쿼리와 동적 쿼리의 특징을 설명할 수 있다.

2. trim 태그를 활용할 수 있다.

3. if 태그를 활용할 수 있다.

4. choose, when, otherwise 태그를 활용할 수 있다.

5. if 태그와 choose, when, otherwiser 태그를 사용할 때 주의할 점을 설명할 수 있다.

6. foreach 태그를 활용할 수 있다.


지금까지 배운 것들만 가지고서도 충분히 내가 만들고 싶은 쿼리를 만들 수 있음

이번에는 알고 있으면 유용한 동적 쿼리에 대해서 배우자

 

우리가 지금까지 작성한 쿼리는 정적 쿼리임

 

다시 한번 전 글 ( https://codingaja.tistory.com/118 ) 에서 작성한 쿼리를 보자

<?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" useGeneratedKeys="true" keyColumn="idx">
		INSERT INTO studygroupInfo(title, contents, regDate) VALUES(#{title}, #{contents}, CURRENT_TIMESTAMP());
	</insert>

	<insert id="joinStudygroupMember" parameterType="com.study.chapter04.StudygroupMember">
		INSERT INTO studygroupMember(studygroupIdx, memberIdx) VALUES(#{studygroupIdx}, #{memberIdx});
	</insert>
</mapper>

어느 상황에서든 INSERT 쿼리가 바뀔 일은 없음

INSERT 쿼리에 사용한 값은 바뀔 수 있지만 INSERT 쿼리의 구조 자체가 바뀔 순 없음

 

이렇게 언제 실행시키든 그 구조가 똑같은 쿼리를 정적 쿼리라고 함

 

 

이번에는 동적 쿼리의 예를 하나 들어보고 바로 동적 쿼리를 배워보자

<?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" useGeneratedKeys="true" keyColumn="idx">
		INSERT INTO studygroupInfo(title, contents, regDate) VALUES(#{title}, #{contents}, CURRENT_TIMESTAMP());
	</insert>

	<insert id="joinStudygroupMember" parameterType="com.study.chapter04.StudygroupMember">
		<if test="idx == 0">
			INSERT INTO studygroupMember(studygroupIdx, memberIdx) VALUES(#{studygroupIdx}, #{memberIdx});
		</if>
		<if test="idx != 0">
			INSERT INTO studygroupMember(idx, studygroupIdx, memberIdx) VALUES(#{idx}, #{studygroupIdx}, #{memberIdx});
		</if>
	</insert>
</mapper>

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 태그 내 구문의 맨 뒤에 해당 하는 문자열이 있다면 지움

 

- prefix, suffix 속성 사용 예시

<insert id="insertMember" parameterType="com.study.chapter04.InsertMemberDto">
	INSERT INTO member(id, pw, nickname, tel) VALUES(#{id}, #{pw}, #{nickname}, #{tel});
</insert>

위와 같은 쿼리가 있을 때 prefix, suffix 를 사용해 아래와 같이 바꿀 수도 있음

<insert id="insertMember" parameterType="com.study.chapter04.InsertMemberDto">
	INSERT INTO member(id, pw, nickname, tel) VALUES
	<trim prefix="(" suffix=");">
		#{id}, #{pw}, #{nickname}, #{tel}
	</trim>
</insert>

! 주의 사항 !

가끔 이런 예시를 들면 "왜 저렇게 사용하나요?" 라는 의문을 갖는 분들이 있는데 저렇게 할 수도 있다는거지 반드시 저렇게 해야한다는게 아님

저렇게 할 수 있다는걸 알고 있으면 나중에 여러분의 상황에 맞게 바꿔서 사용할 수 있을 것

 

 

- 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 속성의 값을 ' (홑따옴표) 로 감쌌음

  자바에서는 문자열 비교는 equals 메서드를 사용해야하지만 MyBatis는 == 연산자를 사용해도 같다, 다르다를 판단할 수 있음

 

문자열 비교할 때 아래와 같이 equals, equalsIsIgnoreCase 메서드를 호출할 수 있음

equals 메서드는 자바의 equals 메서드와 같음

equalsIsIgnoreCase 메서드는 대소문자를 무시하고 같은지를 판단할 수 있음

 


태그명 choose, when, otherwise
설명 choose - 자바의 if ~ else if ~ else와 같은 태그로 if ~ else if ~ else 의 영역을 나타내는 태그

when - 자바의 if ~ else 와 같은 태그로 choose 안에 사용하며 조건식이 참(true)인 경우 안에 있는 구문을 추가해주는 태그

otherwise - 자바의 else와 같은 태그로 choose 안에 사용하며 모든 when의 조건식이 거짓(false)인 경우 otherwise 안에 있는 구문을 추가해주는 태그
속성 choose 태그 - 속성 없음

when 태그 - test 속성 : if의 test 속성과 동일함

otherwise 태그 - 속성 없음

- choose, when, otherwise 태그 사용 예시

  if문에서 사용했던 예시를 choose, when, otherwise 의 상황에 맞게 바꾼 것

<select id="selectMember" parameterType="String" resultType="com.study.chapter04.SelectMemberDto">
	SELECT * FROM member WHERE nickname = #{nickname}
	
	<choose>
		<when test='orderby != null and orderby.equals("오름차순")'>
			ORDER BY idx ASC
		</when>
		<otherwise>
			ORDER BY idx DESC
		</otherwise>
	</choose>
    
	<if test="start >= 0">
		LIMIT #{start}
	
		<if test="end != 0">
			, #{end}
		</if>
	</if>
	
	;
</select>

<< 코드 설명 >>

(1). when과 otherwise는 choose 태그 안에만 사용할 수 있음

  마치 case 는 switch 안에만 사용할 수 있듯이...

  when은 if 내지는 else if의 역할을 하는 태그

  when의 test가 true라면 그 안에 있는 구문이 추가됨

  otherwise는 else 의 역할을 하는 태그

  모든 when의 test가 false라면 otherwise 안에 있는 구문이 추가됨

 

  if문에서도 else를 붙일 때 주의해야하는 것처럼 when, otherwise에서도 otherwise를 붙일 때 주의해야함

  위 코드는 문제가 생길 수 있는 코드임

  orderby 가 null이 아닌데 orderby 가 오름차순 이라면 ORDER BY idx ASC 구문이 붙지만 그외의 모든 상황에서는 ORDER BY idx DESC가 붙는 것임

  그외의 모든 상황이라는건 orderby가 null 이거나 orderby가 오름차순이 아닌 상황임

 

  if문에서 사용한 의도와는 전혀 다른 코드가 된 것

  틀렸다고 할 순 없지만 otherwise를 붙일 때는 조심하자

  if문에서 사용한의도와 완전히 같은 코드가 되려면 아래와 같이 바꿀 수 있음

 

choose, when, otherwise의 마지막으로 if문과 choose, when, otherwise를 비교해보자

(1). orderby가 null이 아닌데 orderby가 오름차순이라면 ORDER BY idx ASC 구문이 추가됨

  orderby가 null이 아닌데 orderby가 내림차순이라면 ORDER BY idx DESC 구문이 추가됨

(2). orderby가 null이 아닌데 orderby가 오름차순이라면 ORDER BY idx ASC 구문이 추가됨

  orderby가 null이 아닌데 orderby가 내림차순이라면 ORDER BY idx DESC 구문이 추가됨

 

위 if, choose, when, otherwise 를 해석해보면 이렇게 해석될 수 있을 것

그러나 여기서 if와 choose, when, otherwise는 큰 차이가 있음

 

if는 if 마다 마다가 개별적인 한 덩어리임

choose, when, otherwise는 coose 단위로 한 덩어리임

 

따라서 컴퓨터는 첫번째 if문의 test가 true이면 ORDER BY idx ASC 구문을 추가한 후 두 번째 if문 또한 체크함

지금 상황에서는 그럴리없지만 두 번째 if문의 test가 true라면 이어서 ORDER BY idx DESC 구문이 추가됨

이러면 문법적 오류가 발생함

if는 if 마다 개별적인 한 덩어리라는점에 주의하자

 

 

choose, when, otherwise 는 choose 안에 있는 한 when, 또는 otherwise 만 실행 되기 때문에 첫 번째 when의 test가 true이면 ORDER BY idx ASC 구문만 추가됨

첫 번째 when의 test가 false면 두 번째 when의 test를 체크하고 두 번째 when의 test가 true이면 ORDER BY idx DESC 구문만 추가됨

이렇게 choose, when, otherwise는 choose 단위로 한 덩어리라는 점에 주의하자


태그명 foreach
설명 자바의 for 또는 향상된 for문과 같은 태그로 반복적으로 구문을 추가해주는 태그
속성 - collection : 반복문에 사용할 반복 가능한 객체 / 배열, Map, List, Set 등과 같은 객체가 반복 가능한 객체임

- item : n번 째 반복에서 반복 가능한 객체 내 n번 째 요소(element) 를 전달 받을 변수명

- index : 반복 시 인덱스 번호가 필요할 때 지정하는 속성으로 인덱스 번호를 전달 받을 변수명

- open : 반복을 시작하기 전 추가할 구문

- close : 반복을 끝낸 후 추가할 구문

- separator : n번 째 반복이 끝난 후 추가할 구문

 

- foreach 태그 사용 예시

DAO가 쿼리 쪽으로 데이터들을 전달할 때 Map 보단 배열, List, Set 을 더 많이 활용하므로 Map인 경우는 설명하지 않음

또한 배열, List, Set은 모두 데이터들을 선형적으로 담고 있기 때문에 사용하는 방법 모두 동일함

 

<< SelectMemberDto >>

package com.study.chapter04;

import java.time.LocalDateTime;

import lombok.Data;

@Data
public class SelectMemberDto {
	private String id;
	private String pw;
	private String nickname;
	private String tel;
	private int idx;
	private LocalDateTime joinDateTime;
	private boolean isDel;
}

 

<< MemberDao >>

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao {
	// ...
    
	SelectMemberDto selectMember(List<SelectMemberDto> selectConditions);
    
	// ...
}

<< 코드 설명 >>

id, pw, nickname, tel, idx, joinDateTime, isDel 멤버 변수를 갖고 있는 DTO가 있음

 

 

 

 

 

 

 

 

DAO에서는 id가 selectMember인 쿼리로 SelectMemberDto들을 담은 List를 전달하고 있음

전달하는 List의 이름은 selectConditions 임

 

 

 

 

 

 

이제 쿼리를 만들자

<?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>

<< 코드 설명 >>

(1). DAO가 전달해주는 데이터들을 받기 위해서 데이터의 타입을 지정함

(2). 이 쿼리가 완성된 한 예를 보면 다음과 같음

  SELECT * FROM member WHERE nickname IN ("홍길동", "김철수", "고영희");

  IN 안에는 반복적으로 문자열이 들어가야하므로 foreach를 사용할 수 있음

  collection 속성은 DAO가 전달해주는 데이터들의 이름

  item 속성은 전달 받은 데이터들에서 첫 번째 데이터부터 마지막 데이터까지 차례대로 저장될 변수명

  open 속성은 반복문이 동작하기 전 추가될 문자열

  close 속성은 반복문이 동작한 후 추가될 문자열

  separator 속성은 n번째 반복이 끝난 후 추가될 문자열

 

만약 open, close 속성을 사용하지 않고 싶다면 다음과 같이 쿼리를 구성하면 됨

그러나 아래와 같이 구성하면 가독성이 많이 떨어진다는 걸 잘 알고 있을 것임

앞서 얘기했던 것처럼 꼭 이걸 써야된다 저걸 써야된다 는 없음

이런 저런 방법을 알고 있으면 내 상황에 맞는 코드를 작성할 수 있는 것

 

foreach의 특별한 점은 반복문이므로 특정 쿼리를 반복적으로 실행하도록 만들 수도 있음

INSERT 또는 UPDATE 또는 DELETE 를 굉장히 많이, 수천번 해야한다 면 아래와 같이 INSERT 쿼리문을 만들어 반복적으로 호출하면 될 것

 

<< 쿼리 >>

<?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="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 joinStudygroupMember(StudygroupMember studygroupMember);
	
	// ...
}

 

<< 컨트롤러 >>

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.PostMapping;

@Controller
public class StudygroupController {
	@Autowired
	private StudygroupDao studygroupDao;
	
	@PostMapping()
	public void joinStudygroupMember(List<StudygroupMember> joinMemberList) {
		for(StudygroupMember joinMember : joinMemberList) {
			studygroupDao.joinStudygroupMember(joinMember);
		}
	}
}

이와 같이 컨트롤러에서 반복적으로 쿼리를 호출해 특정 쿼리를 반복적으로 실행할 수 있지만 굉장히 많이, 수 천번 반복적으로 호출하게 되면 서버 전체에 부하가 굉장히 클 수 있음

 

이렇게 특정 쿼리를 굉장히 많이 실행해야 되는 상황을 벌크(Bulk) 라고함

INSERT 를 많이 실행해야되는 상황을 Bulk INSERT

UPDATE 를 많이 실행해야되는 상황을 Bulk UPDATE

DELETE 를 많이 실행해야되는 상황을 Bulk DELETE

 

이럴 때는 DAO가 쿼리쪽으로 대량의 데이터를 전달하고 쿼리에서는 foreach 를 사용해 실행할 쿼리들을 한번에 생성하는 방법이 있음

 

<< 컨트롤러 >>

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.PostMapping;

@Controller
public class StudygroupController {
	@Autowired
	private StudygroupDao studygroupDao;
	
	@PostMapping()
	public void joinStudygroupMember(List<StudygroupMember> joinMemberList) {
		studygroupDao.joinStudygroupMember(joinMemberList);
	}
}

<< 코드 설명 >>

전과 다르게 컨트롤러에서 DAO로 대량의 데이터들을 한번에 전달함

 

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface StudygroupDao {
	// ...
	
	void joinStudygroupMember(List<StudygroupMember> studygroupMembers);
	
	// ...
}

<< 코드 설명 >>

DAO는 대량의 데이터를 받아 쿼리로 대량의 데이터를 전달함

이때 쿼리로 전달하는 대량의 데이터 이름은 studygroupMembers 임

 

<?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="joinStudygroupMember" parameterType="com.study.chapter04.StudygroupMember">
		INSERT INTO studygroupMember(studygroupIdx, memberIdx)
	
		<foreach collection="studygroupMembers" item="studygroupMember" open="VALUES " separator=", " close=";">
			(#{studygroupMember.studygroupIdx}, #{studygroupMember.memberIdx})
		</foreach>
	</insert>
	
	// ...
</mapper>

<< 코드 설명 >>

쿼리에서는 전달 받은 대량의 데이터를 foreach로 반복하며 INSERT 쿼리를 구성함

 

위 insert 태그가 동작하면 다음과 같은 INSERT 코드가 만들어짐

INSERT INTO studygroupMember(studygroupIdx, memberIdx) VALUES (#{1번째 스터디그룹 idx}, #{1번째 사용자 idx}), (#{2번째 스터디그룹 idx}, #{2번째 사용자 idx}), (#{3번째 스터디그룹 idx}, #{3번째 사용자 idx}), ... , (#{n번째 스터디그룹 idx}, #{n번째 사용자 idx});

 

UPDATE, DELETE 또한 마찬가지로 foreach 를 사용하면 호출 한번으로 대량의 UPDATE, DELETE 가 되도록 할 수 있음

그러나 ! 앞서 언급했듯 "Bulk INSERT, UPDATE, DELETE 할 때 반드시 이 방법을 사용해야된다" 가아님

이 방법도 있다 임


이외에도 다른 예시를 보고 싶거나 더 자세한 사항들을 알고 싶다면 MyBatis 공식 홈페이지 ( https://mybatis.org/mybatis-3/ ) 에서 찾아보자

728x90
LIST

<< 학습 목표 >>

1. INSERT 한 데이터의 PK 값을 돌려받을 수 있다.


프로젝트의 규모가 커지면 하나의 처리를 하기 위해서 쿼리를 여러번 실행해야하는 경우가 자주 발생함

우리는 지금까지 단편적인 기술만 배웠으므로 그런일이 없었지만 곧 지금까지 배운 것들을 종합적으로 활용해서 하나의 프로젝트를 만들어볼 예정임

 

이렇게 하나의 처리를 하기 위해서 쿼리를 여러 번 실행해야할 때 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가 빠졌다는 점에 주목하자

728x90
LIST

<< 학습 목표 >>

1. 쿼리 실행 결과로 데이터를 받을 수 있다.

2. 쿼리 실행 결과로 정보를 받을 수 있다.

3. 쿼리 실행 결과로 데이터들을 받을 수 있다.

4. 쿼리 실행 결과로 정보들을 받을 수 있다.


지금까지 과정을 보면 먼저 MyBatis 를 사용해 쿼리를 실행하고 결과를 받아오는 방법에 대해서 간단하게 배웠음 ( https://codingaja.tistory.com/115 ) 그 후 쿼리에 사용할 값에 대해서 자세히 배웠음 ( https://codingaja.tistory.com/116 )

 

이번에는 쿼리를 실행한 후 결괏값을 받는 방법을 배워보자

 

1. SELECT 쿼리를 실행했을 때는 SELECT 쿼리에 맞는 결괏값을 얻을 수 있음

ex) SELECT COUNT(*) AS count FROM member; 쿼리를 실행했다면 결괏값으로 정수를 얻을 수 있음

SELECT * FROM member WHERE nickname = "홍길동"; 쿼리를 실행했다면 결괏값으로 닉네임이 홍길동인 회원의 정보를 얻을 수 있음

SELECT * FROM member; 쿼리를 실행했다면 결괏값으로 회원들의 정보를 얻을 수 있음

 

2. INSERT 쿼리를 실행했을 때는 결괏값으로 정수 1을 얻을 수 있음

3. UPDATE 쿼리를 실행했을 때는 UPDATE 쿼리로 인해 값이 바뀐 행의 개수(정수)를 얻을 수 있음

4. DELETE 쿼리를 실행했을 때는 DELETE 쿼리로 인해 삭제된 행의 개수(정수)를 얻을 수 있음


이제 SELECT 쿼리부터 시작해서 쿼리를 실행한 후 결괏값을 받는 방법을 알아보자

우선 전 글 ( https://codingaja.tistory.com/116 ) 에서 가장 마지막에 사용했던 Member5.xml, MemberDao5, MyBatisController5 파일들을 복사하자

복사한 파일의 이름은 Member6.xml, MemberDao6, MyBatisController6 으로 지정하자

 

 

1. SELECT 쿼리를 실행했을 때는 SELECT 쿼리에 맞는 결괏값을 얻을 수 있음

  1-1. 결괏값이 정수 하나 일 때

더보기

결괏값으로 정수 한 개만 반환하는 쿼리를 작성하자

<?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();
}

<< 코드 설명 >>

MemberDao6의 getMemberCount 메서드와 매치될 쿼리가 정수를 반환하므로 이 메서드의 반환 타입도 정수여야함

 

 

이번에는 컨트롤러를 만들자

package com.study.chapter04;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyBatisController6 {
	@Autowired
	private MemberDao6 memberDao;
	
	@GetMapping("/chapter04/mybatis/v6/member/count")
	public void getMemberCount(SelectMemberDto selectMemberDto) {
		int count = memberDao.getMemberCount();
		
		System.out.println("가입된 전체 회원의 수 => " + count);
	}

}

 

이와 같이 SELECT 쿼리의 실행 결과가 정수 하나라면 select 태그의 resultType을 _int 로 명시해야하고 해당 쿼리와 매칭될 DAO 메서드는 반환 타입이 int 여야함

 

또한 SELECT 쿼리의 실행 결과가 정수 하나 외에 실수 하나, 문자열 하나 등 데이터 하나라면 select 태그의 resultType 속성의 값으로 그 데이터에 맞는 타입 명을 명시해줘야함

그리고 DAO의 메서드는 반환 타입으로 데이터에 맞는 타입을 써줘야함


  1-2. 결괏값이 한 행(정보 하나) 일 때

더보기

결괏값으로 정보 한 개만 반환하는 쿼리를 작성하자

<?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 속성 값으로 명시

 

 

이번에는 쿼리와 매칭될 DAO에 메서드를 선언하자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao6 {
	// ...
    
	SelectMemberDto getMemberByNickname(String nickname);
    
	// ...
}

<< 코드 설명 >>

MemberDao6의 getMemberByNickname 메서드와 매치될 쿼리가 SelectMemberDto(회원 정보 하나)를 반환하므로 이 메서드의 반환 타입도 똑같이 SelectMemberDto여야함

 

 

이번에는 컨트롤러를 만들자

package com.study.chapter04;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyBatisController6 {
	@Autowired
	private MemberDao6 memberDao;
	
	// ...
	
	@GetMapping("/chapter04/mybatis/v6/member")
	public void getMemberByNickname(String nickname) {
		SelectMemberDto member = memberDao.getMemberByNickname(nickname);
		
		System.out.println("<< 닉네임이 [ " + nickname + " ] 인 회원의 정보 >>");
		System.out.println(member);
	}

	// ...
}

 

 

이와 같이 SELECT 쿼리의 실행 결과가 정보 하나라면 select 태그의 resultType을 정보를 받을 수 있는 DTO 로 명시해야하고 해당 쿼리와 매칭될 DAO 메서드의 반환 타입은 동일한 DTO로 지정해주면됨


  1-3. 결괏값이 여러 행(데이터들) 일 때

더보기

결괏값으로 문자열들(여러 행 / 데이터들)을 반환하는 쿼리를 작성하자

<?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="getAllMemberNickname" resultType="string">
		SELECT nickname FROM member;
	</select>
    
	// ...
</mapper>

<< 코드 설명 >>

(1). 이 쿼리를 실행하면 모든 회원들의 닉네임(문자열들)을 반환하므로 resultType은 string 으로 명시함

 

 

이번에는 쿼리와 매칭될 DAO에 메서드를 선언하자

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;


@Mapper
public interface MemberDao6 {
	// ...
    
	List<String> getAllMemberNickname();
 
	// ...
}

<< 코드 설명 >>

앞서 [ 결괏값이 정수 하나 일 때 ], [ 결괏값이 한 행(정보 하나) 일 때 ] 는 resultType과 DAO 메서드의 반환 타입을 맞춰줬는데 [ 결괏값이 여러 행(데이터들) 일 때 ] 는 resultType과 DAO 메서드의 반환 타입이 다름

 

결국 MyBatis는 SELECT nickname FROM member; 쿼리를 실행한 후 결과를 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 MyBatisController6 {
	@Autowired
	private MemberDao6 memberDao;
	
	// ...

	@GetMapping("/chapter04/mybatis/v6/member/nicknames")
	public void getAllMemberNickname() {
		List<String> nicknameList = memberDao.getAllMemberNickname();
		
		for(String nickname : nicknameList) {
			System.out.println(nickname);
		}
	}
    
	// ...
}

( 코드 설명이 필요 없을 정도로 간단하므로 코드 설명은 생략함 )

 

 

이와 같이 SELECT 쿼리의 실행 결과가 데이터들이라면 select 태그의 resultType은 데이터 타입으로 명시해야하고 해당 쿼리와 매칭될 DAO 메서드는 반환 타입이 List<데이터타입> 여야함


  1-3. 결괏값이 여러 행(정보들) 일 때

더보기

결괏값으로 회원들의 정보(여러 행 / 정보들)을 반환하는 쿼리를 작성하자

<?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="getAllMember" resultType="com.study.chapter04.SelectMemberDto">
		SELECT * FROM member;
	</select>
    
	// ...
</mapper>

 << 코드 설명 >>

이 쿼리를 실행하면 회원들의 정보를 반환하므로 resultType 속성 값은 회원 정보를 담을 수 있게 DTO로 명시해야함

 

 

이번에는 쿼리와 매칭될 DAO에 메서드를 선언하자

package com.study.chapter04;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao6 {
	// ...
    
	List<SelectMemberDto> getAllMember();
    
	// ...
}

<< 코드 설명 >>

앞서 [ 결괏값이 여러 행(데이터들) 일 때 ] 와 마찬가지로 resultType과 DAO 메서드의 반환 타입이 다름

 

결국 MyBatis는 SELECT * FROM member; 쿼리를 실행한 후 결과를 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 MyBatisController6 {
	@Autowired
	private MemberDao6 memberDao;
	
	// ...
	
	@GetMapping("/chapter04/mybatis/v6/members")
	public void getAllMember() {
		List<SelectMemberDto> memberList = memberDao.getAllMember();
		
		for(SelectMemberDto member : memberList) {
			System.out.println(member);
		}
	}
    
	// ...
}

( 코드 설명이 필요 없을 정도로 간단하므로 코드 설명은 생략함 )

 

 

이와 같이 SELECT 쿼리의 실행 결과가 정보들이라면 select 태그의 resultType은 정보를 담을 수 있는 DTO로 명시해야하고 해당 쿼리와 매칭될 DAO 메서드는 반환 타입이 List<DTO타입> 이여야함



2. INSERT 쿼리를 실행했을 때는 결괏값으로 정수 1을 얻을 수 있음

이번에는 INSERT 쿼리를 작성하고 결괏값을 받아 보자

 

아래와 같이 Member6.xml 파일에 INSERT 쿼리를 추가하자

<?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">
	// ...
	
	<insert id="joinMember" parameterType="com.study.chapter04.InsertMemberDto">
		INSERT INTO member(id, pw, nickname, tel) VALUES(#{id}, #{pw}, #{nickname}, #{tel});
	</insert>
    
	// ...
</mapper>

<< 코드 설명 >>

SELECT와 연계해서 생각해보면 특이한 점이 있음

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 로 지정하면 됨

728x90
LIST

<< 학습 목표 >>

1. 다양한 형태의 값을 쿼리에 사용할 수 있다.


전 글 ( https://codingaja.tistory.com/115 ) 에서 쿼리에 필요한 값들을 DAO의 매개변수로 넘겨 사용했었음

 

이번에는 간단하지만 파라미터를 사용하는 다섯 가지 방법을 배워보자

1. #{param1}, #{param2}, #{param3} ... 처럼 #{param숫자} 로 파라미터를 사용할 수 있음

2. #{0}, #{1}, #{2} ... 처럼 #{숫자} 로 파라미터를 사용할 수 있음

3. @Param 어노테이션을 사용해 #{ } 안에 사용할 파라미터 명을 직접 지정할 수 있음

4. 해시맵의 키를 #{ } 안에 사용할 수 있음

5. DTO의 멤버 변수명을 #{ } 안에 사용할 수 있음


여기서 사용할 테이블, 쿼리, DAO, 컨트롤러는 모두 전 글까지 사용했던 것들을 사용할 것임

테이블 : Member 테이블

쿼리 : Member.xml 파일 내 INSERT, SELECT

컨트롤러 : MyBatisController

 

전 글에서는 파라미터를 사용하는 가장 나은 방법인 DTO를 사용했지만 이번에는 다양한 방법을 배우는 것이므로 1 ~ 4번 방법을 배울 때는 DTO가 빠짐


방법1. #{param1}, #{param2}, #{param3} ... 처럼 #{param숫자}

 

1. Member.xml 파일을 복사하고 파일명은 Member1 로 지정하자

 

Member1.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.MemberDao1">
	<insert id="insertMember">
		INSERT INTO member(id, pw, nickname, tel) VALUES(#{param1}, #{param2}, #{param3}, #{param4});
	</insert>

	<select id="selectMember" resultType="com.study.chapter04.SelectMemberDto">
		SELECT * FROM member WHERE nickname = #{param1};
	</select>
</mapper>

<< 코드 설명 >>

Member1.xml 파일 내 쿼리는 INSERT 쿼리(2)와 SELECT 쿼리(3)임

파라미터를 사용하는 방법이므로 SELECT 쿼리는 파라미터가 있는 닉네임으로 회원 정보 조회 SELECT 쿼리임

 

여기서 주의할 점은 이 쿼리 파일과 연결될 DAO는 com.study.chapter04 패키지 내 MemberDao1 임(1)

 

 

2. 쿼리 파일과 연결될 DAO를 추가하자

  DAO도 간단하게 복사하자

  com.study.chapter04 패키지 내 MemberDao 를 복사하고 인터페이스명은 MemberDao1 로 지정하자

 

MemberDao1 내 코드를 아래와 같이 바꾸자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao1 {
	void insertMember(String id, String pw, String nickname, String tel);
	SelectMemberDto selectMember(String nickname);
}

<< 코드 설명 >>

- 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>

 

2. MemberDao1 DAO를 복사하고 인터페이스명을 Member2 로 지정하자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao2 {
	void insertMember(String id, String pw, String nickname, String tel);
	SelectMemberDto selectMember(String nickname);
}


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>

 

2. MemberDao1 DAO를 복사하고 인터페이스명을 Member3 로 지정하자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface MemberDao3 {
	void insertMember(@Param("newMemberId") String id, @Param("newMemberPw")String pw, @Param("_nickname") String nickname, @Param("TEL") String tel);
	SelectMemberDto selectMember(@Param("nickname") String nickname);
}


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>

 

2. MemberDao1 DAO를 복사하고 인터페이스명을 Member4 로 지정하자

package com.study.chapter04;

import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao4 {
	void insertMember(Map<String, String> newMember);
	SelectMemberDto selectMember(Map<String, String> filter);
}


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>

 

2. MemberDao1 DAO를 복사하고 인터페이스명을 Member5 로 지정하자

package com.study.chapter04;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberDao5 {
	void insertMember(InsertMemberDto newMemberDto);
	SelectMemberDto selectMember(SelectMemberDto memberDto);
}


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 ] 으 명시

이 표는 외울 필요는 없음

그리고 어떻게 활용하는지에 대해서는 곧 배우게 될 것

728x90
LIST