본문 바로가기
JAVA

[JAVA] MyBatis - HashMap 사용

by Amy IT 2022. 6. 16.

SQL문 실행시 map을 인자로 전달하거나 실행 결과를 map 타입으로 반환받고자 하는 경우가 있습니다. 이때 Mapper XML 파일에 SQL문 작성시 parameterType 또는 resultType을 HashMap으로 지정할 수 있습니다.

 

저는 오라클 scott/tiger 계정의 dept 테이블을 이용해 실습해 보겠습니다.

 

 

▶ resultType="hashmap"

 

SQL문 실행 결과를 HashMap 타입으로 저장하여 반환하는 형태입니다. 

 

 

1. selectOne

 

SELECT문 실행 결과가 레코드 한 개인 경우, selectOne() 메소드를 이용합니다. 이때 resultType을 hashmap으로 지정하면 레코드 한 개를 HashMap 타입으로 저장하여 HashMap 타입으로 반환합니다. 레코드 한 개를 DTO인 Dept 타입으로 담아서 반환해도 되지만, map 타입으로 저장하는 것이 더 유용한 경우가 있을 수 있습니다. 예를 들어 모델 클래스를 따로 만들지 않고 원하는 value 데이터만 꺼내오고자 하는 경우, 혹은 테이블 간 조인하여 조인한 결과와 일치하는 모델 클래스가 없는 경우 등을 생각해 볼 수 있습니다. 

 

우선 MyBatis를 사용하기 위한 환경설정을 해 줍니다. 자세한 내용은 아래 게시글에서 확인할 수 있습니다.

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

 

[JAVA] MyBatis 시작하기

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

amy-it.tistory.com

요약하면 다음과 같습니다. 

  • Configuration.xml 파일에 jdbc.properties와 typeAliases, Mapper.xml 파일 설정까지 완료합니다.
  • SqlSession 인스턴스를 생성하기 위한 클래스를 만듭니다. 저는 MySqlSessionFactory 클래스와 getSqlSession() 메소드를 만들어, 다른 클래스에서 MySqlSessionFactory.getSqlSession() 으로 SqlSession을 얻어올 수 있도록 설정하였습니다.
  • dept 테이블의 각 레코드를 저장하기 위한 모델 클래스로 Dept라는 이름의 DTO 클래스를 만들었습니다. 

 

이제 부서번호를 이용해 부서 하나를 조회하는 프로그램을 만들어 보겠습니다. 

 

 

Main

 

Main에서 Service 객체를 생성하고 Service 클래스의 selectByDeptnoHashMap 메소드를 호출하며 찾고자 하는 부서 번호 10을 인자로 전달하고 있습니다. 이후 실행 결과를 HashMap 타입으로 반환받아 map의 키값을 이용해 결과를 출력하고 있습니다. resultType을 hashmap으로 지정한 경우, MyBatis는 꺼내온 데이터에서 컬럼 이름을 key값으로 저장하고 각 컬럼에 해당하는 데이터를 value값으로 저장해서 hashmap을 반환합니다. 이 때문에 HashMap의 제네릭을 <String, Object>로 지정하였습니다. 컬럼 이름의 경우 문자열로 저장하면 되지만, 각 컬럼에 해당하는 실제 데이터는 문자열, 숫자, 날짜 등 다양한 타입을 가지기 때문입니다.

import java.util.HashMap;
import java.util.List;
import java.util.Set;

import com.dto.Dept;
import com.service.OracleMyBatisService;

public class OralceMyBatisMain {
	public static void main(String[] args) {
		OracleMyBatisService service = new OracleMyBatisService();
		HashMap<String, Object> map = service.selectByDeptnoHashMap(10);
		System.out.println(map);
		Set<String> keys = map.keySet();
		for (String key : keys) {
			System.out.println("key: "+key+"\t value: "+map.get(key)); 
		}
	}
}

 

 

Service

 

Service 클래스의 selectByDeptnoHashMap 메소드는 SqlSession 인스턴스를 얻어와서 DAO 클래스의 메소드로 부서 번호와 함께 전달합니다. 

package com.service;

import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.config.MySqlSessionFactory;
import com.dao.OracleMyBatisDAO;
import com.dto.Dept;

public class OracleMyBatisService {
	OracleMyBatisDAO dao;
	public OracleMyBatisService() {
		super();
		dao = new OracleMyBatisDAO();
	}
	public HashMap<String, Object> selectByDeptnoHashMap(int deptno) {
		SqlSession session = MySqlSessionFactory.getSqlSession();
		HashMap<String, Object> map = null;
		try {
			map = dao.selectByDeptnoHashMap(session, deptno);
		} finally {
			session.close();
		}
		return map;
	}
}

 

 

DAO

 

DAO 클래스의 selectByDeptnoHashMap 메소드는 전달받은 SqlSession을 이용해 selectOne 메소드를 호출하며 SQL문의 id와 int 형의 deptno를 인자로 전달합니다. 

