본문 바로가기
JAVA

[JAVA] MyBatis - 동적 SQL

by Amy IT 2022. 6. 19.

MyBatis의 가장 강력한 장점 중 하나는 동적 SQL을 처리하는 방법입니다. 이번 게시글에서는 MyBatis에서 동적 SQL을 처리하는 방법에 대해 알아보도록 하겠습니다.

 

MyBatis를 사용하기 위한 환경설정과 기본 사용법은 아래 게시글에서 확인할 수 있습니다.

https://amy-it.tistory.com/64

 

[JAVA] MyBatis 시작하기

▶ MyBatis 란? MyBatis(마이바티스)는 SQL 매핑(mapping) 프레임워크입니다. MyBatis는 자바와 데이터베이스를 연동하기 위해 JDBC로 처리하는 상당 부분의 코드와 파라미터 설정 및 결과 매핑을 대신해 줌

amy-it.tistory.com

 

기본 구조는 동일하게 사용하고, Main, Service 클래스, DAO 클래스, Mapper.xml 파일을 중심으로 살펴보겠습니다.

 

 

▶ if 

 

if 태그를 사용하면 조건식을 지정해 해당 조건을 만족하는 경우에만 특정 구문을 동적으로 덧붙일 수 있습니다. Main에서 전달한 데이터에 deptno가 존재하는 경우, 해당 deptno를 이용해 데이터를 조회하고자 합니다. SQL문 작성시 if 태그를 사용해  deptno가 null이 아닌 경우에 조건식을 덧붙이는 형태가 되겠습니다.

 

Main

 

Main에서 HashMap 타입으로 검색조건을 저장해 Service 클래스의 selectDynamicDeptno 메소드로 전달합니다. map을 사용하면 key값을 이용한 검사가 가능합니다. 

OracleMyBatisService service = new OracleMyBatisService();
HashMap<String, Integer> map = new HashMap<>();
map.put("deptno", 10);
List<Dept> list = service.selectDynamicDeptno(map);
for (Dept dept : list) {
	System.out.println(dept);
}

 

Service

 

Service에서는 DAO클래스의 selectDynamicDeptno 메소드로 session과 map을 전달합니다.

public List<Dept> selectDynamicDeptno(HashMap<String, Integer> map) {
	SqlSession session = MySqlSessionFactory.getSqlSession();
	List<Dept> list = null;
	try {
		list = dao.selectDynamicDeptno(session, map);
	} finally {
		session.close();
	}
	return list;
}

 

DAO

 

DAO클래스에서는 session을 이용해 selectList 메소드를 호출하며 id값과 map을 전달합니다.

public List<Dept> selectDynamicDeptno(SqlSession session, HashMap<String, Integer> map) {
	List<Dept> list = session.selectList("selectDynamicDeptno", map);
	return list;
}

 

Mapper.xml

 

if 태그를 사용하여 deptno가 null이 아닌 경우에만 where절이 동적으로 붙습니다. deptno가 null인 경우 전체 데이터를 SELECT하게 됩니다. 이때 전달받은 인자가 map 타입이므로 #{}를 사용하면 지정한 key에 해당하는 value를 꺼내오게 됩니다.

<select id="selectDynamicDeptno" parameterType="hashmap" resultType="Dept">
   select * from dept 
   <if test="deptno != null">
      where deptno=#{deptno}
   </if>
</select>

 

10번 부서 정보가 조회됩니다.

 

 

 

 

▶ choose, when, otherwise 

 

조건식을 여러 개 지정하고자 하는 경우가 있습니다. if 태그를 사용하면 복수 개의 조건을 모두 만족하는 경우 해당 조건식이 모두 실행됩니다. 이와 달리 여러 조건식 중 하나만을 선택적으로 사용하도록 하기 위해서는 choose 태그를 사용해야 합니다. 자바의 switch 구문과 유사하다고 할 수 있습니다. 이번에는 Main에서 전달한 데이터에 dname이 존재하는 경우 dname을 이용해 데이터를 조회하고, loc이 존재하는 경우 loc을 이용해 데이터를 조회하고자 합니다. 

 

