Programming/Spring

[Spring] MyBatis: 반환값과 매개변수에 따라 달라지는 쿼리

Isaac S. Lee 2023. 11. 24. 12:25

🌿쿼리 경우의 수


1. 반환값(X), 매개변수(X)

2. 반환값(X), 매개변수(O)

3. 반환값(O), 매개변수(X)

  • 반환값: 단일값
  • 반환값: 레코드 1줄
  • 반환값: 레코드 N줄

4. 반환값(O), 매개변수(O)

  • 반환값: 단일값
  • 반환값: 레코드 1줄
  • 반환값: 레코드 N줄

 

 

[Spring] MyBatis: Connection Pool (Commons DBCP, HikariCP)

🌿MyBatis 영속성 계층 (데이터베이스 조작 계층) Mybatis는 자바 오브젝트와 SQL사이의 자동 매핑 기능을 지원하는 ORM(Object relational Mapping) 프레임워크이다. MyBatis는 Spring 기술이 아닌 독립적인 기술

isaac-christian.tistory.com

이전 MyBatis 글에서 이어진다.

 

🌿반환값(X), 매개변수(X)


  • 정적 쿼리

 

MyBatisController.java

@GetMapping(value="/m1.do")
public String m1() {
	
	this.dao.m1();
	
	return "list";
}​

반환값도 매개변수도 없으므로 바로 dao를 호출한다.

dao는 위에서 의존 주입 되었기 때문에 바로 사용하면 된다.

 

MyBatisDAO.java

public interface MyBatisDAO {

	void m1();
	
}

인터페이스에 m1 메서드가 추가되었다.

 

MyBatisDAOImpl.java

@Override
public void m1() {

	// update tblMyBatis set age = age + 1;
	int result = this.template.update("namespace.m1");

	System.out.println("적용된 행의 개수: " + result);

}

나이를 증가시키는 쿼리를 만들어보자.
어떤 쿼리를 날리느냐에 따라서 'template.'을 했을 때 선택하는 게 달라진다.
현재는 update를 할 예정이므로 template.update를 사용한다. 이 안에 쿼리들 중 namespace.id를 넣어 주도록 한다.
result는 쿼리 입장에서 반환값이 아니라 피드백일 뿐이다.

 

mybatis.xml

<insert id="m1">
	update tblMyBatis set age = age + 1
</insert>

메서드 이름을 id로 부여해 주는 것이 좋다.

 

이제 서버를 다시 시작하여 실행하면 테이블에 들어있는 모든 사람의 나이가 1씩 증가한다.

 

🌿반환값(X), 매개변수(O)


단일 데이터

MyBatisController.java

@GetMapping(value="/m2.do")
public String m2(String seq, Model model) {
	
	//m2.do?seq=n
	//delete from tblMyBatis where seq = ?
	int result = this.dao.m2(seq);
	
	model.addAttribute("result", result);
	
	return "list";
}

단일값 매개변수를 넘겼을 때를 구현해 보도록 하자.

기본키를 하나 넘겨주면 해당 데이터를 삭제하는 쿼리를 만들어 보도록 하자.

그리고 Model을 만들어서 피드백도 받도록 한다.

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m2(String seq);
	
}

 

MyBatisDAOImpl.java

@Override
public int m2(String seq) {

    return this.template.update("mybatis.m2", seq);
}

seq를 콤마로 함께 넘기면 m2 쿼리에 사람 번호가 넘어가서 삭제된다.

 

mybatis.xml

<delete id="m2" parameterType="String">
	delete from tblMyBatis where seq = #{seq}
</delete>

seq라는 매개변수가 하나 넘어와야 한다. MyBatis에서는 매개변수를 '#{}'로 사용한다.

나중에 내부적으로 JDBC에서 사용하던 '?'로 바뀌게 된다.

MyBatis의 #{}에는 매개변수의 key를 넣는데, 이 경우에는 key는 어떤 것을 사용해도 상관이 없다. #{aaa}처럼 아무 이름이나 적어도 seq가 들어가게 되는데, 그 이유는 넘어온 데이터가 하나밖에 없기 때문이다. 그래서 이 경우에는 이름이 필요 없다.

데이터를 가져와서 결합하는 것을 데이터 바인딩이라고 하며, 물리적으로 의미는 없지만 사람이 봤을 때 헷갈리지 않도록 관습적으로 변수의 이름으로 작성한다.