package com.dao;

import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.dto.Dept;

public class OracleMyBatisDAO {
	public HashMap<String, Object> selectByDeptnoHashMap(SqlSession session, int deptno) {
		HashMap<String, Object> map = session.selectOne("selectByDeptnoHashMap", deptno);
		return map;
	}
}

 

 

Mapper.xml

 

Mapper XML 파일에서 selectByDeptnoHashMap을 id로 갖는 SQL문을 작성합니다. int 형의 deptno를 인자로 전달받기 위해 parameterType을 int로 지정하고, 실행된 결과를 HashMap으로 저장하기 위해 resultType을 hashmap으로 지정합니다. 전달받은 인자는 SQL문의 #{ } 자리에 설정되어, Main에서 지정한 부서 번호를 조회할 수 있게 됩니다. 

<?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="Mapper">

   <select id="selectByDeptnoHashMap" parameterType="int" resultType="hashmap">
      select * from dept where deptno=#{deptno}
   </select>

</mapper>

 

실행 결과는 다음과 같이 출력됩니다.

 

 

10번 부서의 모든 정보가 각각 key값 대 value값으로 저장된 것을 확인할 수 있습니다. 

 

 

* HashMap 사용시 주의점

 

HashMap의 제네릭을 <String, Object>로 지정하였으므로, 각 key에 해당하는 value를 꺼내올 때 필요시 형변환을 해야 합니다. 그런데 HashMap 타입으로 반환받은 10번 부서에서 각 key에 해당하는 value를 꺼내오려고 할 때, 다음과 같이 하면 예외가 발생되는 것을 확인할 수 있습니다.

String dname = (String) map.get("DNAME");
String loc = (String) map.get("LOC");
int deptno = (int) map.get("DEPTNO"); //예외 발생
System.out.println(deptno+"\t"+dname+"\t"+loc);

예외 발생 메세지입니다.

java.lang.ClassCastException: 
   class java.math.BigDecimal cannot be cast to class java.lang.Integer

 

BigDecimal 타입을 Integer 타입으로 변환시킬 수 없다는 것입니다. 이는 MyBatis에서 오라클 DBMS의 숫자 타입 데이터를 HashMap으로 저장할 때 Integer 타입이 아닌 BigDecimal 타입으로 저장되기 때문에 발생하는 문제입니다. 이에 대한 해결책으로 두 가지를 생각해 볼 수 있습니다.

 

① 먼저 Object 타입으로 얻어온 부서 번호 10을 BigDecimal로 명시적 형변환 합니다. 이후 BigDecimal 클래스에서 제공하는 intValue() 메소드로 BigDecimal 타입을 int 타입으로 변환합니다. 1을 더해 int로 올바르게 변환되었는지 확인합니다. 11이 출력되는 것을 확인할 수 있습니다.

int deptno = ((BigDecimal) map.get("DEPTNO")).intValue();
System.out.println(deptno+1); //11

 

② Object 타입으로 얻어온 부서 번호 10을 String 클래스에서 제공하는 valueOf() 메소드로 String 타입으로 변환합니다. String 타입으로 변환한 부서 번호 10을 다시 Integer 클래스에서 제공하는 parseInt() 메소드로 int 타입으로 변환합니다. 마찬가지로 1을 더해 int로 올바르게 변환되었는지 확인합니다. 11이 출력되는 것을 확인할 수 있습니다.

String temp_deptno = String.valueOf(map.get("DEPTNO"));
int deptno2 = Integer.parseInt(temp_deptno);
System.out.println(deptno2+1); //11

 

 

 

2. selectList

 

SELECT문 실행 결과가 여러 개의 레코드인 경우, selectList() 메소드를 이용합니다. 이때 resultType을 hashmap으로 지정하면 레코드 한 개씩 HashMap 타입으로 저장한 후, 모든 레코드를 다시 List 타입에 담아 반환합니다. 

 

 

Main

 

Main에서는 Service 클래스의 selectAllHashMap 메소드를 호출한 후 반환받은 결과를 List<HashMap<String, Object>> 타입으로 저장합니다. List에서 각 방에 들어 있는 HashMap 하나를 꺼낸 후 map의 key값들을 이용해 각 key에 해당하는 value값을 순회하며 출력하고 있습니다. 

List<HashMap<String, Object>> list = service.selectAllHashMap();
for (HashMap<String, Object> map2 : list) {
	Set<String> keys2 = map2.keySet();
	for (String key : keys2) {
		System.out.println("key: "+key+"\t value: "+map2.get(key));
	}
	System.out.println("-----------------------------------");
}

 

 

Service 

 

Service 클래스의 selectAllHashMap 메소드는 SqlSession 인스턴스를 얻어와서 DAO 클래스의 메소드로 전달합니다.

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

 

 

DAO

 

DAO 클래스의 selectAllHashMap 메소드는 전달받은 SqlSession을 이용해 selectList 메소드를 호출하며 SQL문의 id를 인자로 전달합니다. 

