53일차 공부 DB연동
** 데이터 베이스 연동 준비
=> 데이터베이스에 접속
=> 프로그래밍 언어와 연동에 필요한 드라이버
1. Dynamic Web Project 생성
=> 프로젝트 설정 파일인 web.xml 파일이 포함되도록 생성
=> servlet-api.jar 파일과 jstl.jar 파일을 WebContet/WEB-INF/lib 디렉토리에 복사
servler-api.jar : JDK SE 버전을 설치한 상태에서 HttpServlet 클래스를 사용하기 위해서
jstl.jar : jsp 페이지에서 if 나 for 를 java 코드를 이용하지 않고 사용하기 위해서
2, JDBC
=> Java를 이용해서 데이터베이스에 접속하는 방식
3. JDBC를 사용하는 방법
1) JDK가 제공하는 API를 이용하는 방법
=> Connection, Statement(PreparedStatement, CallableStatement), ResultSet을 이용
2) 프레임워크를 이용하는 방법
=> JPA(Hibernate - SI를 제외한 전분야에서 이용), MyBatis(공공기관 SI에서 주로 이용)
Spring의 JDBC프레임워크(공부할 때만 이용)
4. 연동 방법
1) 데이터베이스 드라이버를 애플리케이션에 사용할 수 있도록 복사
=> 일반 Application의 경우는 build path에 복사
=> Web Application의 경우는 WebContent/WEB-INF/lib 디렉토리에 복사
=> maven의 경우는 porm.xml에 작성 : Spring
=> gradle의 경우는 json 파일에 작성 : Android
2) Driver Class를 로드
=> 한번만 수행 - 일반 Application에서는 하지 않아도 됨
Class.forName(String driverClassName)
=> 데이터베이스 종류마다 다름
MySQL : com.mysql.jdbc.Driver
Oracle : oracle.jdbc.driver.OracleDriver
3) 데이터베이스 연결
Connection 연결변수명 = DriverManager.getConnection(String url, String account, String password);
=> url은 데이터베이스 종류마다 다르게 설정
MySQL : jdbc:mysql://HOST:PORT/DBNAME (대문자는 바뀌는 부분)
Oracle : jdbc:oracle:thin:@HOST:PORT:SID - Oracle 11g 까지 기본
jdbc:oracle:thin:@HOST:PORT:SERVICENAME - Oracle 12g 이후 기본
4) 데이터베이스 사용
=> Statement를 이용해서 SQL을 실행
=> int 나 ResultSet으로 실행 결과를 받아서 사용
5) 사용한 자원을 반납 - close()
CREATE table Item(
code int PRIMARY KEY auto_increment,
title char(50) NOT NULL,
category varchar(50),
description text
)engine=innodb DEFAULT charset=utf8;
--샘플 데이터 작성
insert into Item values(1, 'Java', 'language','오픈소스 라이브러리가 많은 범용 프로그래밍 언어');
insert into Item values(2, 'Eclipse', 'IDE', '프로그래밍을 편리하게 할 수 있도록 해주는 오픈 소스 프로그램');
insert into Item values(3, 'Tomcat', 'Web Application Server','apache web server를 이용하는 오픈 소스 프로그램');
insert into Item values(4, 'Oracle', 'DataBase', '대기업이나 공공기관이 주로 사용하는 관계형 DBMS');
insert into Item values(5, 'MySQL', 'DataBase', '오픈 소스 기반의 관계형 DBMS - Maria DB와 거의 동일');
insert into Item values(6, 'MongoDB', 'DataBase', '가장 많이 언급되는 NoSQL');
insert into Item values(7, 'DBeaver', 'DB Tool', '데이터베이스 사용을 쉽게 해주는 프로그램');
insert into Item values(8, 'Spring', 'Framework', '자바 프레임워크');
insert into Item values(9, 'Android', 'Mobile OS', 'OHA 컨소시엄이 만든 Linux 기반의 모바일 운영체제');
insert into Item values(10, 'Android Studio', 'IDE', 'Android 용 프로그램을 개발할 수 있도록 해주는 프로그램');
insert into Item values(11, 'iOS', 'Mobile OS', 'Apple이 만든 unix 기반의 모바일 운영체제');
insert into Item values(12, 'Xcode', 'IDE', 'iOS 및 Mac 용 프로그램을 개발할 수 있도록 해주는 프로그램');
commit;
select * from Item;
6. 데이터베이스 드라이버를 프로젝트에 복사
=> WebContent/WEB-INF/lib 디렉토리에 복사
7. 연동할 데이터베이스 테이블을 표현할 DTO 클래스를 생성
=> domain.ITEM
package domain;
package javaweb0622;
public class Domain {
//클라이언트용 ㅡ프로그램이면 속성 앞의 접근 지정자를 public으로 하고
//생성자만 만들고 접근자 메소드는 만들지 않아도 된다.
private int code;
private String title;
private String category;
private String description;
public Domain() {
super();
// TODO Auto-generated constructor stub
}
public Domain(int code, String title, String category, String description) {
super();
this.code = code;
this.title = title;
this.category = category;
this.description = description;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Domain [code=" + code + ", title=" + title + ", category=" + category + ", description=" + description
+ "]";
}
}
8. Item 테이블과 연동할 Dao 클래스를 생성하고 필요한 변수와 연결 메소드와 해제 메소드를 생성 - 서버에서 사용할 거면 싱글톤 패턴으로 디자인
=> dao.
=> Connection, PreparedStatement, ResultSet 이 필요
package dao;
import java.sql.DriverManager;
import java.sql.ResultSet;
import com.mysql.jdbc.Connection;
import com.mysql.jdbc.PreparedStatement;
public class ItemDao {
// 데이터베이스 연동에 필요한 변
private Connection con;
private PreparedStatement pstmt;
private ResultSet rs;
private ItemDao() {
// 드라이버 클래스 로드
// 한번만 수행하면 되기 때문에 생성자에 작성
try {
Class.forName("");
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
private static ItemDao itemDao;
public static ItemDao sharedInstance() {
if(itemDao ==null) {
itemDao = new ItemDao();
}
return itemDao;
}
//연결메소드와 해제메소드
//연결과 해제는 모든 곳에서 사용이 되는 부분이므로 중복해서 코딩하지 않을려고 별도의 메소드로 생성
//이 메소드는 코드의 중복을 회피할려고 만든 메소드이므로 private 으로 생성해서 외부에서 호출하지 못하도록 생성
private void connect() {
try {
con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/sample?useUnicode=true&characterEncoding=utf8",
"root","password");
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
private void close() {
try {
if(rs != null) {
rs.close();
}
if(pstmt !=null) {
pstmt.close();
}
if(con != null) {
con.close();
}
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
9. Service 인터페이스와 ServiceImpl 구현
=> 사용자의 요청마다 호출되는 형태라서 템플릿 메소드 패턴을 적용하고 사용자의 요청 1개에 메소드 1개씩 매핑되는 것이 좋다.
1) Service 인터페이스를 생성
=> service.ItemService
package service;
public interface ItemService {
}
2) ServiceImpl 클래스를 생성
=> service.ItemServiceImpl
=> Service 인터페이스를 implements
=> Dao 클래스를 주입받아야 한다.
package service;
import dao.ItemDao;
public class ItemServiceImpl implements ItemService {
private ItemDao itemDao;
private ItemServiceImpl() {
//Dao 인스턴스를 생성
itemDao = ItemDao.sharedInstance();
}
private static ItemService itemService;
public static ItemService sharedInstance() {
if(itemService == null) {
itemService = new ItemServiceImpl();
}
return itemService;
}
}
10. Controller 클래스를 생성
=> HttpServlet 으로 부터 상속
=> service를 주입받아야 한다.
=> 싱글톤을 설정하지 않아도 WAS가 싱글톤으로 처리를 한다
=> url 패턴을 설정해야 하는데 이전에는 확장자 패턴을 많이 사용했고 최근에는 디렉토리 패턴에 작업을 기재하는 형태로 많이 작성
=> iteminsert.do 이런식이었는데 item/insert 형태로 많이 작성
=> item 디렉토리 패턴을 사용
=> controller.ItemController
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import service.ItemService;
import service.ItemServiceImpl;
@WebServlet({ "/", "/item/*" })
public class itemController extends HttpServlet {
private static final long serialVersionUID = 1L;
// 서비스 인스턴스 참조 변수
private ItemService itemService;
public itemController() {
super();
itemService = ItemServiceImpl.sharedInstance();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//공통된 부분을 제거하는 주소를 만든다
String contextPath = request.getContextPath();
String reqeustURI = request.getRequestURI();
String command = requestURI.substring(contextPath.length());
//전송방식을 저장
String method = request.getMethod();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
11. 시작페이지 출력 작업
=> WebContent 디렉토리에 index.jsp 파일로 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Item CRUD</title>
</head>
<body>
<h3>ITEM CRUD 작업</h3>
</body>
</html>
2) Controller 클래스의 doGet 메소드에서 시작 요청을 처리하는 코드를 작성
=> 시작 요청이 오면 WebContent/index.jsp로 포워딩 하도록 설정
=> 단순한 페이지 이동은 포워딩으로 처리
//시작 요청이 온 경우 index.jsp 페이지로 포워
if(command.contentEquals("/")) {
RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp");
dispatcher.forward(request, response);
}
3) 404에러가 난 경우
=> Controller의 가장 상단에 어노테이션에서 처리하지 않는 요청이 아닌지 확인
=> doGet 메소드의 비교하는 문장에서 요청하는 처리르 맞게 했는지
=> 포워딩할 주소와 실제 페이지의 위치가 같은지 확인
=> 이클립스와 톰캣의 경우 가끔 html 페이지이면 못읽고 jsp 페이지이면 읽어내는 경우가 발생한다.
12. 조회 작업
=> 전체 데이터 조회를 한다던가 데이터 개수를 파악하는 일들을 할 때 파라미터가 없다.
여러 데이터 중에서 하나의 데이터를 선택해서 조회하는 경우는 파라미터로 기본키의 값을 주어야 한다.
조건을 주고 데이터를 조회하는 경우는 파라미터로 조건과 값을 주어야 한다.
페이징 처리를 할 때는 현재 페이지 번호와 데이터개수를 추가로 넘겨주어야 한다.
조회를 할 때 매개변수가 많은 경우는 4개까지가 된다.
현재 페이지 번호, 페이지 당 데이터 개수, 검색에 사용할 필드명, 검색에 사용할 값이다.
=> 리턴 타입은 4가지
Scala Type : 데이터 개수를 찾아오는 경우, ID 나 Nickname 존재 여부
Map 이나 DTO : 기본키를 가지고 데이터 1개를 조회하는 경우(상세보기 나 로그인)
List<Scala> : id 목록을 가져온다던가 하는 경우
List<Map 이나 DTO> : 기본키가 아닌 항목을 가지고 데이터를 조회하는 경우
13. 전체 데이터 조회
1) 요청을 생성
<body>
<h3>ITEM CRUD 작업</h3>
<ul>
<li><a href="item/list">전체 데이터 조회</a></li>
</ul>
</body>
2) DAO 작업
=> 메소드 모양 : public List<Item> list();
=> sql 작성 : item 테이블의 전체 데이터를 가져오는 SQL - select * from item;
데이터가 2개 이상이면 order by를 이용해서 정렬하는 것이 좋은데 기본키로 오름차순정렬해서 출력하고자 하는 경우는 order by를 사용할 필요가 없다.
관계형 데이터베이스는 order by 가 없으면 기본키로 오름차순 정렬해서 가져온다.
3) Service 작업
=> ServiceImpl
package service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface ItemService {
//Item 테이블의 모든 데이터를 읽어오는 메소
public void list (HttpServletRequest request, HttpServletResponse response);
}
@Override
public void list(HttpServletRequest request, HttpServletResponse response) {
// 1. 파라미터 읽기
// 2. 파라미터 변환 작업이나 알고리즘 처리
// 3. 호출할 Dao 메소드의 매개변수를 생성
// 4. Dao의 메소드를 호출해서 결과를 저장
List<Item> list = itemDao.list();
// 5. Dao 메소드 호출 결과를 View로 전달하기 위해서 request 나 session에 저장
// 포워딩 할 거면 request
// 리다이렉트 할거면 session
request.setAttribute("list", list);
}
4) Controller 작업
=> 요청에 필요한 Service 메소드를 호출하고 그 결과를 확인해서 필요한 View페이지로 이동하도록 작성
=> 조회는 필요한 메소드를 호출하고 결과 페이지로 포워딩 시키면 된다.
}else if(command.contentEquals("/item/list")) {
//전체 데이터를 가져오는 서비스 메소드를 호출
itemService.list(request, response);
// 결과 페이지로 이동
// 현재 요청이 /item/list이므로 ../view/list.jsp이면
// WebContent/view/list.jsp가 된다.
RequestDispatcher dispatcher =
request.getRequestDispatcher(
"../view/list.jsp");
dispatcher.forward(request, response);
}
5) View 작업 - Designer가 있는 경우에는 데이터의 모양을 설명하고 이 작업을 동시에 진행
=> WebContent 디렉토리에 view 디렉토리를 생성하고 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>목록보기</title>
</head>
<body>
<h3 align="center">데이터 목록 보기</h3>
<table align="center" border="1">
<tr>
<th>코드</th>
<th>카테고리</th>
<th>이름</th>
</tr>
<c:forEach var="item" items="${list}">
<tr>
<td> ${item.code}</td>
<td> ${item.category}</td>
<td> ${item.title}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
6) 처리 과정
=> index.jsp 에서 전체 데이터 조회를 클릭하면 ItemController의 doGet 메소드로 가야한다.
여기서 출력이 안되면 URL을 확인해야 한다.
=> ItemController에서 ItemServiceImpl의 메소드를 호출한다.
여기서 출력이 안되면 메소드를 잘못 호출
=> ItemServiceImpl 클래스에서 ItemDao의 메소드를 호출한다.
여기서 출력이 안되면 메소드를 잘못 호출
=> ItemDao 클래스의 마지막에서 리턴할 데이터를 출력한다.
여기서 출력이 잘못되었으면 sql과 매개변수를 확인
매개변수가 잘못된 경우 ItemServiceImpl에서 잘못 넘겨주었을 수도 있다.
=> ItemServiceImpl 에서 ItemDao 메소드의 매개변수를 출력
=> ItemController의 doGet 메소드로 와서 결과 페이지이름과 출력페이지이름이 정확한지 확인
이 부분이 잘못되면 404 에러가 나던지 화면에 아무것도 보이지 않는다.
=> list.jsp 파일을 다시 확인
ItemServiceImpl에서 저장한 attribute 이름과 출력 내용이 일치하는지 확인
13. 데이터 삽입
=> 과정 : 삽입요청 -> 입력 페이지로 이동 -> 삽입 요청 -> 실제 데이터 삽입 요청 -> Controller -> 입력 페이지로 이동하고 사용자의 입력 -> Controller -> Service -> Dao -> Repository -> Dao -> ServiceImpl -> Controller -> 결과 페이지
=> 최근에는 입려 페이지로 이동하고 처리하는 형태의 1개의 URL만 사용하고 입력페이지로 이동할 대는 GET방식을 이용하고 처리하는 부분은 POST방식으로 처리
=> 기본키
기본키 나 unique 속성의 데이터를 입력하는 경우는 중복검사를 해줘야 한다.
기본키를 자동설정하는 경우는 sequence 나 auto_increment를 이용하는 경우는 삽입하는 SQL에서 처리하고 가장 큰 번호를 찾아서 +1을 하는 경우에는 Dao에 메소드를 추가해야 한다.
가장 큰 번호를 찾아서 +1 을 하는 방식은 Oracle 이나 MySQL이나 동일하기 때문에 데이터베이스를 변경해도 그대로 두면 된다.
하나의 SQL구문을 별도로 실행해야 한다는 점은 단점이다.
1) index.jsp 파일에 삽입 요청을 생성
<li><a href="item/insert">데이터삽입</a></li>
2) ItemController 클래스에 위의 요청을 처리하는 코드를 doGet에 삽입
else if(command.equals("/item/insert") && method.equals("GET")) {
//입력페이지로 이동
RequestDispatcher dispatcher =
request.getRequestDispatcher(
"../view/insert.jsp");
dispatcher.forward(request, response);
3) WebContent/view/insert.jsp 파일을 추가하고 화면 디자인
4) ItemDao 클래스에 가장 큰 code를 찾아오는 메소드와 데이터를 삽입하는 메소드를 생성
=> 가장 큰 code를 찾아오는 메소드 - select max(code) form item
public int maxCode(){
}
//가장 큰 글번호 찾아오는 메소드
public int maxCode(){
int result = 0;
connect();
try {
//SQL을 생성
pstmt = con.prepareStatement(
"select max(code) from item");
//SQL을 실행
rs = pstmt.executeQuery();
if(rs.next()) {
result = rs.getInt("max(code)");
}
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
close();
return result;
}
=> 데이터를 삽입하는 메소드
데이터 삽입과 갱신은 DTO 나 Map을 매개변수로 받고 정수를 리턴
데이터 삭제는 일반적으로 Primary Key를 매개변수로 받고 정수를 리턴
insert into 테이블이름(컬럼이름 나열) values(?,?....)
고정된 값은 ? 대신에 직접 입력(오라클에서 오늘 날짜는 sysdate, 일련번호는 시퀀스이름.nextval 등, MySQL에서는 Auto_Increment는 생략)
public int insert(Item item) {
//-1로 초기화해서 -1이 리턴되면 작업 실패
int result = -1;
connect();
try {
pstmt = con.prepareStatement(
"insert into item(code, category, title, description) "
+ "values(?,?,?,?)");
//?에 값을 바인딩
pstmt.setInt(1, item.getCode());
pstmt.setString(2, item.getCategory());
pstmt.setString(3, item.getTitle());
pstmt.setString(4, item.getDescription());
//SQL 실행
result = pstmt.executeUpdate();
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
close();
return result;
}
5) ItemService 클래스에 데이터 삽입 처리를 위한 메소드를 생성
=> ItemService 인터페이스에 데이터 삽입 처리를 위한 메소드를 선언
// Item 테이블에 데이터를 저장하는 메소드
public void insert (
HttpServletRequest request,
HttpServletResponse response);
=> ItemServiceImpl 클래스에 데이터 삽입 처리를 위한 메소드를 구현
@Override
public void insert(HttpServletRequest request, HttpServletResponse response) {
// 1. 파라미터 읽기
String category = request.getParameter("category");
String title = request.getParameter("title");
String description = request.getParameter("description");
// 2. 파라터를 가지고 필요한 작업 수행
// 가장 큰 code를 찾아서 +1을 해서 챙ㄷ에 대이
int code = itemDao.maxCode() +1;
// 3. 호출할 DAO 메소드의 매개변수를 생성
Item item = new Item();
item.setCode(code);
item.setCategory(category);
item.setTitle(title);
item.setDescription(description);
// 4. DAO 메소드를 호출
int result = itemDao.insert(item);
// 5. 결과를 저장
request.getSession().setAttribute("result", result);
}
6) ItemController 클래스의 doGet 메소드에 삽입요청을 처리하는 코드를 추가
}else if(command.equals("/item/insert")
&& method.equals("POST")) {
//삽입을 처리
itemService.insert(request, response);
//삽입하고 결과 페이지로 이동
//작업을 수행했으므로 목록보기로 이동
response.sendRedirect("list");