[ Java / Mybatis ] Mybatis 사용해보기

2024. 6. 22. 13:40· LANGUAGE/└ Java

환경 : Spring Tools Suite4

 

[ MyBatis ]

MyBatis는 데이터베이스를 쉽게 다룰 수 있도록 도와주는 ORM(Object-Relational Mapping) 프레임워크로, 개발 시 도움을 주는 라이브러리다. 

 

(이전에 내가 기록해왔던..) 글들과는 달리 mapper가 필요 없어지고 dto가 짧아진다. (변수 선언만 해주면 됨.)

또한, sql문의 띄어쓰기를 신경쓰지 않아도 돼서 편핟. 그냥 홀더를 쓰던 부분에 정적/동적 배치에 따라 사용하는 기호가 달라질 뿐이다. 

 

 

 정적 배치(계속 같은 값이 들어가는 경우) : $ 사용

 동적 배치(계속 다른 값이 들어가는 경우) : # 사용

 

 

MyBatis 사용 방법순서 : 

의존성 설정 -> DB 설정 -> MyBatis 설정 -> Mapper 인터페이스 작성 -> XML 작성 -> MyBatis 사용


개념은 이렇고.. 이걸 사용해보자...

사용하면서 이해해 나가보자.. 이해할 수 있자나....

 

 

먼저 원래 사용하던 DAO, DTO, MAPPER의 개념이 있어야 mybatis를 이해하여 사용할 수 있다

간단히 pocketmon 테이블을 활용하여 사용해보자.

 

 

 

   - PocketmonDto.java

import lombok.Data;

@Data //toString+Getter+Setter
public class PocketmonDto {
	private int pocketmonNo;
	private String pocketmonName;
	private String pocketmonType;
}

    (+) 변수선언 및  어노테이션(@Data) 선언으로 Dto 준비가 끝남.

 

   - PocketmonDao.java

@Repository
public class PocketmonDao {

	@Autowired
	private SqlSession sqlSession;//myBatis
	
	public List<PocketmonDto> selectList(){
		//PocketmonDto 영역의 list라는 구문을 실행해서 나온 결과를 반환해라
        	//(주의) 구문을 적고 뒤에 데이터는 최대 1개밖에 전달할 수 없다
		return sqlSession.selectList("pocketmon.list");
	}
	
	public void insert(PocketmonDto pocketmonDto) {
		//dto로 가지고 있는 정보들을 일일히 풀어서 홀더로 매칭해줄 필요가 없음!! 개편해요
		sqlSession.insert("pocketmon.add", pocketmonDto);
	}
	
	public boolean update(PocketmonDto pocketmonDto) {
		return sqlSession.update("pocketmon.edit", pocketmonDto) > 0;
	}
	
	public boolean delete(int pocketmonNo) {
		return sqlSession.delete("pocketmon.remove", pocketmonNo) > 0;
	}
	
