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>
개인 공부 기록용입니다:)
'· LANGUAGE > └ Java' 카테고리의 다른 글
[ Java ] 내장객체로 비.암(비밀번호 암호화 하기) (0) | 2024.06.25 |
---|---|
[ Java ] MimeMessage - 이메일 전송하기 (0) | 2024.06.25 |
[ Java ] 스프링에서 이메일 보내기 (JavaMailSenderImpl / MimeMessage) (0) | 2024.06.19 |
[ Java ] swagger(springdoc) 사용하기 (+예제 실습) (0) | 2024.05.12 |
[ Java / Database ] 최종 로그인 시간 기록하기 (0) | 2024.03.19 |