관리 메뉴

거니의 velog

(14) 마이바티스 프레임워크 사용하기 5 본문

Java_Spring Framework part1

(14) 마이바티스 프레임워크 사용하기 5

Unlimited00 2023. 11. 10. 19:33

5. 마이바티스의 동적 SQL문 사용하기

* 이번에는 마이바티스의 동적 SQL 기능이 왜 등장했는지 그리고 어떻게 사용하는지 알아보자. 다음 첫 번째 SQL문은 아무 조건 없이 모든 회원 정보를 조회하는 SQL문이다. 두 번째와 세 번째 SQL문은 첫 번째 SQL 문에 대해, 두 번째는 ID로, 세 번째는 ID와 비밀번호를 조건절로 하여 회원 정보를 조회하는 SQL문이다.

(1)
select * from t_member;

(2)
select * from t_member
where
id = 'hong';

(3)
select * from t_member
where
id = 'hong'
and pwd = '1234';

* 이 SQL문들은 select문에 대해 각각의 조건절에 따라 각각의 SQL문을 따로 작성해야 한다. 그런데 세 개의 SQL 문을 분석해 보면 조건절만 다르지 동일한 select문을 사용하고 있다. 그러면 세 개의 SQL문을 일일히 만드는 것보다는 마이바티스의 동적 SQL 기능을 이용하면 각 조건절에 대해 한 개의 SQL문으로 구현할 수 있어 더 편리하다. 즉, 공통 SQL문에 대해 조건값의 유무에 따라 동적으로 공통 SQL 문에 조건절을 추가하는 것이다. 이것이 바로 동적 SQL문이 등장하게 된 배경이다.

* 동적 SQL문은 주로 where 절을 동적으로 추가한다. 그리고 마이바티스의 동적 SQL문에 사용되는 태그들은 JSP의 JSTL에서 사용되는 코어 태그들과 유사하다.

* 마이바티스의 동적 SQL문의 특징은 다음과 같다.

1. SQL 문의 조건절에서 사용한다.

2. 조건절(where)에 조건을 동적으로 추가한다.

3. JSTL과 XML 기반으로 동적 SQL문을 작성한다.
* 동적 SQL문을 구성하는 요소들은 다음과 같다.

1. if

2. choose(when, otherwise)

3. trim(where, set)

4. foreach

(1) <if> 태그로 동적 SQL문 만들기

* 동적 SQL 문을 작성할 때 각 조건에 대해 동적으로 SQL문에 조건식을 추가해 주는 <if> 태그에 대해 알아보자. <if> 태그는 <where> 태그 안에서 사용된다. <where> 태그는 <if> 태그에 따라 조건식이 존재하면 공통 SQL문에 where 절을 추가한다. 반대로 조건식이 없으면 추가하지 않는다.

* <if> 태그는 JSTL의 <c:if> 코어 태그의 사용법과 같다.

<where>
    <if test="조건식">
    	추가할 구문
    </if>
</where>

1. 다음과 같이 실습 파일을 준비한다.

2. member.xml에 모든 회원 정보를 조회하는 select 문에 대해 <where> 를 이용한 조건절을 다음과 같이 추가한다. 첫 번째 <if> 태그는 parameterType 속성의 memberVO의 name 속성으로 전달된 값이 있으면 select 문에 이름으로 조회하는 내용이다. 두 번째 <if> 태그는 parameterType 속성의 memberVO의 email 속성으로 전달된 값이 있으면 select문에 이메일로 조회하는 내용이다.

	<!-- 동적 SQL문 -->
	<select id="searchMember" parameterType="memberVO" resultMap="memResult">
		<![CDATA[
			select * from t_member 
		]]> <!-- 공통 SQL 문이다. -->
		<where> <!-- where 태그를 이용해 SQL 문의 where 절을 구성한다. -->
			<if test=" name != '' and name != null"> <!-- name 속성 값을 체크해 공백이 아니거나 null 이 아니면 'name=name 속성 값'조건절을 공통 SQL문 뒤에 추가한다. -->
				name = #{name}
			</if>
			<if test="email != '' and email != null "> <!-- email 속성 값을 체크해 공백과 null이 아니면 'email=email 속성 값' 구문을 공통 SQL문 뒤에 추가한다. -->
				and email = #{email}
			</if>
		</where>
		order by joinDate desc
	</select>

