학습목표
회원등록을 구현할 수 있다.
JDBC 프로그램 방법에 대해 설명할 수 있다.
회원등록
인증을 제공하거나 인증된 회원이 게시판을 이용하게 만드는 것
아이디, 비밀번호, 이름, 권한(관리자, 사용자) 등록
JDBC(Java Database Connectivity)
- 자바에서 데이터베이스를 연동하기 위한 표준 API로 가장 코어적인 프로그램
- 자바(Java) 응용프로그램이 관계형 데이터베이스(Relational Database)에 접속하기 위한 자바 Standard API
자바에서는 데이터베이스를 이용하기 위한 프로그램을 짜줘야 함
JDBC에서 제공되고 있는 API를 이용해 프로그램 작성
JDBC Vendor로 하여금 표준을 구현 및 확장하도록 해줌
인터페이스로 만든 이유는 데이터베이스별로 사용되는 클래스의 로직이 다르기 때문
JDBC 실행 흐름
- 응용프로그램에서는 데이터베이스가 인지하는 SQL 문을 String으로 만들어 JDBC의 함수에 인자로 전달함
- JDBC는 인자로 넘어온 SQL 문을 데이터베이스에 전달함
- 데이터베이스는 SQL문 처리 결과를 JDBC로 넘겨줌
- JDBC는 데이터베이스로부터 받은 처리 결과를 자바 객체 형태로 응용프로그램에 리턴함
1. JDBC Driver 로딩
- 각 데이터베이스를 위해 JDBC API를 구현한 Driver를 로딩함
- Driver 이름을 문자열로 지정해 로딩
Class.forName("org.h2.Drive")
2. Connection 객체 획득
- 데이터베이스 연결 객체
- DriverManager 클래스의 getConnection() 함수 이용
- 매개변수에 데이터베이스 URL과 인증 정보 입력
DriverManager.getConnection(url, "sa", "");
3. Statement 객체 획득
- 데이터베이스에서 실행할 SQL 문을 표현하는 객체
- Connection의 createStatement() 함수로 획득함
conn.createStatement();
4. SQL 문 실행
Statement 객체의 executeQuert() 혹은 executeUpdate() 함수를 이용해 실행함
- executeQuery() : SELECT SQL을 실행시키기 위한 함수
- executeUpdate() : SELECT를 제외한 나머지 SQL문을 실행시키기 위한 함수
stmt.executeUpdate();
함수가 2개인 이유? --> 리턴 타입의 차이 때문
5. 결과 값 이용
- SQL 문을 실행한 결과 값 획득
- executeQuert() 함수에 의한 결과 값은 ResultSet 타입으로 반환
ResultSet rs = stmt.executeQuery();
Select 된 ROW, COLUMN의 집합
실습
회원가입을 구현하기 전, 공통으로 필요한 것 작성
JDBC를 이용하려면 데이터베이스와 connection을 맺고 적절하게 close 해야 함
[공통내용 작성]
JDBCUtil.java
import java.sql.Connection;
import java.sql.DriverManager;
public class JDBCUtil {
public static Connection getConnection() {
try {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:tcp://localhost/~/test/h2";
// 이 url로 되어있는 DB에 connection 맺음, DB의 인증정보는 달라질 수 있음
return DriverManager.getConnection(url, "sa", "");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
getConnection 함수
- 어디에선가 커넥션이 필요하다고 하면 getConnection 함수를 호출하여, H2 데이터베이스와 connection 맺은 것을 return 시킴
- Driver 로딩 (Class.forName) - 드라이버 클래스명은 사용하는 데이터베이스에 따라 상이함
- url로 커넥션 맺은 내용을 함수의 return 값으로 제공
- 에러 발생시 에러 로그 찍도록 catch 문 작성
데이터베이스가 다 이용이 끝났을 때 close 하기 위한 static 함수 작성
package biz.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCUtil {
public static Connection getConnection() {
try {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:tcp://localhost/~/test/h2";
// 이 url로 되어있는 DB에 connection 맺음, DB의 인증정보는 달라질 수 있음
return DriverManager.getConnection(url, "sa", "");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void close(Statement stmt, Connection conn) {
try {
if(stmt != null)
stmt.close();
} catch (Exception e){
e.printStackTrace();
}finally {
stmt = null;
}
try {
if(conn != null)
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
finally {
conn = null;
}
}
}
함수를 호출하면 connection close, sttement close 시키는 역할
null이 아니라면 close하는 try-catch문으로 구성
close 함수를 오버로딩해서 매개변수 추가
--> Statement와 Connection만 매개변수로 들어올 수도 있고 ResultSet 객체가 전달될 수도 있어서
작성된 함수를 복사해서 추가한 뒤 매개변수에 ResultSet 객체 추가
package biz.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCUtil {
public static Connection getConnection() {
try {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:tcp://localhost/~/test/h2";
// 이 url로 되어있는 DB에 connection 맺음, DB의 인증정보는 달라질 수 있음
return DriverManager.getConnection(url, "sa", "");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void close(Statement stmt, Connection conn) {
try {
if(stmt != null)
stmt.close();
} catch (Exception e){
e.printStackTrace();
}finally {
stmt = null;
}
try {
if(conn != null)
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
finally {
conn = null;
}
}
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if(rs != null)
rs.close();
} catch (Exception e){
e.printStackTrace();
}finally {
rs = null;
}
try {
if(stmt != null)
stmt.close();
} catch (Exception e){
e.printStackTrace();
}finally {
stmt = null;
}
try {
if(conn != null)
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
finally {
conn = null;
}
}
}
connection 맺거나 close 할 때 공통으로 사용할 수 있는 클래스
다양한 유저 요청이 Controller에 들어감
여러 Controller들의 공통 타입을 위해서 Interface 선언
Controller.java
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Controller {
String handleRequest(HttpServletRequest request,
HttpServletResponse response);
}
클라이언트 요청 정보 처리를 위한 Controller의 공통 타입 목적의 인터페이스
Controller는 인터페이스를 implement 받고 handlerRequest를 구현해서 작성됨
HandlerMapping.java
클라이언트 요청 시 URL에 따라 실행되는 Controller가 등록된 곳
package controller;
import java.util.HashMap;
import java.util.Map;
public class HandlerMapping {
private Map<String, Controller> mappings;
public HandlerMapping() {
mappings = new HashMap<String, Controller>();
}
public Controller getController(String path) {
return mappings.get(path);
}
}
getController() : 함수를 호출하면 등록되어 있는 Controller를 리턴 시켜 주는 역할의 함수
클라이언트 요청 시 MVC 모델로 Controller 역할은 Servlet이 담당함
Servlet에서 HandlerMapping을 통해서 Controller를 선택해서 실행되는 구조
DispatcherServlet.java
package controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private HandlerMapping mapping;
// 생성자 부분에 로그 출력
public DispatcherServlet() {
super();
System.out.println("==>DispatcherServlet 생성");
}
// Servlet에 라이프 사이클 함수를 이용하기 위해 init 함수 작성
// 모든 요청 시에 이 Servlet이 실행되는데
// URL에 맞는 Controller는 HandlerMapping에 있다보니 초기화 필요
@Override
public void init() throws ServletException {
mapping = new HandlerMapping();
}
// 요청이 doGet으로 들어왔을때 process 함수 호출
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
// 요청이 doPost로 들어왔을때 request에 ChracterEncodingSet 지정
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("EUC-KR");
// 구체적인 처리는 process에서 진행
process(request, response);
}
protected void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 클라이언트 URL 분석을 위해 요청한 URL 값 추출
String uri = request.getRequestURI();
// 슬래시를 기준으로 맨 마지막이 path 정보임
String path = uri.substring(uri.lastIndexOf("/"));
// 이 path를 HandlerMapping에 요청하여 path에 맞는 Controller를 얻음
// Mapping에 path 정보로 등록되어 있는 Controller가 리턴됨
Controller ctrl = mapping.getController(path);
// Controller에 함수 호출
String viewPage = ctrl.handleRequest(request, response);
// 최종 유저에게 보여줘야 되는 view 화면으로 리다이렉트(Redirect)
RequestDispatcher rd = request.getRequestDispatcher(viewPage);
rd.forward(request, response);
}
}
모든 요청 시에 해당 Servlet이 실행되도록 제작함
web.xml 파일에 등록하기 위해 어노테이션 삭제
--> 어느 요청 시에 Servlet이 실행되는지 web.xml 파일로 확인함
--
먼저 프로젝트를 구성하기 위해서 공통적으로 필요한 내용을 작성함
DBMS가 필요할 때 사용되는 데이터베이스를 위한 JDBCUtil,
제어를 위한 Controller, HandlerMapping 작성 완료
--
[회원가입 구현]
DispatcherServlet이 모든 요청 시에 실행되게 만들어야 함
--> web.xml에 Servlet 등록
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>controller.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
확장자가 .do로 들어오는 모든 요청에 Servlet이 실행되도록 지정함
회원정보 표현을 위한 VO 클래스 생성
UserVO.java
package biz.user;
public class UserVO {
private String id;
private String password;
private String name;
private String role;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
// toString 함수를 오버라이드 받아서 데이터 출력되도록 구현함
@Override
public String toString() {
return "UserVO [id=" + id + ", password=" + password + ", name=" + name + ", role=" + role + "]";
}
}
한 명의 회원을 저장하기 위한 클래스
Source로 Setter, Getter 만들고 toString도 Source로 구현
DBMS 관련되어 있는 코드가 들어가는 클래스 생성
UserDAO.java
package biz.user;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import biz.common.JDBCUtil;
public class UserDAO {
// Connection 객체, SQL문 실행 객체, Select문 결과값 표현 객체
private Connection conn;
private PreparedStatement stmt;
private ResultSet rs;
private static String USER_INSERT=
"insert into users (id, password, name, role) "+
"values (?,?,?,?)";
// 유저 데이터를 저장하기 위해서 호출되는 함수, insertUser
public void insertUser(UserVO vo) {
try {
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(USER_INSERT);
stmt.setString(1, vo.getId());
stmt.setString(2, vo.getPassword());
stmt.setString(3, vo.getName());
stmt.setString(4, vo.getRole());
stmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.close(stmt, conn);
}
}
}
유저 업무를 진행하기 위한 DBMS 프로그램이 들어가는 곳
회원가입만 구현할 것이기 때문에 insertUser 함수만 작성함
클라이언트가 회원가입을 요청했을 때 실행될 Controller 생성
InsertUserController.java
package controller.user;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import biz.user.UserDAO;
import biz.user.UserVO;
import controller.Controller;
public class InsertUserController implements Controller{
@Override
public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 클라이언트가 회원가입을 요청했을때 넘어오는 데이터 추출(id, password, name, role)
String id = request.getParameter("id");
String password = request.getParameter("password");
String name = request.getParameter("name");
String role = request.getParameter("role");
UserVO vo = new UserVO();
vo.setId(id);
vo.setPassword(password);
vo.setRole(role);
// DAO가 정상적으로 구현돼있다면 DAO에 의해서 DB에 저장됨
UserDAO dao = new UserDAO();
dao.insertUser(vo);
// 화면을 출력할 view 페이지를 문자열로 지정함
return "login.html";
}
}
모든 Controller가 동일 타입으로 표현되어야 하므로, 이미 만들어져 있는 Controller를 구현함
webapp에 login.html 생성
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>로그인</h1>
<hr/>
<form action="Login.do" method="post">
<table border="1">
<tr>
<td>아이디</td>
<td><input type="text" name="id"/>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="password"/>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Login"/>
</tr>
</table>
</form>
<br/>
<a href="insertUser.html">회원가입</a>
</body>
</html>
EUC-KR로 했더니 한글깨짐 문제가 발생해서 UTF-8로 하니 해결됐다.
마지막으로 동일 위치에 insertUser.html 생성
insertUser.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>회원가입</h1>
<hr/>
<form action="insertUser.do" method="post">
<table border="1">
<tr>
<td>아이디</td>
<td><input type="text" name="id"/>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="password"/>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="name"/>
</tr>
<tr>
<td>권한</td>
<td>
<input type="radio" name="role" value="Admin" checked="checked"/> 관리자
<input type="radio" name="role" value="User"/> 사용자
</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="회원가입"/>
</tr>
</table>
</form>
<br/>
<a href="insertUser.html">회원가입</a>
</body>
</html>
유저가 회원가입 데이터를 입력한 다음 insert.do로 요청함
그 요청을 DispatcherServlet에서 받아서 HandlerMapping에 Controller 요청
HandllerMapping에 Controller 등록
mappings.put("/insertUser.do", new InsertUserController());
--
하.. 진짜 복잡하다.. 하나도 모르겠는데 일단 따라는 했지만 구동하기 왤케 무섭지
오류나면 어디서 어떻게 고쳐야할지 파일 많아서 까마득한데 제발 멀쩡히 돌아가주세요
--
login.html Run
회원가입 링크 눌러서 페이지 이동
버튼을 눌렀을 때 에러없이 넘어가면 테스트 성공
다 해놓고 불안해서 테스트하기 무섭다
정상적으로 데이터가 저장됐는지 확인하고 싶다면 H2의 DB console 창에서 확인 가능
왼쪽에서 USERS 클릭하면 자동으로 SQL문이 완성 -> 실행
습.. NAME 왜 null 나왔지...? 'bbb'가 찍혀야하는데..
이건 내일 찾아봐야지 ㅠ 어디가 문제여..
--
다시 살펴보니 insertUserController에서 UserVO 만들 때 name을 빠뜨렸다
UserVO vo = new UserVO();
vo.setId(id);
vo.setPassword(password);
vo.setName(name);
vo.setRole(role);
--
전에 정보 찾다 본건데 프로젝트를 할 때는 디자인을 제일 나중에 해야된다고 했었다.
오늘 해보면서 그 이유를 뼈저리게 느낄 수 있었다 ㅎ 이건 진짜 간단한 로직일텐데.. 이것도 이렇게 복잡하면
여러 디자인이 들어간 화면 구성하려면 진짜 시작도 못 할듯..
--
VO, DAO 정처기할때 잠깐 봤는데 생소해서 좀 찾아봤다.
https://dkswnkk.tistory.com/500
'온라인 강좌 > JSP & Servlet 활용' 카테고리의 다른 글
21차시 Project - 게시판 등록, 조회 (0) | 2023.07.05 |
---|---|
20차시 Project - 인증 (0) | 2023.07.05 |
18차시 Project - 환경 구축 (0) | 2023.07.04 |
17차시 Spring Boot (0) | 2023.07.04 |
16차시 MVC Model (0) | 2023.07.04 |