Main

 

Main에서 HashMap 타입으로 검색조건을 저장해 전달합니다. Service와 DAO 클래스는 앞선 예시와 유사하므로 생략하겠습니다. 

OracleMyBatisService service = new OracleMyBatisService();
HashMap<String, String> map = new HashMap<>();
map.put("loc", "DALLAS");
map.put("dname", "SALES");
List<Dept> list = service.selectDynamicChoose(map);
for (Dept dept : list) {
	System.out.println(dept);
}

 

Mapper.xml

 

choose, when, otherwise 태그를 사용하여 첫 번째, 두 번째, 세 번째 중 한 가지를 선택하도록 합니다. dname이 null이 아닌 경우 첫 번째 조건식을 실행하고 아래 검사는 더 이상 진행하지 않습니다. 첫 번째 조건을 만족하지 않고 두 번째 조건인 loc이 null이 아닌 경우 두 번째 조건식을 실행합니다. 두 가지 모두 해당되지 않는 경우 otherwise 구문이 실행되며, 여기서는 공백으로 두었으므로 전체 데이터가 SELECT됩니다. 

<select id="selectDynamicChoose" parameterType="HashMap" resultType="Dept">
   select * from dept
   <choose>
      <when test="dname != null">
         where dname = #{dname}  
      </when>
      <when test="loc != null">
         where loc = #{loc}
      </when>
      <otherwise>
      </otherwise>
   </choose>
</select>

 

 

Main에서 넘겨준 map에 dname과 loc이 모두 존재하므로, 첫 번째 조건식을 만족하여 dname을 이용한 데이터 조회가 이루어집니다. 따라서 dname이 SALES인 부서 정보가 조회됩니다.

 

 

 

 

▶ trim, where, set 

 

앞서 여러 조건식 중 하나를 선택할 때 choose 태그를 사용하는 것을 보았습니다. 그런데 해당하는 모든 조건식을 실행해야 하는 경우 생각해 보아야 할 점이 있습니다. 위의 예시를 다음과 같이 바꿔보면 문제가 발생합니다.

<select id="selectDynamicChoose" parameterType="HashMap" resultType="Dept">
   select * from dept
   <if test="dname != null">
      where dname = #{dname} 
   </if>
   <if test="loc != null">
      where loc = #{loc}
   </if>
</select>

dname과 loc이 모두 존재하는 경우 where 키워드가 두 번 붙게 되어 SQL 문법 오류가 발생하는 것입니다. 이때 필요한 것이 trim 기능입니다. 

 

 SELECT 

 

Main

 

dname이 SALES이고 loc이 CHICAGO인 부서 정보를 조회하고자 합니다. 

OracleMyBatisService service = new OracleMyBatisService();
HashMap<String, String> map = new HashMap<>();
map.put("dname", "SALES");
map.put("loc", "CHICAGO");
List<Dept> list = service.selectTrim(map);
for (Dept dept : list) {
	System.out.println(dept);
}

 

Mapper.xml

 

where 키워드를 태그로 작성하면 where절 안의 내용이 존재하는 경우 MyBatis가 동적으로 "where"를 추가하고 존재하지 않는 경우 추가하지 않게 됩니다. 또한, 내용이 and나 or로 시작하는 경우 문법에 맞게 자동으로 and나 or를 지워줍니다. 즉 dname과 loc이 모두 존재하지 않는 경우 select * from dept가 되고, loc만 존재하는 경우 select * from dept where loc=#{loc}이 되고, 모두 존재하는 경우 select * from dept where dname=#{dname} and loc=#{loc}이 되는 것입니다.

<select id="selectTrim" parameterType="hashmap" resultType="Dept">
   select * from dept 
   <where>
      <if test="dname != null">
         dname=#{dname}
      </if>
      <if test="loc != null">
         and loc=#{loc}
      </if>
   </where>
</select>

 