public List<HashMap<String, Object>> selectAllHashMap(SqlSession session) {
	List<HashMap<String, Object>> list = session.selectList("selectAllHashMap");
	return list;
}

 

 

Mapper.xml

 

Mapper XML 파일에서 selectAllHashMap을 id로 갖는 SQL문을 작성합니다. SELECT된 결과를 레코드 하나씩 HashMap으로 담기 위해 resultType을 hashmap으로 지정합니다. HashMap으로 저장된 각각의 레코드는 다시 List에 모두 담겨서 반환됩니다.

<select id="selectAllHashMap" resultType="hashmap">
  select * from dept 
</select>

 

출력 결과는 다음과 같습니다.

 

 

바깥 for문에서는 List에서 각 방에 저장된 HashMap을 하나씩 꺼내옵니다. 꺼내온 하나의 HashMap에서 key값들을 꺼낸 후, 안쪽 for문에서 key값을 이용하여 key에 해당하는 value값을 순회하며 출력합니다. 안쪽 for문이 순회를 모두 마치면 하나의 HashMap의 데이터, 즉 하나의 레코드의 데이터가 모두 출력된 것입니다. 이를 구분선을 이용해 구분지어 주었습니다. 이후 다시 바깥 for문으로 돌아가 다음 HashMap, 즉 다음 레코드를 순회하게 됩니다. 

 

 

 

 

▶ parameterType="hashmap"

 

SQL문 실행시 HashMap 타입을 인자로 전달하는 형태입니다. 마찬가지로 SQL문 실행 결과에 따라 selectOne(), selectList() 메소드를 모두 사용 가능하지만, 이번에는 selectList()만을 살펴보겠습니다. 

 

 

1. selectList

 

Mapper XML 파일에 SQL문을 작성할 때 parameterType을 지정할 수 있는데, 주의할 점은 여러 개의 인자를 전달받을 수 없다는 점입니다. 예를 들어 10번 부서와 20번 부서 정보를 조회하고 싶을 때, SQL문 실행시 parameterType을 int로 지정하고 10, 20 이라는 두 개의 int 값을 전달할 수 없다는 것입니다. 이때 여러 개의 값을 Map이나 List와 같은 형태로 저장하여 인자로 전달할 수 있습니다. 여기서는 HashMap 타입으로 전달하는 방법을 알아보겠습니다.

 

 

Main

 

Main에서 HashMap<String, Integer>을 생성하고, 각 key에 해당하는 value값으로 검색하고자 하는 검색어를 저장합니다. 이후 Service 클래스의 selectByHashMap 메소드를 호출하며 생성한 HashMap을 인자로 전달합니다. 반환받은 List의 데이터를 순회하며 출력하고 있습니다.

HashMap<String, Integer> map3 = new HashMap<>();
map3.put("key1", 10);
map3.put("key2", 20);
List<Dept> list2 = service.selectByHashMap(map3);
for (Dept dept : list2) {
	System.out.println(dept);
}

 

 

Service

 

Service 클래스의 selectByHashMap 메소드는 SqlSession 인스턴스를 가져온 후, DAO 클래스의 selectByHashMap 메소드를 호출하며 map과 함께 인자로 전달합니다. 

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

 

 

DAO

 

DAO 클래스의 selectByHashMap 메소드는 전달받은 SqlSession을 이용해 selectList 메소드를 호출하며 SQL문의 id와 map을 인자로 전달합니다. 

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

 

 

Mapper.xml

 

Mapper XML 파일에서 selectByHashMap을 id로 갖는 SQL문을 작성합니다. HashMap을 인자로 전달받고 있으므로 parameterType을 hashmap으로 지정합니다. MyBatis는 전달받은 HashMap에서 #{ }로 표기한 위치에 key값에 해당하는 value값을 자동으로 꺼내와 설정해 줍니다. 이후 실행된 결과를 레코드 하나씩 resultType으로 지정한 Dept 객체에 담고, 모든 Dept 객체를 다시 List에 담아 반환합니다. 

<select id="selectByHashMap" parameterType="hashmap" resultType="Dept">
  select * from dept where deptno in (#{key1}, #{key2})
</select>

 

실행 결과는 다음과 같습니다.

 

 

10번과 20번 부서 정보가 잘 조회되는 것을 확인할 수 있습니다.

 

 

이상으로 MyBatis에서 SQL문 실행시 HashMap을 인자로 전달하거나 결과를 HashMap으로 리턴받는 방법에 대해 알아보았습니다.

 

 

'JAVA' 카테고리의 다른 글

[JAVA] MyBatis - RowBounds  (0) 2022.06.19
[JAVA] MyBatis - 동적 SQL  (0) 2022.06.19
[JAVA] MyBatis 시작하기  (0) 2022.06.15
[JAVA] JDBC - DAO, DTO 패턴  (0) 2022.06.09
[JAVA] JDBC - PreparedStatement  (0) 2022.06.07

댓글