MyBatis의 기본적인 CRUD에 대해 알아보자
MyBatis CRUD를 위한 환경설정
✔️ WEB-INF/views 일반 폴더 생성
✔️ WEB-INF/views/main.jsp 생성
💻 main.jsp
<body>
여기는 main.jsp야
</body>
현재 기본 index.jsp는 나옴!
![](https://blog.kakaocdn.net/dn/uWWL8/btrQVaEeO2m/CLaQRYZGUICkp6Upehkff0/img.png)
http://localhost:8888/mybatis/WEB-INF/views/main.jsp 로 직접 접근 시
![](https://blog.kakaocdn.net/dn/UBrQU/btrQUbXZy1J/6QHJgs8kLwvnSc2R9Ma7o1/img.png)
👉🏻 WEB-INF 폴더는 WAS가 관리하므로 직접 접근이 불가함!
* 앞으로 Spring 프로젝트 구조상 모든 화면 관련 파일들은 WEB-INF 폴더 내부의 views 폴더에 들어가 있을 것!
👉🏻 WEB-INF 폴더는 WAS가 전적으로 관리하기 때문에 일반적인 브라우저에서 url 주소로 접속이 불가하다는 특징이 있음
👉🏻 contextRoot로 요청할 시 index.jsp 페이지가 로딩되자마자 WEB-INF/views/sample.jsp로 곧바로 포워딩시킬 것!
💻 index.jsp
<body>
여기는 index.jsp야
<jsp:forward page="WEB-INF/views/main.jsp" />
</body>
이제 포워딩 완료되어 메인페이지 == main.jsp 화면의 구조가 될 것!
✔️ 필요한 패키지 및 폴더 생성
패키지
com.kh.mybatis.member.controller
com.kh.mybatis.member.model.vo
com.kh.mybatis.member.model.service
com.kh.mybatis.member.model.dao
com.kh.mybatis.board.controller
com.kh.mybatis.board.model.vo
com.kh.mybatis.board.model.service
com.kh.mybatis.board.model.dao
com.kh.mybatis.common.model.vo
com.kh.mybatis.common.filter
일반 폴더
webabb/WEB-INF/views/board
webabb/WEB-INF/views/common
webabb/WEB-INF/views/member
✔️ menubar.jsp 생성 후 VSCode에서 폼 만들기
💻 menubar.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>
<style>
body {
color : midnightblue;
}
.login-area a {
text-decoration : none;
color : black;
font-size : 12px;
}
.nav-area {
background-color: midnightblue;
color : white;
height : 50px;
}
.menu {
display : table-cell;
width : 250px;
height : 50px;
vertical-align : middle;
font-size : 20px;
font-weight : bold;
}
.menu:hover {
background-color: darkslateblue;
cursor : pointer;
}
</style>
</head>
<body>
<h1 align="center">Welcome to MyBatis World</h1>
<br>
<div class="login-area" align="right">
<c:choose>
<c:when test="${ true }">
<!-- 로그인 전에 보여지는 로그인 폼 -->
<form action="" method="post">
<table>
<tr>
<td>아이디</td>
<td><input type="text" name="userId" required></td>
<td rowspan="2">
<button type="submit" style="height:50px;">로그인</button>
</td>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="userPwd" required></td>
<td></td>
</tr>
<tr>
<td colspan="3" align="center">
<a href="">회원가입</a> |
<a href="">비밀번호 찾기</a>
</td>
</tr>
</table>
</form>
</c:when>
<c:otherwise>
<!-- 로그인 후에 보여지는 프로필 화면 -->
<div>
<!-- table>(tr>td*2)*2 2행 2열 테이블 애밋문법 -->
<table>
<tr>
<td colspan="2">
<h3>xxx님 환영합니다 ^^</h3>
</td>
</tr>
<tr>
<td><a href="">마이페이지</a></td>
<td><a href="">로그아웃</a></td>
</tr>
</table>
</div>
</c:otherwise>
</c:choose>
</div>
<br>
<div class="nav-area" align="center">
<div class="menu">HOME</div>
<div class="menu">공지사항</div>
<div class="menu">게시판</div>
<div class="menu">ETC</div>
</div>
</body>
</html>
💻 main.jsp
<body>
<!-- 여기는 main.jsp야 -->
<jsp:include page="common/menubar.jsp" />
</body>
🔥 회원가입 기능 🔥
💻 menubar.jsp
<td colspan="3" align="center">
<!-- contextRoot 기준으로 내 경로 마지막 값만 기술하는 상대 경로 방식 -->
<a href="enrollForm.me">회원가입</a> |
<a href="">비밀번호 찾기</a>
</td>
💻 MemberEnrollFormController Servlet 생성
url mapping: /enrollForm.me
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("WEB-INF/views/member/memberEnrollForm.jsp").forward(request, response);
}
💻 memberEnrollForm.jsp 생성
+ form 태그 안에 아래 파일을 복붙 할 것!
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<jsp:include page="../common/menubar.jsp" />
<div class="outer">
<br>
<h1 align="center">회원가입</h1>
<!--
현재 나의 url: http://localhost:8888/mybatis/enrollForm.me
요청하고자 하는 url: http://localhost:8888/mybatis/insert.me
절대 경로 방식: /mybatis/insert.me
상대 경로 방식: insert.me
-->
<form action="insert.me" method="post">
<!-- 회원가입form안에.txt -->
<table align="center">
<tr>
<td>* ID</td>
<td><input type="text" name="userId" required></td>
</tr>
<tr>
<td>* PWD</td>
<td><input type="password" name="userPwd" required></td>
</tr>
<tr>
<td>* NAME</td>
<td><input type="text" name="userName" required></td>
</tr>
<tr>
<td> EMAIL</td>
<td><input type="email" name="email"></td>
</tr>
<tr>
<td> BIRTHDAY</td>
<td><input type="text" name="birthday" placeholder="생년월일(6자리)"></td>
</tr>
<tr>
<td> GENDER</td>
<td align="center">
<input type="radio" name="gender" value="M" checked> 남
<input type="radio" name="gender" value="F"> 여
</td>
</tr>
<tr>
<td> PHONE</td>
<td><input type="text" name="phone" placeholder="-포함"></td>
</tr>
<tr>
<td> ADDRESS</td>
<td><input type="text" name="address"></td>
</tr>
</table>
<br>
<div align="center">
<button type="reset">초기화</button>
<button type="submit">회원가입</button>
</div>
</form>
</div>
</body>
</html>
+ 💻 menubar.jsp
menubar를 include 했기 때문에 이곳에서 스타일 설정을 해 줘도 회원가입 페이지에 적용됨!
<style>
/* menubar.jsp에 스타일 단 한번만 정의하고 다 include 해서 쓸 것 */
.outer {
background-color: midnightblue;
color : white;
width : 900px;
height : 600px;
margin : auto;
margin-top : 50px;
}
</style>
💻 MemberInsertController Servlet 생성
url mapping: /insert.me
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// post 방식의 경우 인코딩 설정 먼저
request.setCharacterEncoding("UTF-8");
// 요청 시 전달값 뽑기
String userId = request.getParameter("userId");
String userPwd = request.getParameter("userPwd");
String userName = request.getParameter("userName");
String email = request.getParameter("email");
String birthday = request.getParameter("birthday");
String gender = request.getParameter("gender");
String phone = request.getParameter("phone");
String address = request.getParameter("address");
// VO 객체로 가공
Member m = new Member(userId, userPwd, userName, email, birthday, gender, phone, address);
// 서비스로 VO 객체를 넘겨서 요청 후 결과 받기
int result = new MemberServiceImpl().insertMember(m);
// 결과에 따른 응답페이지 지정
if(result > 0) { // 성공 => 메인 페이지로 url 재요청
response.sendRedirect(request.getContextPath());
} else { // 실패 => 에러문구를 담아서 에러페이지로 포워딩
request.setAttribute("errorPage", "회원가입 실패");
request.getRequestDispatcher("WEB-INF/views/common/errePage.jsp").forward(request, response);
}
}
💻 Member 클래스 생성
경로: com\kh\mybatis\board\model\vo\Member.java
package com.kh.mybatis.member.model.vo;
import java.sql.Date;
public class Member {
// 필드부
private int userNo; // USER_NO NUMBER PRIMARY KEY,
private String userId; // USER_ID VARCHAR2(30) NOT NULL UNIQUE,
private String userPwd; // USER_PWD VARCHAR2(100) NOT NULL,
private String userName; // USER_NAME VARCHAR2(15) NOT NULL,
private String email; // EMAIL VARCHAR2(100),
private String birthday; // BIRTHDAY VARCHAR2(6),
private String gender; // GENDER VARCHAR2(1) CHECK (GENDER IN('M', 'F')),
private String phone; // PHONE VARCHAR2(13),
private String address; // ADDRESS VARCHAR2(100),
private Date enrollDate; // ENROLL_DATE DATE DEFAULT SYSDATE,
private Date modifyDate; // MODIFY_DATE DATE DEFAULT SYSDATE,
private String status; // STATUS VARCHAR2(1) DEFAULT 'Y' CHECK (STATUS IN('Y', 'N'))
// 생성자부
public Member() { }
public Member(int userNo, String userId, String userPwd, String userName, String email, String birthday,
String gender, String phone, String address, Date enrollDate, Date modifyDate, String status) {
super();
this.userNo = userNo;
this.userId = userId;
this.userPwd = userPwd;
this.userName = userName;
this.email = email;
this.birthday = birthday;
this.gender = gender;
this.phone = phone;
this.address = address;
this.enrollDate = enrollDate;
this.modifyDate = modifyDate;
this.status = status;
}
// 회원가입용 생성자
public Member(String userId, String userPwd, String userName, String email, String birthday, String gender,
String phone, String address) {
super();
this.userId = userId;
this.userPwd = userPwd;
this.userName = userName;
this.email = email;
this.birthday = birthday;
this.gender = gender;
this.phone = phone;
this.address = address;
}
// 메소드부 (Alt + Shift + S: Source 타고 들어가는 단축키)
public int getUserNo() {
return userNo;
}
public void setUserNo(int userNo) {
this.userNo = userNo;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getEnrollDate() {
return enrollDate;
}
public void setEnrollDate(Date enrollDate) {
this.enrollDate = enrollDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Member [userNo=" + userNo + ", userId=" + userId + ", userPwd=" + userPwd + ", userName=" + userName
+ ", email=" + email + ", birthday=" + birthday + ", gender=" + gender + ", phone=" + phone
+ ", address=" + address + ", enrollDate=" + enrollDate + ", modifyDate=" + modifyDate + ", status="
+ status + "]";
}
}
💻 MemberDao 클래스 생성
경로: com\kh\mybatis\board\model\dao\MemberDao.java
*기존의 JDBC 코드
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertMember");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, m.getUserId());
...
result = pstmt.executeUpdate();
} catch(xxx) {
} finally {
JDBCTemplate.close(pstmt);
}
return result;
sqlSession 객체에서 제공하는 메소드를 통해 sql문을 찾고 실행해서 결과까지 받아 볼 수 있음
[ 표현법 ]
sqlSession.sql문종류에맞는메소드("매퍼파일의namespace.해당sql문의id속성값", sql문을완성시킬데이터값);
👉🏻 INSERT 구문을 실행하고자 할 때 : sqlSession.insert() 메소드
👉🏻 UPDATE 구문을 실행하고자 할 때 : sqlSession.update() 메소드
👉🏻 DELETE 구문을 실행하고자 할 때 : sqlSession.delete() 메소드
👉🏻 SELECT문을 실행할 수 있는 메소드
(1) 단일 행 조회: selectOne() 메소드
(2) 여러 행 조회: selectList() 메소드
👉🏻 해당 sql문이 미완성된 상태가 아니라면 "sql문을완성시킬데이터값" 매개변수는 생략 가능
public int insertMember(SqlSession sqlSession, Member m) {
// int result = sqlSession.insert("memberMapper.insertMember", m);
// return result;
// 이렇게 1줄로도 표현 가능!
return sqlSession.insert("memberMapper.insertMember", m);
}
💻 MemberService 인터페이스 생성
경로: com\kh\mybatis\board\model\service\MemberService.java
기업에서는 Service단을 만들 때 일반 클래스로 만들지 않고 인터페이스(모든 필드 상수 필드, 모든 메소드가 추상메소드)로 정의함
(기업에 따라서 Dao도 인터페이스로 만드는 곳이 있음)
👉🏻 프로젝트 매니저 등이 인터페이스로 필요한 메소드 틀을 정의해 두면 실무자가 틀(메소드) 중에서 필요한 것을 가지고 와 작업함으로써 여러 개발자가 통일성 있게 작업 가능해짐!
🗣️ 필요한 메소드는 뭐가 있을까?
🗣️ 어떻게 넘기고 받을까?
1. 회원가입용 메소드
public int insertMember(Member m); // 아이디 등 중복확인 후 insert 하고 나면 결과 int값 (0 또는 1 반환)
2. 로그인용 메소드
public Member loginMember(Member m); // 로그인 후에는 해당 회원의 정보를 세션으로 보내서 계속 활용할 수 있게
3. 회원 정보 수정용 메소드
public int updateMember(Member m); // 비밀번호 등 확인 후 update 하고 나면 결과 int값 (0 또는 1 반환)
4. 회원 탈퇴용 메소드
public int deleteMember(Member m); // delete이지만 sql에서는 update 구문으로 진행! 결과 int값 (0 또는 1 반환)
5. 아이디 중복 체크용 메소드
public int idCheck(String checkId);
...
package com.kh.mybatis.member.model.service;
import com.kh.mybatis.member.model.vo.Member;
// 인터페이스 : 상수 필드(public static final) + 추상 메소드 (public abstract, 몸통부가 없는 메소드)
public interface MemberService {
// 회원가입용 메소드
/* public abstract */ int insertMember(Member m);
// 인터페이스는 어차피 추상 메소드만 정의가 가능하기 때문에 public abstract는 묵시적으로 생략!
// 로그인용 메소드
Member loginMember(Member m);
// 회원 정보 수정용 메소드
int updateMember(Member m);
// 회원 탈퇴용 메소드
int deleteMember(Member m);
// ...
// 보통 설계 단계에서 다음과 같이 인터페이스로 추상 메소드만 설계해 두고
// 실제 개발 단계에서 해당 인터페이스를 구현하는 구현체 클래스를 만들어 메소드를 완성시켜 사용함!
}
💻 MemberServiceImpl 클래스 생성
MemberService 인터페이스를 상속받아 구현할 Service
인터페이스를 상속받아 구현할 클래스에는 뒤에 Impl을 붙이는 것이 관례!
처음 빨간 줄 뜨는 이유는 "너 인터페이스를 상속하면서 인터페이스에 있는 메소드를 사용하지 않았어!" 라고 알려 주는 꼴임
👉🏻 메소드를 추가해 주면 사라짐!
[참고용] 기본 틀
package com.kh.mybatis.member.model.service;
import com.kh.mybatis.member.model.vo.Member;
public class MemberServiceImpl implements MemberService {
@Override
public int insertMember(Member m) {
// TODO Auto-generated method stub
return 0;
}
@Override
public Member loginMember(Member m) {
// TODO Auto-generated method stub
return null;
}
@Override
public int updateMember(Member m) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int deleteMember(Member m) {
// TODO Auto-generated method stub
return 0;
}
}
* 기존 JDBC 때의 흐름
Connection conn = JDBCTemplate.getConnection();
int result = new MemberDao().insertMember(conn, m);
if(result > 0) {
JDBCTemplate.commit();
} else {
JDBCTemplate.rollback();
}
JDBCTempalte.close(conn);
return result;
@Override
public int insertMember(Member m) {
// 1. SqlSession 객체 생성
SqlSession sqlSession = Template.getSqlSession();
// 2. 만들어진 SqlSession 객체와 전달값을 DAO단으로 넘기면서 요청 후 결과 받기
int result = new MemberDao().insertMember(sqlSession, m);
// 3. 전달받은 값 기준으로 트랜잭션 처리
if(result > 0) { // 성공(커밋)
sqlSession.commit();
}
// 고민해 볼 것: 이 시점에서 result == 0 이라면 아무것도 insert 되지 않은 것
// 현재 MEMBER 테이블의 내용물에는 변화가 없음
// 단일 프로세스: 한 트랜잭션의 쿼리문이 한 개일 경우에는 실패 시 롤백을 "굳이 하지 않아도" 됨! (== 롤백 구문 작성해도 문제는 없음)
// => 한 트랜잭션의 쿼리문이 여러 개일 경우에는 반드시 롤백 처리 구문을 작성해야 함!
// 4. sqlSession 객체 반납
sqlSession.close();
// 5. 결과 반환
return result;
}
💻 member-mapper.xml 생성
경로: resources/mappers/member-mapper.xml
이 파일을 사용하기 위해서는 mybatis-config.xml에 mapper 등록을 해 줘야 함!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="memberMapper">
<!--
별칭 지정 전(풀 클래스명)
<insert id="insertMember" parameterType="com.kh.mybatis.member.model.vo.Member">
-->
<insert id="insertMember" parameterType="member">
INSERT INTO MEMBER (USER_NO
, USER_ID
, USER_PWD
, USER_NAME
, EMAIL
, BIRTHDAY
, GENDER
, PHONE
, ADDRESS)
VALUES (SEQ_UNO.NEXTVAL
, #{userId}
, #{userPwd}
, #{userName}
, #{email}
, #{birthday}
, #{gender}
, #{phone}
, #{address})
</insert>
</mapper>
👉🏻 4째 줄까지는 통일! (마이바티스 개발자 문서에서 가지고 옴)
👉🏻 #{변수명}으로 쓰면 동적 바인딩(문자열이면 알아서 따옴표 붙임)
👉🏻 ${변수명}으로 쓸 경우 정적 바인딩! (문자열일 경우 홑따옴표(')를 붙이지 않을 경우 오류)
👉🏻 namespace: 해당 mapper 파일의 고유한 별칭
👉🏻 resultMap: 마이바티스의 핵심 기능 중 하나
ResultSet으로부터 조회된 컬럼값을 하나씩 뽑아서 내가 지정한 VO 객체의 각 필드에 담는 (맵핑) JDBC 코드를 줄여주는 역할 수행
<resultMap id="식별자" type="조회된결과를담아서반환하고자하는VO객체타입(풀클래스명)혹은별칭">
<result column="조회결과를뽑고자하는DB컬럼명" property="해당결과를받고자하는필드명" />
<result column="" property="" />
<result column="" property="" />
... 매칭시킬 만큼 지정!
</resulMap>
✔️ resultMap은 관례상 가장 상단에 쓰는 것을 권장
✔️ property 속성에 작성하는 필드명은 내부적으로 setter 메소드에 의해 필드값이 대입되는 구조이므로 오타 주의!
* mapper에서 DML (INSERT, UPDATE, DELETE) 문을 실행할 수 있는 메소드
<insert id="각sql문들의식별자" parameterType="전달받을자바타입(풀클래스)혹은별칭">
쿼리문 작성
</insert>
<update></update>
<delete></delete>
👉🏻 parameterType 속성은 전달받을 값이 없다면 생략 가능(완성된 쿼리문일 경우)
👉🏻 update, delete 태그도 마찬가지로 작성하면 됨
👉🏻 DML 문의 경우 어차피 실행 결과가 처리된 행의 개수(int)이기 때문에 resultType 또는 resultMap에 대한 속성은 안 써도 됨
* mapper에서 DML (SELECT) 문을 실행할 수 있는 메소드
<select id="각sql문들의 식별자" parameterType="전달받을자바타입(풀클래스명)혹은별칭"
resultType="조회결과를 반환하고자하는자바타입(내장타입)" 또는 resultMap="조회결과를뽑아서맵핑할resultMap의id값">
쿼리문 작성
</select>
👉🏻 parameterType 속성은 전달받을 값이 없다면 생략 가능(완성된 쿼리문일 경우)
👉🏻 select 태그의 경우 반드시 resultType(자바에서 제공하는 자료형으로 가공하고자 할 때)
또는 resultMap(내가 만든 VO 클래스로 가공하고자 할 때) 으로 결과값에 대한 타입을 지정해야 함
mapper.xml 파일의 ?(물음표 구멍)을 메꿀 때는 $가 아닌 # 쓰자!
(== 내부적으로 해당 getter 메소드가 호출)
parameterType이 VO일 경우: #{필드명}
👉🏻 $가 아닌 #임을 주의하고, 대괄호와 필드명 사이에 띄어쓰기 하면 안 됨!
parameterType이 단일 변수값일 경우(int일 경우): #{매개변수명}
parameterType이 여러 개일 경우(HashMap에 담긴 값으로 구멍을 메꿀 경우): #{키값}
👉🏻 #{변수명}으로 쓰면 동적 바인딩(문자열이면 알아서 따옴표 붙임)
👉🏻 ${변수명}으로 쓸 경우 정적 바인딩! (문자열일 경우 홑따옴표(')를 붙이지 않을 경우 오류)
✔️ ${변수명}을 쓸 때 해커가 DROP TABLE MEMBER를 쓴다면? 테이블 삭제될 위험 있음 (= SQL Injection)
💻 mybatis-config.xml에 member-mapper.xml 등록
<mappers>
<mapper resource="/mappers/member-mapper.xml" />
</mappers>
💻 mybatis-config.xml에 별칭 등록
<typeAliases>
<typeAlias type="com.kh.mybatis.member.model.vo.Member" alias="member" />
</typeAliases>
❗️❓ 주의사항
Dao에서의 sql을 완성시킬 데이터 값
mapper.xml의 parameterType에 대한 값은 반드시 일치해야 함!
💻 errorPage.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<jsp:include page="menubar.jsp" />
<h1 align="center" style="color:red;">${ errorMsg }</h1>
</body>
</html>