이는 다음의 trim 태그를 직접 사용자 정의한 것과 동일합니다. prefix는 맨앞에 추가할 키워드를, prefixOverrides는 필요시 맨앞에 제거할 키워드를 지정하는 속성입니다. 

<select id="selectTrim" parameterType="hashmap" resultType="Dept">
   select * from dept 
   <trim prefix="where" prefixOverrides="and | or">
      <if test="dname != null">
         dname=#{dname}
      </if>
      <if test="loc != null">
         and loc=#{loc}
      </if>
   </trim>
</select>

 

dname이 SALES이고 loc이 CHICAGO인 부서 정보가 조회됩니다. 

 

 

 

 UPDATE 

 

마찬가지로 UPDATE도 위와 같이 trim 태그를 사용해 실행해 보도록 하겠습니다. 

 

Main

 

실습을 위해 만든 99번 부서를 다음과 같이 만들었습니다. 99번 부서의 이름을 개발로 UPDATE해 보겠습니다.

 

 

Main에서 deptno와 dname이 담긴 map을 전달합니다. 

OracleMyBatisService service = new OracleMyBatisService();
HashMap<String, Object> map = new HashMap<>();
map.put("deptno", 99);
map.put("dname", "개발");
service.updateTrim(map);

 

Service

 

Service에서 session과 map을 전달합니다. 이후 명시적으로 commit을 실행합니다. 

public void updateTrim(HashMap<String, Object> map) {
	SqlSession session = MySqlSessionFactory.getSqlSession();
	try {
		dao.updateTrim(session, map);
		session.commit();
	} finally {
		session.close();
	}
}

 

DAO

 

DAO에서 id값과 map을 전달하고, 실행된 결과를 출력합니다.

public void updateTrim(SqlSession session, HashMap<String, Object> map) {
	int result = session.update("updateTrim", map);
	System.out.println(result+"개의 레코드 업데이트");
}

 

Mapper.xml

 

이번에는 set 키워드를 태그로 작성하여 set 안의 내용이 존재하는 경우 동적으로 "set"이 추가되도록 합니다. 또한, 구문이 콤마로 끝날 경우 자동으로 콤마를 지워줍니다. 여기서는 dname만 존재하므로 update dept set dname=#{dname} where deptno=#{deptno}가 됩니다. 

<update id="updateTrim" parameterType="hashmap">
   update dept 
   <set>
      <if test="dname != null">
         dname=#{dname},
      </if>
      <if test="loc != null">
         loc=#{loc}
      </if>
   </set>
   where deptno=#{deptno}
</update>

 

마찬가지로 다음과 같이 trim 태그를 사용자 정의한 것과 동일합니다. prefix로 맨앞에 추가할 키워드를 지정하고, suffixOverrides로 필요시 맨뒤에 제거할 키워드를 지정합니다. 

<update id="updateTrim" parameterType="hashmap">
   update dept 
   <trim prefix="set" suffixOverrides=",">
      <if test="dname != null">
         dname=#{dname},
      </if>
      <if test="loc != null">
         loc=#{loc}
      </if>
   </trim>
   where deptno=#{deptno}
</update>

 

99번 부서 정보가 업데이트된 것을 확인할 수 있습니다.

 

 

 

 

▶ foreach 

 

foreach 태그를 사용하면 컬렉션에 대해 반복처리하는 작업이 매우 용이해 집니다. 

 

 SELECT 

 

Main

 

10, 20, 30번 부서 정보를 모두 조회하고자 합니다. List에 검색하고자 하는 정보를 담아 전달합니다. 

OracleMyBatisService service = new OracleMyBatisService();
List<Integer> deptnoes = Arrays.asList(10, 20, 30);
System.out.println(deptnoes);
List<Dept> list = service.multiSelect(deptnoes);
for (Dept dept : list) {
	System.out.println(dept);
}

 

Mapper.xml

 