	//상세조회를 구현하는 방법은 두 가지
	//1. selectList를 사용해서 목록으로 처리 - 여러개 나와도 에러가 안 남(X)
	//2. selectOne을 사용해서 상세조회 처리 - 여러개 나오면 에러가 발생(O)
	public PocketmonDto selectOne(int pocketmonNo) {
		//1
		/*
		List<PocketmonDto> list = sqlSession.selectList("pocketmon.find", pocketmonNo);
		return list.isEmpty() ? null : list.get(0);
		*/
		
		//2
		return sqlSession.selectOne("pocketmon.find", pocketmonNo);
	}
}

 

 

    - pocketmon-mapper .xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="pocketmon">
	
	<!-- resultType은 조회 결과의 형태를 의미하며 select에만 작성 -->
	<select id="list" resultType="PocketmonDto">
		select * from pocketmon
	</select>
	
	<insert id="add">
		insert into pocketmon (
			pocketmon_no, pocketmon_name, pocketmon_type
		)
		values ( #{pocketmonNo}, #{pocketmonName}, #{pocketmonType})
	</insert>
	
	<update id="edit">
		update pocketmon
		set 
			pocketmon_name=#{pocketmonName}, 
			pocketmon_type=#{pocketmonType}
		where pocketmon_no=#{pocketmonNo}
	</update>
	
	<delete id="remove">
		delete pocketmon where pocketmon_no = #{pocketmonNo}
	</delete>
	
	<!-- 
		상세(단일)조회
		- 구문은 목록 때와 동일하게 생성
		- 차이는 호출할 때 selectList로 부르냐, selectOne으로 부르냐의 차이
	-->
	<select id="find" resultType="PocketmonDto">
		select * from pocketmon where pocketmon_no = #{pocketmonNo}
	</select>
	
</mapper>

 

 

    - PocketmonSelectTest01.java

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootTest
public class PocketmonSelectTest01 {

	@Autowired
	private PocketmonDao pocketmonDao;
	
	@Test
	public void test() {
		List<PocketmonDto> list = pocketmonDao.selectList();
		log.debug("결과 수 : {}",list.size());
	}
}

 

 

이게 기본 흐름이라면.. 이제 나올 예제는 mybatis를 더 화려하게 쓰는.. 방법이다.. 


   - memberDto.java 

import java.sql.Date;
import lombok.Data;

@Data //Setter+Getter+toString
public class MemberDto {
	private String memberId;
	private String memberPw;
	private String memberNick;
	private String memberBirth;
	private String memberContact;
	private String memberEmail;
	private String memberPost;
	private String memberAddress1;
	private String memberAddress2;
	private String memberLevel;
	private int memberPoint;
	private Date memberJoin;
	private Date memberLogin;
}

 

 

   - member-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="member">

	<!-- 
		조회
		- 기본검색(type, keyword)
		- 복합검색
		- 계층형검색
	-->
	
	<select id="list" resultType="MemberDto">
		select * from member
	</select>
	
	<!-- 
		column은 정적배치이므로 $ 사용, keyword는 동적배치이므로 # 사용
	-->
	<select id="search" resultType="MemberDto">
		select * from member where instr(${column}, #{keyword}) > 0 
		order by ${column} asc, member_id asc
	</select>
	
	<!-- column, keyword 유무에 따라 목록 또는 검색을 실행 -->
	<select id="listOrSearch" resultType="MemberDto">
		select * from member
		<if test="column != null and keyword != null">
			where instr(${column}, #{keyword}) > 0
			order by ${column} asc, member_id asc
		</if>
	</select>
	
	<!-- 
		복잡한 검색(complex search)
		- 테이블의 각 항목별로 특징에 맞게 검색하도록 구현
		- 유사 검색, 일치 검색, 구간 검색 중 적절한 것을 선택
	-->
	<select id="complex" resultType="MemberDto">
		select * from member
		<where>
			<if test="memberId != null"> <!-- 아이디(포함검사) -->
				and member_id like #{memberId} || '%'
			</if>
			<if test="memberNick != null"> <!-- 닉네임(포함검사) -->
				and instr(member_nick, #{memberNick}) > 0
			</if>
			<if test="memberContact != null"> <!-- 연락처(일치검사) -->
				and member_contact = #{memberContact}
			</if>
			<choose> <!-- 포인트(구간검사) -->
				<when test="minPoint != null and maxPoint != null"> <!-- 둘 다 있으면 -->
					and member_point between #{minPoint} and #{maxPoint}				
				</when> 
				<when test="minPoint != null"> <!-- 최소 값이 있으면 -->
					<!-- XML에서 불가능한 글자를 사용할 경우 CDATA 영역을 만들어 해결 -->
					<![CDATA[
						and member_point >= #{minPoint}
					]]>
				</when>
				<when test="maxPoint != null"> <!-- 최대 값이 있으면 -->
					<![CDATA[
						and member_point <= #{maxPoint}					
					]]>
				</when>
			</choose>
		</where>
	</select>
	
</mapper>

 

 

(설명 추가)

<!-- 
    column은 정적배치이므로 $ 사용, keyword는 동적배치이므로 # 사용
-->
<select id="search" resultType="MemberDto">
    select * from member where instr(${column}, #{keyword}) > 0 
    order by ${column} asc, member_id asc
</select>

    (+) 정적 배치 : $ / 동적 배치 : #

         계속 값이 바뀌는 경우 #을 사용한다.

<choose> <!-- 포인트(구간검사) -->
    <when test="minPoint != null and maxPoint != null"> <!-- 둘 다 있으면 -->
        and member_point between #{minPoint} and #{maxPoint}				
    </when> 
    <when test="minPoint != null"> <!-- 최소 값이 있으면 -->
        <!-- XML에서 불가능한 글자를 사용할 경우 CDATA 영역을 만들어 해결 -->
        <![CDATA[
            and member_point >= #{minPoint}
        ]]>
    </when>
    <when test="maxPoint != null"> <!-- 최대 값이 있으면 -->
        <![CDATA[
            and member_point <= #{maxPoint}					
        ]]>
    </when>
</choose>

   (+) HTML과 거의 흡사한 명령어를 가지고 있다. (<choose></choose>, <when></when>, <if></if>)

   (+) 만약 SQL문에 부등호와 같이 XML에서 불가능한 글자를 사용할 경우 CDATA 영역을 만들어 사용한다

 

 

 

 

 

 위의 복합검색 코드가 제대로 돌아가는지 테스트를 진행 해보자

   - ComplexSearchTest04.java [전체코드]

@Slf4j
@SpringBootTest
public class ComplexSearchTest04 {

	@Autowired
	private SqlSession sqlSession;
	
	@Test
	public void test() {
		//상황 1. 아무런 정보도 없을 시
		Map<String, Object> data1 = new HashMap<>();
		
		List<MemberDto> list1 = sqlSession.selectList("member.complex", data1);
		for(MemberDto memberDto : list1) {
			log.debug("member = {}", memberDto);
		}
		
		//상황 2. memberId를 넣었을 때
		Map<String, Object> data2 = new HashMap<>();
		data2.put("memberId", "test");
		
		List<MemberDto> list2 = sqlSession.selectList("member.complex", data2);
		for(MemberDto memberDto : list2) {
			log.debug("member = {}", memberDto);
		}
		
		//상황 3. memberNick을 넣었을 때
		Map<String, Object> data3 = new HashMap<>();
		data3.put("memberNick", "테스트");
		
		List<MemberDto> list3 = sqlSession.selectList("member.complex", data3);
		for(MemberDto memberDto : list3) {
			log.debug("member = {}", memberDto);
		}
		
		//상황 4. 둘 다 넣었을 때
		Map<String, Object> data4 = new HashMap<>();
		data4.put("memberId", "test");
		data4.put("memberNick", "테스트");
		
		List<MemberDto> list4 = sqlSession.selectList("member.complex", data4);
		for(MemberDto memberDto : list4) {
			log.debug("member = {}", memberDto);
		}
		
	}
	
}

 

    (테스트 1. 아무런 정보도 없을 경우)

//상황 1. 아무런 정보도 없을 시
Map<String, Object> data1 = new HashMap<>();

List<MemberDto> list1 = sqlSession.selectList("member.complex", data1);
for(MemberDto memberDto : list1) {
    log.debug("member = {}", memberDto);
}

출력결과

 

    (테스트 2. memberId를 넣은 경우)

//상황 2. memberId를 넣었을 때
Map<String, Object> data2 = new HashMap<>();
data2.put("memberId", "test");

List<MemberDto> list2 = sqlSession.selectList("member.complex", data2);
for(MemberDto memberDto : list2) {
    log.debug("member = {}", memberDto);
}

출력결과

 

    (테스트 3. memberNick을 넣은 경우)

//상황 3. memberNick을 넣었을 때
Map<String, Object> data3 = new HashMap<>();
data3.put("memberNick", "테스트");

List<MemberDto> list3 = sqlSession.selectList("member.complex", data3);
for(MemberDto memberDto : list3) {
    log.debug("member = {}", memberDto);
}

 

    (테스트 4. memberId, memberNick 둘 다 넣은 경우)

//상황 4. 둘 다 넣었을 때
Map<String, Object> data4 = new HashMap<>();
data4.put("memberId", "test");
data4.put("memberNick", "테스트");

List<MemberDto> list4 = sqlSession.selectList("member.complex", data4);
for(MemberDto memberDto : list4) {
    log.debug("member = {}", memberDto);
}

 

테스트마다 호출되는 SQL문이 다르다!!

 

 


[ 있을 수 있는 여러 조건들.... ]

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="member">

	<!-- 
		조회
		- 기본검색(type, keyword)
		- 복합검색
		- 계층형검색
	-->
	
	<select id="list" resultType="MemberDto">
		select * from member
	</select>
	
	<!-- 
		column은 정적배치이므로 $ 사용, keyword는 동적배치이므로 # 사용
	-->
	<select id="search" resultType="MemberDto">
		select * from member where instr(${column}, #{keyword}) > 0 
		order by ${column} asc, member_id asc
	</select>
	
	<!-- column, keyword 유무에 따라 목록 또는 검색을 실행 -->
	<select id="listOrSearch" resultType="MemberDto">
		select * from member
		<if test="column != null and keyword != null">
			where instr(${column}, #{keyword}) > 0
			order by ${column} asc, member_id asc
		</if>
	</select>
	
	<!-- 
		복잡한 검색(complex search)
		- 테이블의 각 항목별로 특징에 맞게 검색하도록 구현
		- 유사 검색, 일치 검색, 구간 검색 중 적절한 것을 선택
	-->
	<select id="complex" resultType="MemberDto">
		select * from member
		<where>
			<!-- 아이디(포함검사) -->
			<if test="memberId != null"> 
				and member_id like #{memberId} || '%'
			</if>
			<!-- 닉네임(포함검사) -->
			<if test="memberNick != null">
				and instr(member_nick, #{memberNick}) > 0
			</if>
			<!-- 연락처(일치검사) -->
			<if test="memberContact != null">
				and member_contact = #{memberContact}
			</if>
			<!-- 포인트(구간검사) -->
			<choose>
				<when test="minPoint != null and maxPoint != null"> <!-- 둘 다 있으면 -->
					and member_point between #{minPoint} and #{maxPoint}				
				</when> 
				<when test="minPoint != null"> <!-- 최소 값이 있으면 -->
					<!-- XML에서 불가능한 글자를 사용할 경우 CDATA 영역을 만들어 해결 -->
					<![CDATA[
						and member_point >= #{minPoint}
					]]>
				</when>
				<when test="maxPoint != null"> <!-- 최대 값이 있으면 -->
					<![CDATA[
						and member_point <= #{maxPoint}					
					]]>
				</when>
			</choose>

			<!-- 
				(Q) YYYY-MM-DD 형식의 날짜로 가입일 검색(memberJoin)
			-->	
			<if test="loginDate != null">
				and to_char(member_join, 'YYYY-MM-DD') = #{memberJoin}
			</if>
			
			<!-- 
				(Q) YYYY-MM-DD 형식의 날짜 2개로 로그인 일자 검색(minLoginDate, maxLoginDate)
					* minLoginDate는 0시 0분 0초로 설정해야 하고
					* maxLoginDate는 23시 59분 59초로 설정해야 한다
			-->		
			<choose>
				<when test="minLoginDate != null and maxLoginDate != null">
					and member_login is not null 
					and member_login between 
						to_date(#{minLoginDate} || ' ' || '00:00:00', 'YYYY-MM-DD HH24:MI:SS') 
						and 
						to_date(#{maxLoginDate} || ' ' || '23:59:59', 'YYYY-MM-DD HH24:MI:SS')
				</when>
				<when test="minLoginDate != null">
					<![CDATA[
						and member_login is not null
						and member_login >=
							to_date(#{minLoginDate} || ' ' || '00:00:00', 'YYYY-MM-DD HH24:MI:SS')
					]]>
				</when>
				<when test="maxLoginDate != null">
					<![CDATA[
						and member_login is not null
						and member_login <= 
							to_date(#{maxLoginDate} || ' ' || '23:59:59', 'YYYY-MM-DD HH24:MI:SS')
					]]> 
				</when>
			</choose>
			
			<!-- 회원등급 : 여러 개 선택 가능하도록 구현 (memberLevelList) -->		
			<if test="memberLevelList != null and memberLevelList.size() > 0">
				and member_level in 
				<foreach collection="memberLevelList" item="memberLevel"
							open="(" separator="," close=")">
					#{memberLevel}
				</foreach>
			</if>
		</where>
	</select>
	
</mapper>

 

 

 

 

 

개인 공부 기록용입니다:)

참고 : https://ccomccomhan.tistory.com/130

728x90