이제 매개변수의 자료형으로 parameterType을 작성해야 한다. seq의 Type을 String으로 넘겼으므로 String 자료형을 표현해 주도록 한다. 이는 생략할 수 있지만 가독성을 위해 되도록 작성하는 것이 좋다.

 

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>

</style>
</head>
<body>
	<!-- list.jsp -->
	<h1>결과</h1>
	
	<div class="message" title="결과">${result}</div>
	
	<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
	<script>
	
	</script>
</body>
</html>

 

복합 데이터 (DTO)

MyBatisController.java

public String m3(Model model) {
	
	MyBatisDTO dto = new MyBatisDTO();
	dto.setName("Lee");
	dto.setAge("25");
	dto.setAddress("서울시 강남구 대치동");
	dto.setGender("m");
	
	int result = this.dao.m3(dto);
	
	model.addAttribute("result", result);
	
	return "list";
}

이번에는 더 많은 데이터로 dto를 넘겨서 insert 작업을 해보도록 하자.

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m3(MyBatisDTO dto);
	
}

 

MyBatisDAOImpl.java

@Override
public int m3(MyBatisDTO dto) {

	return this.template.insert("mybatis.m3", dto);
}

총 4개의 데이터를 전달해야 하는데, 이게 dto에 포장이 되어 있다.

포장이 되어 있든, 낱개로 되어 있든 중요한 게 아니다. 중요한 건 어떤 쿼리를 어떻게 사용할 것이냐이다.

매개변수를 최대 한 개밖에 못 넣는데, 지금은 복합값(클래스 객체)을 넘기기 때문에 받아주는 쿼리 쪽에서 차이가 생긴다.

 

mybatis.xml