검색할 정보가 각 item으로 담겨 있는 arraylist를 전달받고, 이는 다시 foreach 태그에 list타입으로 전달됩니다. open과 close 속성으로 열고 닫는 문자열을 명시하고, separator 속성으로 구분자를 지정할 수 있습니다. 전달받은 컬렉션에서 item을 하나씩 꺼내서 지정한 문자열을 이용해 자동으로 반복처리하게 됩니다. 여기에서는 select * from dept where deptno in (10, 20, 30) 과 같은 형태로 SQL문이 완성됩니다.

<select id="multiSelect" parameterType="arraylist" resultType="Dept">
   select * from dept
   <where>
      <foreach item="item" collection="list"
         open="deptno in (" separator="," close=")">
            #{item}
      </foreach>
   </where>
</select>

 

10, 20, 30번 부서 정보가 조회됩니다.

 

 

 

 INSERT 

 

Main

 

12번, 13번 부서를 추가하고자 합니다. 이번에는 Dept 객체를 List에 담아 전달해 보겠습니다.

OracleMyBatisService service = new OracleMyBatisService();
List<Dept> depts = Arrays.asList(new Dept(12, "", ""), new Dept(13, "", ""));
service.multiInsert(depts);

 

Mapper.xml

 

insert all 구문 아래 foreach 태그를 이용해 list의 각 항목이 모두 순회할 때까지 복수 개의 레코드가 추가되도록 합니다. 이때, 넘어온 List에 담긴 각 item은 Dept 객체이므로, 전달받은 Dept 객체 안의 데이터를 꺼내오기 위해 "item 이름.꺼내올 변수명" 과 같은 형태로 지정해 줍니다. 주의할 점은 insert all 구문의 문법상 반드시 서브쿼리를 작성해야 하므로 select * from dual 이라는 무의미한 서브쿼리를 덧붙여야 한다는 것입니다. 

<insert id="multiInsert" parameterType="arraylist">
   insert all 
   <foreach item="item" collection="list"> 
      into dept (deptno, dname, loc)
      values (#{item.deptno}, #{item.dname}, #{item.loc})
   </foreach>
   select * from dual
</insert>

 

12번, 13번 부서가 추가된 것을 확인할 수 있습니다.

 

 

 

 UPDATE 

 

Main

 

12번, 13번 부서의 지역을 서울로 수정해 보겠습니다. 

OracleMyBatisService service = new OracleMyBatisService();
List<Integer> deptnoes2 = Arrays.asList(12, 13);
service.multiUpdate(deptnoes2);

 

Mapper.xml

 

multiSelect와 유사하나, 이번에는 trim 기능 없이 where절을 작성하였습니다. 

<update id="multiUpdate" parameterType="arraylist">
   update dept 
   set loc = '서울' 
   where deptno in 
   <foreach item="item" collection="list" 
      open="(" separator="," close=")">
         #{item}
   </foreach>
</update>

 

12번, 13번 부서 정보가 수정된 것을 확인할 수 있습니다.

 

 

 

 DELETE 

 

Main

 

12번, 13번 부서를 삭제해 보겠습니다. 

OracleMyBatisService service = new OracleMyBatisService();
List<Integer> deptnoes2 = Arrays.asList(12, 13);
service.multiDelete(deptnoes2);

 

Mapper.xml

 

지금까지의 예시와 유사합니다.

<delete id="multiDelete" parameterType="arraylist">
   delete from dept 
   where deptno in
   <foreach item="item" collection="list" 
      open="(" separator="," close=")">
      #{item}
   </foreach>
</delete>

 

2개의 레코드가 삭제된 것을 확인할 수 있습니다.

 

 

 

이상으로 MyBatis의 동적 SQL 사용법에 대해 알아보았습니다.

 

 

▶ References

 

 

 

'JAVA' 카테고리의 다른 글

[JAVA] MyBatis - RowBounds  (0) 2022.06.19
[JAVA] MyBatis - HashMap 사용  (0) 2022.06.16
[JAVA] MyBatis 시작하기  (0) 2022.06.15
[JAVA] JDBC - DAO, DTO 패턴  (0) 2022.06.09
[JAVA] JDBC - PreparedStatement  (0) 2022.06.07

댓글