3. MemberServlet 클래스를 다음과 같이 작성한다. 브라우저에서 action 값 searchMember를 전송할 때 검색창에 입력한 name과 email 값을 가져옵니다. 그런 다음 MemberDAO의 searchMember() 메서드를 호출하면서 SQL문의 조건 값으로 전달한다.

      }else if(action.equals("searchMember")){
          String name = request.getParameter("name");
          String email = request.getParameter("email"); // 검색창에 입력한 검색 조건을 가져온다.
          memberVO.setName(name);
          memberVO.setEmail(email);
          List<MemberVO> membersList =dao.searchMember(memberVO);
          request.setAttribute("membersList",membersList);
          nextPage="test03/listMembers.jsp";

4. MemberDAO 클래스를 다음과 같이 작성한다. 서블릿에서 전달된 이름과 이메일을 MemberVO 객체의 각 속성에 저장한 후 다시 SqlSession 클래스의 selectList() 메서드를 호출하면서 SQL문으로 전달한다.

    public List<MemberVO> searchMember(MemberVO  memberVO){
        sqlMapper = getInstance();
        SqlSession session = sqlMapper.openSession();
        List list = session.selectList("mapper.member.searchMember", memberVO); // 회원 검색창에서 전달된 이름과 나이 값을 memberVO에 설정하여 SQL문으로 전달한다.
        return list;		
    }

5. searchMember.jsp에서는 검색 조건을 입력하고 <hidden> 태그와 action 값을 searchMember로 설정해 서블릿으로 전송한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>회원 검색창</title>
</head>

<body>
    <h1>회원검색</h1>
    <form action="${contextPath}/mem4.do">
        <input type="hidden" name="action" value="searchMember" /> <!-- hidden 태그를 이용해 서블릿으로 action 값을 전달한다. -->
		이름 : <input type="text" name="name" /><br>
		이메일 : <input type="text" name="email" /><br>
        <input type="submit" value="검색" />
    </form>
</body>

</html>

6. 다음의 주소로 요청하여 이름과 이메일로 조회한다. 그러면 이름과 이메일을 동시에 만족하는 회원 정보를 출력한다.

- http://localhost:8090/pro23/test03/searchMember.jsp

7. 이번에는 이름으로만 조회해 보자. 그러면 이름에 해당하는 회원 정보를 출력한다.

8. 마찬가지로 이메일로만 조회하면 이메일에 해당하는 모든 회원 정보를 표시한다.

9. 마지막으로 회원 검색창에 아무것도 입력하지 않고 검색을 클릭하면 모든 회원 정보를 조회한다.


(2) <choose> 태그로 동적 SQL문 만들기

* 이번에는 <choose> 태그를 이용해 회원 정보를 조회해 보자. <choose> 태그는 SQL문에서 자바의 switch 문과 같은 기능을 구현한다고 생각하면 된다. 이 때 <otherwise> 태그는 생략할 수 있다.

<choose>
    <when test="조건식1">
    	구문 1
    </when>
    <when test="조건식2">
    	구문 2
    </when>
    ...
    <otherwise>
    	구문 n+1;
    </otherwise>
</choose>

* <c:choose> 태그를 이용해 이름과 이메일로 회원을 조회하는 SQL문을 작성한다.

[member.xml]

	<!-- SQL문 id를 searchMember로 지정한다. -->
	<select id="searchMember" parameterType="memberVO" resultMap="memResult">
		<![CDATA[
			select * from t_member 
		]]>
	  
		<where>
			<choose>
				<when test="name != '' and name != null and  email != '' and email != null"> <!-- name과 email 속성 값이 모두 있는 경우 'name=name 속성 값 and email=email 속성 값' 조건식을 where 절에 추가한다. -->
					name = #{name} and email = #{email}
				</when>
				<when test="name != '' and name != null"> <!-- name 속성 값만 있을 경우 'name=name 속성 값'조건식을 where 절에 추가한다. -->
					name = #{name}
				</when>
				<when test="email !='' and email != null"> <!-- email 속성 값만 있을 경우 'email=email 속성 값'조건식을 where 절에 추가한다. -->
					email = #{email}
				</when>
			</choose>
		</where>
		order by joinDate desc
	</select>

* 실행 결과는 <if> 태그를 사용했을 때와 같다.


(3) <foreach> 태그로 회원 정보 조회하기

* <foreach> 태그를 이용하면 SQL문에서 한 번에 여러 개의 데이터를 처리할 수 있다. 사용법은 다음과 같다.

<foreach item="item" collection="list" index="index" open="(" separator="," close=")" >
	#{item}
</foreach>

* 다음 표는 <foreach> 태그의 여러 가지 속성을 정리한 것이다.

< foreach 태그에 관련된 속성들 >

속성 설명
collection 전달받은 인자 값을 의미하며, 배열과 List 계열 인스턴스를 전달할 수 있다. List 인스턴스 전달 시에는 list로 표시하고 배열 전달 시에는 array로 표시한다.
index foreach 문이 반복될 때마다 1씩 증가시키면서 접근하는 값의 위치를 나타낸다. 최초의 값의 위치는 0이다.
item 반복문이 실행될 때마다 collection 속성에 지정된 값에 접근하여 차례대로 사용한다.
open 해당 구문이 시작될 때의 지정한 기호를 추가한다.
close 해당 구문이 끝날 때의 지정한 기호를 추가한다.
separator 한 번 이상 반복될 때 반복되는 사이에 지정한 기호를 추가한다.

* 그럼 <foreach> 태그를 이용해 회원 정보를 조회해 보자.

1. 다음과 같이 member.xml을 작성하여 SQL문으로 Map 데이터가 전달되면 <foreach> 태그로 Map 데이터의 값을 반복해서 접근한 후 in 조건절에 조건 값으로 추가한다.

	<select id="foreachSelect" resultMap="memResult" parameterType="java.util.Map">
		<![CDATA[
			select * from t_member 
		]]>
      
		where name in
		<foreach item="item" collection="list" open="(" separator="," close=")" > <!-- SQL문에 List 인스턴스나 배열을 전달하면 자동으로 Map에 전달되어 이름을 키(key)로 사용한다. List 인스턴스는 list를 키로 사용하고 배열은 array를 키로 사용한다. -->
			#{item}
		</foreach> <!-- foreach 문을 이용해 반복해서 list의 값을 표시한다. -->
		order by joinDate desc
   </select>

2. MemberServlet 클래스에서는 브라우저에 action 값 foreachServlet으로 요청하면 ArrayList에 회원 이름을 저장하여 SQL문으로 전달한다.

       }else if(action.equals("foreachSelect")) {
		  List<String> nameList = new ArrayList<String>();
		  nameList.add("홍길동");
		  nameList.add("차범근");
		  nameList.add("이순신");
		  List<MemberVO> membersList=dao.foreachSelect(nameList); // ArrayList에 검색할 이름을 저장한 후 SQL문으로 ArrayList를 전달한다.
		  request.setAttribute("membersList",membersList);
		  nextPage="test03/listMembers.jsp";

3. MemberDAO 클래스에서는 이름이 저장된 ArrayList를 다시 SqlSession의 selectList() 메서드를 호출하면서 SQL문으로 전달한다.

    public List<MemberVO> foreachSelect(List nameList){
        sqlMapper = getInstance();
        SqlSession session = sqlMapper.openSession();
        List list = session.selectList("mapper.member.foreachSelect", nameList); // 검색 이름이 저장된 nameList를 SQL문으로 전달한다.
        return list;
    }

4. 다음의 주소로 요청하여 결과를 확인한다.

- http://localhost:8090/pro23/mem4.do?action=foreachSelect


(4) <foreach> 태그로 회원 정보 추가하기

* MySQL과 달리 오라클에서는 insert 문을 동시에 여러 개 사용하면 오류가 발생한다. 따라서 오라클에서는 <foreach> 태그의 open과 close 속성에 SQL문을 설정한 후 서브 쿼리 형식으로 다중 insert 문을 구현한다. 이처럼 <foreach> 태그를 이용하면 여러 정보를 한꺼번에 테이블에 추가할 수 있다.

1. member.xml을 다음과 같이 작성한다.

   <!-- foreach로 반복 작업을 할 때는 처음에 INSERT ALL을 추가한다. -->
   <!-- foreach로 반복 작업이 끝난 후 SELECT * FROM DUAL 을 마지막에 추가한다. -->
   <insert id="foreachInsert"  parameterType="java.util.Map">
      <foreach item="item" collection="list" open="INSERT ALL" separator=" " close="SELECT * FROM DUAL" >
          INTO  t_member(id, pwd, name, email)
          VALUES  (#{item.id},
                    #{item.pwd},
                    #{item.name},
                    #{item.email})
      </foreach>
   </insert>

2. 서블릿에서는 브라우저에서 전송된 action 값 foreachInsert에 대해 세 명의 회원 정보를 memList에 저장한 후 SQL문으로 전달하도록 구현한다.

	   }else if(action.equals("foreachInsert")) {
          List<MemberVO> memList = new ArrayList<MemberVO>();
          memList.add(new MemberVO("m1", "1234", "박길동", "m1@test.com"));
          memList.add(new MemberVO("m2", "1234", "이길동", "m2@test.com"));
          memList.add(new MemberVO("m3", "1234", "김길동", "m3@test.com")); // 테이블에 추가할 회원 정보를 memList에 저장한다.
          int result=dao.foreachInsert(memList); // SQL 문으로 memList를 전달한다.
          nextPage="/mem4.do?action=listMembers";

3. MemberDAO 클래스에서는 서블릿에서 회원 정보로 설정된 MemberVO 객체를 저장한 memList를 전달받는다. 그리고 이를 다시 SqlSession의 insert() 메서드로 전달한다.

     public int foreachInsert(List memList){
        sqlMapper = getInstance();
        SqlSession session = sqlMapper.openSession();
        int result = session.insert("mapper.member.foreachInsert", memList); // 회원 정보가 저장된 memList를 SQL문으로 전달한다.
        session.commit(); // 반드시 commit() 을 호출한다.
        return result ;		
     }

4. 다음의 주소로 요청하면 다음과 같이 박길동, 이길동, 김길동 이렇게 세 명의 회원 정보가 한꺼번에 추가된 것을 볼 수 있다.

- http://localhost:8090/pro23/mem4.do?action=foreachInsert


(5) <sql> 태그와 <include> 태그로 SQL문 중복 제거하기

* 마이바티스에서는 <sql> 태그를 제공하여 매퍼 파일에서 SQL문을 재사용할 수 있게 해준다.

   <sql id="a">
    <![CDATA[
      select * from t_member
     ]]> 
   </sql> <!-- sql 태그를 이용해 공통 SQL 문의 refid를 a로 지정한다. -->
  
  <select id="searchMember" parameterType="memberVO" resultMap="memResult">
	   <include refid="a" /> <!-- include 태그를 이용해 공통 SQL문을 재사용한다. -->
	  
       <where>
		  <choose>
		      <when test="name != '' and name != null and  email != '' and email != null">
			     name=#{name} and email=#{email}
		      </when>
		      <when test="name != '' and name != null">
			     name = #{name}
		      </when>
		      <when test="email !='' and email != null">
			    email = #{email}
		      </when>
	      </choose>
       </where>
       order by joinDate desc
   </select>
   
   <select id="foreachSelect" resultMap="memResult" parameterType="java.util.Map">
     <include refid="a" /> <!-- include 태그를 이용해 공통 SQL문을 재사용한다. -->
      
      where name in
      <foreach item="item" collection="list" open="(" separator="," close=")" >
         #{item}
      </foreach>
      order by joinDate desc
   </select>

* 매퍼 파일에서 공통 SQL문에 <sql> 태그를 적용해 refid를 할당하였다. 그런 다음 다른 SQL문에서 <include> 태그의 refid에 미리 설정한 공통 SQL문의 id를 설정하여 편리하게 사용하고 있다.

* 이처럼 매퍼 파일의 SQL문이 복잡할 때는 공통으로 사용하는 SQL문에 미리 <sql> 태그를 적용한 후 다른 SQL 문에서 재사용하는 방법이 유용하다.

* 마이바티스에서 오라클 연동해 like 검색하는 방법

- 마이바티스에서 오라클의 like 연산자로 검색할 때는 '%' 기호와 조건 값 사이에 반드시 || 기호를 사용해서
  연결해 주어야 한다.
  
   <!--  like 검색 -->
   <select id="selectLike" resultMap="memResult"  parameterType="String" >
      <![CDATA[
         select * from t_member
         where
         name like '%' || #{name} || '%'		
      ]]>
   </select>

* 지금까지 마이바티스의 동적 SQL 기능을 알아보았다. 이와 관련해 더 자세한 내용은 다음의 링크를 참고하길 바란다.

https://mybatis.org/mybatis-3/ko/dynamic-sql.html

 

MyBatis – 마이바티스 3 | 동적 SQL

동적 SQL 마이바티스의 가장 강력한 기능 중 하나는 동적 SQL을 처리하는 방법이다. JDBC나 다른 유사한 프레임워크를 사용해본 경험이 있다면 동적으로 SQL 을 구성하는 것이 얼마나 힘든 작업인지

mybatis.org