🌿쿼리 경우의 수
1. 반환값(X), 매개변수(X)
2. 반환값(X), 매개변수(O)
3. 반환값(O), 매개변수(X)
- 반환값: 단일값
- 반환값: 레코드 1줄
- 반환값: 레코드 N줄
4. 반환값(O), 매개변수(O)
- 반환값: 단일값
- 반환값: 레코드 1줄
- 반환값: 레코드 N줄
이전 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 <= 24
</select>
연산자를 <, > 대신에 <, >를 사용할 수 있다.
<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 처리를 할 수 있다.