<insert id="m3" parameterType="com.test.domain.MyBatisDTO">
	insert into tblMyBatis (seq, name, age, address, gender)
	values (seqMyBatis.nextVal, #{name}, #{age}, #{address}, #{gender})
</insert>

어느 패키지 안에 들어 있는 클래스인지를 명시해 주어야 한다.

parameterType으로 DTO를 넣을 때, Isaac이라는 사람을 찾을 때 서울에 사는 사람인지, 부산에 사는 사람인지 알 수가 없다.

물론 String을 넣는 정석은 java.lang.String이지만, java.lang은 가장 기본적인 패키지이기 때문에 유일하게 자동 임포트 되어 생략할 수 있다.

이번에는 넘어온 데이터가 총 4개이기 때문에 getter의 이름을 적어주어야 한다. 이때 생략할 것은 생략하고 getter의 name을 적게 된다.

그러면 MyBatis가 각각의 Setter들을 불러와서 쿼리를 실행한다.

 

테이블에 입력한 데이터가 insert 된 것을 확인할 수 있다.

 

Mapped Statement collection does not contain value for ~

매핑된 Statement collection에서 데이터를 찾지 못하고 있다는 오류가 발생한다면, 이 이름을 가진 태그를 못 찾고 있다는 의미이다. 이 오류는 대소문자를 틀리는 경우, 오타를 내는 경우에서 에러가 발생한다.

 

java.sql.SQLException. 부적합한 열 유형

첫 번째 넣은 데이터가 없는 경우에 발생한다.

 

복합 데이터 (HashMap)

이번에는 DTO가 아니라 HashMap을 이용해 복합 데이터를 넘겨 보도록 하자.

2번 사람의 주소를 update 하는 작업을 해보도록 하자.

 

MyBatisController.java

@GetMapping(value="/m4.do")
public String m4(Model model) {

	//update tblMyBatis set address = ? where seq = ?;
	HashMap<String,String> map = new HashMap<String,String>();
	
	map.put("seq", "2");
	map.put("address", "서울시 강남구 압구정동");
	
	int result = this.dao.m4(map);
	
	model.addAttribute("result", result);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m4(HashMap<String, String> map);
	
}

 

MyBatisDAOImpl.java

@Override
public int m4(HashMap<String, String> map) {

    return this.template.update("mybatis.m4", map);
}

 

mybatis.xml

<update id="m4" parameterType="java.util.HashMap">
	update tblMyBatis set address = #{address} where seq = #{seq}
</update>

마찬가지로 key 이름을 넣어서 바인딩하도록 한다.

HashMap을 넘길 때 풀네임을 'map'으로 짧게 생략해서 작성할 수 있다.

 

2번 Sopia의 주소가 변경된 것을 확인할 수 있다.

 

🌿반환값(O), 매개변수(X)


단일값 반환 (1행 1열)

  • select count(*) from tblMyBatis

 

MyBatisController.java

@GetMapping(value="/m5.do")
public String m5(Model model) {
	
	int count = this.dao.m5();
	
	model.addAttribute("count", count);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m5();
	
}

 

MyBatisDAOImpl.java

@Override
public int m5() {

	return this.template.selectOne("mybatis.m5");
}

JDBC였다면 쿼리를 짜고, statement stat 객체를 만들고, request rs에 쿼리를 실행한 결과 stat.executeQuery를 담아 if (rs.next())로 쿼리를 반환받았을 것이다.

MyBatis에서는 반환을 받을 때 이러한 과정 없이 selectOne() 메서드 또는 selectList() 메서드를 사용한다. 이는 레코드의 개수에 차이가 있다.

이 경우에는 레코드가 하나이므로 selectOne()를 사용한다.

Object는 제네릭이기 때문에 왼쪽 reuslt에 어떤 자료형을 준비해 놓느냐에 따라서 다른 형이 된다. 현재 return은 integer이므로 정수형이 된다.

 

mybatis.xml

<select id="m5" resultType="Integer">
	select count(*) from tblMyBatis
</select>

파라미터는 없지만 이 값이 돌아가야 하므로 resultType을 명시해야 한다.

이때 클래스 형식을 작성하면 된다.

java.lang은 기본 패키지므로 생략된 상태이다.

 

레코드의 개수가 출력된다.

 

🌿반환값(O), 매개변수(O, X)


레코드 1개 반환 (컬럼 N개)

  • select * from tblMyBatis where seq = ?

번호를 주면 해당 번호의 레코드의 컬럼을 모두 가져오는 작업을 해보도록 하자.

 

MyBatisController.java

@GetMapping(value="/m6.do")
public String m6(String seq, Model model) {
	
	MyBatisDTO dto = this.dao.m6(seq);
	
	model.addAttribute("dto", dto);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	MyBatisDTO m6(String seq);
	
}

 

MyBatisDAOImpl.java

@Override
public MyBatisDTO m6(String seq) {

	return this.template.selectOne("mybatis.m6", seq);
}

JDBC에서는 값을 하나만 돌려주는 게 아니라 레코드 전체를 돌려주어야 하므로 DTO를 미리 만들었다. 그리고 모든 데이터를 1:1로 옮겨 담아 return 하는 작업을 해야 했다.

Spring에서는 한 줄이면 작업을 완성할 수 있으므로 생산성이 훨씬 높아진다.

 

mybatis.xml

<select id="m6" parameterType="String" resultType="com.test.domain.MyBatisDTO">
	select * from tblMyBatis where seq = #{seq}
</select>

DTO를 resultTypte으로 선언하면 MyBatis가 알아서 1:1 대응을 해준다.

컬럼값을 알아서 대입시켜주는 조건은 오라클의 컬럼 이름과 DTO의 멤버변수 이름, HTML의 태그 이름이 동일한 것이다.

1. DB > 테이블의 컬럼명
2. DTO > 멤버변수명
3. HTML > 태그 name

 

  • http://localhost:8090/mybatis/m6.do?seq=1

해당 seq의 dto가 출력된다.

 

레코드 N개 반환

  • select name from tblMyBatis;

 

MyBatisController.java

@GetMapping(value="/m7.do")
public String m7(Model model) {
	
	List<String> names = this.dao.m7();
	
	model.addAttribute("name", names);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	List<String> m7();
	
}

 

MyBatisDAOImpl.java

@Override
public List<String> m7() {

	return this.template.selectList("mybatis.m7");
}

 

mybatis.xml

<select id="m7" resultType="String">
	select name from tblMyBatis
</select>
  • select + N행 = resultTYpe > 1행의 자료형만 기재

여러 개의 레코드를 반환받을 때에는 1행의 자료형만을 기재한다.⭐

 

테이블의 name이 list 형태로 출력된다.

 

레코드 N개 반환 (컬럼 N개)

  • select * from tblMyBatis;

게시판 등의 글을 불러오는 등의 레코드 N개를 반환받을 때 사용할 수 있다.

 

MyBatisController.java

@GetMapping(value="/m8.do")
public String m8(Model model) {
	
	List<MyBatisDTO> list = this.dao.m8();
	
	model.addAttribute("list", list);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	List<MyBatisDTO> m8();
	
}

 

MyBatisDAOImpl.java

@Override
public List<MyBatisDTO> m8() {

	return this.template.selectList("mybatis.m8");
}

 

mybatis.xml

<select id="m8" resultType="com.test.domain.MyBatisDTO">
	select * from tblMyBatis
</select>

 

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>

</style>
</head>
<body>
	<!-- list.jsp -->
	<h1>결과</h1>
	
	<div class="message" title="결과">${result}</div>
	
	<div class="message" title="레코드 수">${count}</div>
	
	<div class="message" title="DTO">${dto}</div>
	
	<div class="list">
		<c:forEach items="${name}" var="name">
		<div>${name}</div>
		</c:forEach>
	</div>
	
	<div class="list">
		<c:forEach items="${list}" var="dto">
		<div>${dto.name} - ${dto.age} - ${dto.address}</div>
		</c:forEach>
	</div>
	
	<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
	<script>
	
	</script>
</body>
</html>

모든 레코드의 정보가 출력된다.

 

상황별 레코드 반환

  • select count(*) from employees
  • select count(*) from tblMyBatis

상황에 따라 다른 테이블의 레코드 수를 가져오고 싶다고 하자.

이를 m9.do?table=tblMyBatis로 table 파라미터로 넘겨서 하나의 주소로 해당 테이블의 레코드를 반환하게끔 작업해 보도록 하자.

 

MyBatisController.java

@GetMapping(value="/m9.do")
public String m9(Model model, String table) {
	
	int count = this.dao.m9(table);
	
	model.addAttribute("count", count);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m9(String table);
	
}

 

MyBatisDAOImpl.java

@Override
public int m9(String table) {
	
	return this.template.selectOne("mybatis.m9", table);
}

 

mybatis.xml

<select id="m9" parameterType="String" resultType="Integer">
	select count(*) from #{table}
</select>

이렇게 하면 실행될 거 같지만 ORA-00903: invalid table name 오류가 발생한다.

#{key}로 데이터 바인딩을 하면 자료형에 따라 표기가 달라지는데, 숫자인 경우 ''가 붙지 않고, 문자열인 경우 ''가 자동으로 붙게 된다.

그래서 table을 식별자가 아니라 문자열 데이터로 인식하게 되어서 오류가 발생했던 것이다.

<select id="m9" parameterType="String" resultType="Integer">
	select count(*) from ${table}
</select>
  • #{key}: 데이터 바인딩 (자료형에 따라 표기가 달라짐)
  • ${key}: 식별자 바인딩 (테이블명, 컬럼명 등등)

식별자 바인딩을 하기 위해서는 ${key}를 사용해야 한다.

 

  • http://localhost:8090/mybatis/m9.do?table=tblMyBatis

 

  • http://localhost:8090/mybatis/m9.do?table=tblAddress

테이블에 따라서 출력하는 레코드의 개수가 달라진다.

 

연산자 사용 (CDATA)

MyBatisController.java

@GetMapping(value="/m10.do")
public String m10(Model model) {
	
	int count = this.dao.m10();
	
	model.addAttribute("count", count);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	int m10();
	
}

 

MyBatisDAOImpl.java

@Override
public int m10() {

	return this.template.selectOne("mybatis.m10");
}

 

 

mybatis.xml

<select id="m10" resultType="Integer">
	select count(*) from tblMyBatis where age &lt;= 24
</select>

연산자를 <, > 대신에 &lt, &gt를 사용할 수 있다.

 

<select id="m10" resultType="Integer">
	select count(*) from tblMyBatis
	
	<![CDATA[
	where age <= 23
	]]>
</select>

또는 CDATA를 사용하는 방법을 사용할 수 있다. CDATA의 반대는 PCDATA이다.

시작태그부터 끝태그까지 모두 PCDATA이다. 여기서 일부 영역을 CDATA Section으로 묶게 되면 이 부분을 해석하지 않는다.

이는 일종의 Escape를 한 것으로 생각하면 된다.

 

24살 이하인 사람은 Isaac 1명 이므로 count로 1을 출력한다.

 

주소 검색

MyBatisController.java

@GetMapping(value="/m11.do")
public String m11(Model model, String word) {
	
	//주소 검색
	List<MyBatisDTO> list = this.dao.m11(word);

	model.addAttribute("list", list);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	List<MyBatisDTO> m11(String word);
	
}

 

MyBatisDAOImpl.java

@Override
public List<MyBatisDTO> m11(String word) {
	
	return this.template.selectList("mybatis.m11", word);
}

 

mybatis.xml

<select id="m11" parameterType="String" resultType="com.test.domain.MyBatisDTO">

<!-- select * from tblMyBatis
		where address like '%역삼%' -->

	select * from tblMyBatis
		where address like '%#{word}%'
		
</select>

위와 같이 쿼리를 만들면 오류가 발생한다.

이는 '%?%'로 바뀌어서 변수에 문자열 식별자로 홑따옴표 ''가 붙게 되어서 생기는 오류이다.

 

<select id="m11" parameterType="String" resultType="com.test.domain.MyBatisDTO">

<!-- select * from tblMyBatis
		where address like '%역삼%' -->

	select * from tblMyBatis
		where address like '%' || #{word} || '%'
		
</select>

변수로 바인딩해야 할 표현식을 문자열 안에 넣으면 안 되므로 분리시켜야 한다.

 

  • http://localhost:8090/mybatis/m11.do?word=역삼

주소가 역삼인 사람의 데이터만 출력된다.

 

동적 쿼리 1

  • m12.do?type=1 > select first_name from employees
  • m12.do?type=2 > select last_name from employees

상황(type)에 따라 실행되는 쿼리를 다르게 해 보자.

 

MyBatisController.java

@GetMapping(value="/m12.do")
public String m12(Model model, String type) {
	
	List<String> names = dao.m12(type);
	
	model.addAttribute("name", names);
	
	return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	List<String> m12(String type);
	
}

 

MyBatisDAOImpl.java

@Override
public List<String> m12(String type) {

	return this.template.selectList("mybatis.m12", type);
}

 

mybatis.xml

<select id="m12" parameterType="String" resultType="String">
	
	<!-- 
	<if test="type == 1">
		select first_name from employees
	</if>
	
	<if test="type == 2">
		select last_name from employees
	</if>
	-->

	select
		<if test="type == 1">first_name</if>
		<if test="type == 2">last_name</if>
	from employees
	
</select>

else나 else if는 없으므로 다중 if문을 쓰거나 choose when문을 사용한다.

 

type에 따라서 first_name 또는 last_name이 출력된다.

 

동적 쿼리 2

  • 이름: m13.do?column=name&word=Isaac
  • 주소: m13.do?column=address&word=역삼
  • 나이: m13.do?column=age&word=24

상황에 따라 다른 컬럼을 검색해 보도록 하자.

 

MyBatisController.java

@GetMapping(value="/m13.do")
public String m13(Model model, String column, String word) {

    HashMap<String,String> map = new HashMap<String,String>();
    map.put("column", column);
    map.put("word", word);

    List<MyBatisDTO> list = this.dao.m13(map);

    model.addAttribute("list", list);

    return "list";
}

 

MyBatisDAO.java

public interface MyBatisDAO {

	List<MyBatisDTO> m13(HashMap<String, String> map);
	
}

 

MyBatisDAOImpl.java

@Override
public List<MyBatisDTO> m13(HashMap<String, String> map) {

	return this.template.selectList("mybatis.m13", map);
}

 

mybatis.xml

<select id="m13" parameterType="map" resultType="com.test.domain.MyBatisDTO">

	select * from tblMyBatis
	
		<!--
		<if test="column == 'name'">
		where name = #{word}
		</if>
		
		<if test="column == 'address'">
		where address list '%' || #{word} || '%'
		</if>
		
		<if test="column == 'age'">
		<![CDATA[ where age <= #{word} ]]>
		</if>
		-->
		
		<choose>
			<when test="column == 'name'">where name #{word}</when>
			<when test="column == 'address'">where address list '%' || #{word} || '%'</when>
			<otherwise>
				<![CDATA[ where age <= #{word} ]]>
			</otherwise>
		</choose>
</select>

choose when문을 사용하면 otherwise로 else 처리를 할 수 있다.