1. MyBatis 프레임워크란?
2. MyBatis 프레임워크의 동작 방식
3. Spring Boot에서 MyBatis 프레임워크 사용하기
4. 최종 결과물
5. 마무리
Mybatis는 그러한 문제를 해결하기위해 개발된 퍼시스턴스 프레임워크(Persistence Framework) 이며 JDBC의 기능을 자동화했기에 사용하기 쉽고, SQL코드를 분리하여 사용할 수 있어 RDBMS에 엑세스 하는 작업을 캡슐화 할 수 있다. (초기 Mybatis 역시 XML에 의존적인 관계였으나 3.0버전부터는 Class에 어노테이션을 사용하는 방법으로 Query를 분리해서 관리할 수 있다.)
영속성에 대한 개념이 이해가 된다면 퍼시스턴스 프레임워크는 쉽게 이해할 수 있다. 퍼시스턴스 프레임워크는 자료를 데이터베이스에 저장하는 과정을 도와주고 자동화하는 소프트웨어이다.
인터넷을 찾아보면 MyBatis를 설명하는 글들을 많이 있는데, 종종 MyBatis를 ORM(Object Relational Mapping)이라고 소개한다. 그러나 면밀히 말하면 MyBatis는 ORM이 아니고 SQL Mapper이다. 그렇다면 ORM과 SQL Mapper는 무엇일까?
이후 유저가 요청할때 (4)번부터 (10)번까지의 과정이 실행되는데 SqlSessionFactory객체를 통해 각각의 요청에 맞는 SqlSession객체를 생성하는데 이때에 SqlSessionFactory가 DB의 정보가 담겨 있는 DataSource를 SqlSession에 전달해줌으로 DB연동이 가능해진다. (SqlSession에는 설정값도 같이 가지고 있다.) 또한 Mapper Interface에 등록된 Query를 실행하여 결과를 받고 SqlSession에 ResultSet 타입으로 담아서 가져온다. MyBatis는 이 ResultSet을 설정된 매핑 규칙에 따라 Java 객체로 변환하여 반환한다.
DataSource는 JDBC의 Connection을 처리하는 기능을 가지고 있다. DataSource는 JNDI(Java Naming and Directory Interface) 라는 이름과 디렉토리를 바인딩하는 기술로 DB와 관련된 정보가 담겨 있는데 해당 정보를 기반으로 물리적인 DB에 접근 할 수 있게 한다.
참고 : Oracle - JNDI 자원
오늘은 num, name, addr 필드만 있는 mem table을 사용해서 CRUD를 구현해 보려고 한다.
앞서 MyBatis 프레임워크의 동작 방식 파트에서 설명했듯 MyBatis는 Sql의 쿼리문을 Mapper Interface에 담아 보관하고 할당된 객체를 실행함으로 쿼리의 결과값을 반환하기 때문에 Mapper Interface를 설정해줘야 한다.
Spring Boot에서는 어노테이션을 사용하면 상당히 쉽게 Mapper Interface를 지정할수 있는데 @mapper 어노테이션을 사용하면 된다.
이후 쿼리의 성격에 따라 @Select, @Insert, @Update, @Delete와 세부 쿼리를 작성해 주면 된다.
package pack.model;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import pack.controller.MemBean;
@Mapper
public interface MemMapperInter {
@Select("SELECT num, name, addr FROM mem")
List<MemDto> getAll();
@Select("SELECT num, name, addr FROM mem WHERE num=#{num}")
MemDto getPart(String num);
@Insert("INSERT INTO mem(num, name, addr) VALUES(#{num}, #{name}, #{addr})")
int insertData(MemBean bean);
@Update("UPDATE mem SET name=#{name}, addr=#{addr} WHERE num=#{num}")
int updateData(MemBean bean);
@Delete("DELETE FROM mem WHERE num=#{num}")
int deleteData(String num);
}
Thymeleaf에서 제공하는 th:field를 사용하면 server side에서 render시에 해당 값으로 name과 value, id 속성으로 변환되어 client side로 전송된다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form th:action="@{/update}" method="post" th:object="${dto}">
번호 : <input type="text" name="num" readonly="readonly" th:field="*{num}"><br/>
이름 : <input type="text" name="name" th:field="*{name}"><br/>
주소 : <input type="text" name="addr" th:field="*{addr}"><br/>
<button>등록</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>멤버 목록</h3>
<a th:href="@{/insert}">멤버 추가</a>
<table border="1">
<tr>
<th>번호</th>
<th>이름</th>
<th>주소</th>
<th>수정 / 삭제</th>
</tr>
<th:block th:if="${datas.size} >0">
<tr th:each="dto:${datas}">
<td>[[${dto.num}]]</td>
<td>[[${dto.name}]]</td>
<td>[[${dto.addr}]]</td>
<td><a th:href="@{/update(num=${dto.num})}">수정</a> / <a th:href="@{/delete(num=${dto.num})}">삭제</a></td>
</tr>
<tr>
<td colspan="5" style="text-align: center; font-weight: bold">[[|총
멤버 수 : ${datas.size}|]]</td>
</tr>
</th:block>
</table>
<a th:href="@{/}">메인으로 돌아가기</a>
</body>
</html>
package pack.controller;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import pack.model.MemDao;
import pack.model.MemDto;
@Controller
public class MemController {
@Autowired
private MemDao memDao;
@GetMapping("/")
public String mainPage() {
return "index";
}
@GetMapping("/memlist")
public String listProcess(Model model) {
ArrayList<MemDto> list = (ArrayList<MemDto>) memDao.getDataAll();
model.addAttribute("datas", list);
return "list";
}
@GetMapping("/insert")
public String ins() {
return "insert";
}
@PostMapping("/insert")
public String ins(Model model, MemBean bean) {
boolean b = memDao.insertData(bean);
System.out.println(b);
if(b) return "redirect:/memlist";
else return "redirect:/error";
}
@GetMapping("/error")
public String err() {
return "error";
}
@GetMapping("/update")
public String update(Model model, MemBean bean) {
MemDto dto = memDao.getData(bean.getNum());
model.addAttribute("dto", dto);
return "update";
}
@PostMapping("/update")
public String processUpdate(Model model, MemBean bean) {
boolean b = memDao.updateData(bean);
if(b) return "redirect:/memlist";
else return "redirect:/error";
}
@GetMapping("/delete")
public String deleteProcess(Model model, @RequestParam("num") String num) {
boolean b = memDao.deleteData(num);
if(b) return "redirect:/memlist";
else return "redirect:/error";
}
}
package pack.model;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestParam;
import pack.controller.MemBean;
@Repository
public class MemDao {
@Autowired
private MemMapperInter mapperInter;
// 전체 데이터 찾기
public List<MemDto> getDataAll() {
List<MemDto> list = mapperInter.getAll();
return list;
}
// 수정 삭제 대상 자료 읽기
public MemDto getData(String num) {
MemDto dto = mapperInter.getPart(num);
return dto;
}
// 추가
public boolean insertData(MemBean bean) {
// 번호 자동 증가 또는 번호 중복 확인 작업 이 꼭 필요하다! : but 이번 프로젝트는 생략
try {
int re = mapperInter.insertData(bean);
if (re > 0) return true;
else return false;
} catch (Exception e) {
System.out.println("insertData : " + e);
return false;
}
}
// 수정
public boolean updateData(MemBean bean) {
try {
int re = mapperInter.updateData(bean);
if (re > 0) return true;
else return false;
} catch (Exception e) {
System.out.println("insertData : " + e);
return false;
}
}
// 삭제
public boolean deleteData(String num) {
try {
int re = mapperInter.deleteData(num);
if (re > 0) return true;
else return false;
} catch (Exception e) {
System.out.println("insertData : " + e);
return false;
}
}
}