728x90
300x250

[JSP] 21. Jsp/Servlet(MVC) Maven 기반의 다중 업로드, 다운로드, 삭제 구현(1)


이 글의 소스코드를 수정해서 삭제 기능까지 추가해보도록 하겠다.


1. [JSP] 12. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(1)

https://yyman.tistory.com/1414

2. [JSP] 12. Jsp/Servlet(MVC) Maven 기반의 다중 파일 업로드, 다운로드 구현(2)

https://yyman.tistory.com/1415



1. 결과


입력부터 다운로드, 삭제에 이르는 전 과정을 담아보았다.



그림 1. 다중 업로드 작업




그림 2. 다중 업로드된 모습





그림 3. 파일을 찾을 수 없을 때 반응




그림 4. 다운로드 모습




그림 5. 삭제 모습



그림 6. 파일이 다 지워질 경우에 폴더 삭제까지 하기



그림 7. 프로젝트 구성도(변경됨)




2. 변경 - Controller (FrontController.java)


package com.fileWeb.controller;


import java.io.IOException;

import java.util.HashMap;

import java.util.Map;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


import com.fileWeb.util.HttpUtil;


/**

 * Servlet implementation class HomeController

 */

public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

       

private String charset = null;

    public FrontController() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

// FrontController 패턴 & Command 패턴

protected void doAction(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

charset = sc.getInitParameter("charset");

req.setAttribute("charset", charset);

req.setCharacterEncoding(charset);

res.setContentType("text/html; charset=" + charset);

System.out.println(charset);

String uri = req.getRequestURI();

System.out.println("uri : " + uri);

String conPath = req.getContextPath();

System.out.println("conPath : " + conPath);

String command = uri.substring(conPath.length());

System.out.println("command : " + command);

Controller subController = null;

System.out.println("reqMapSize : " + req.getParameterMap().size());


if(command.equals("/board/insert.do")){

System.out.println("insert");

System.out.println("----------------");


    subController = new BoardInsertController();

    subController.execute(req, res);

   

}else if (command.equals("/board/insertResult.do")) {

System.out.println("insertResult");

System.out.println("----------------");


    subController = new BoardInsertResultController();

subController.execute(req, res);

}

else if(command.equals("/board/insertMultiResult.do")){

System.out.println("insertResult");

System.out.println("----------------");


    subController = new BoardInsertMultiResultController();

subController.execute(req, res);

}else if(command.equals("/board/download.do")) {

System.out.println("download");

System.out.println("----------------");

HttpUtil.fileDownload(req, res, null);

}else if(command.equals("/board/fileRemove.do")) {

System.out.println("remove");

System.out.println("----------------");

    HttpUtil.removeFile(req, res);

}

}

}



파일명: FrontController.java


[첨부(Attachments)]

FrontController-updated.zip





3. 변경 - Controller (BoardInsertMultiResultController.java)


변경 내용은 BoardInsertMultiResultController에 구현된 업로드 관련 소스를 HttpUtil로 이동시켰다.

파일명이 다소 너무 길어서 한 가지 이야기하면, 작명도 조금 연습하면 좋을 듯 싶다. (너무 긴 건 좋은 건 아니다.)


package com.fileWeb.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import com.fileWeb.util.HttpUtil;


public class BoardInsertMultiResultController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws

ServletException, IOException {

HttpUtil.uploadFile(req, res);

HttpUtil.forward(req, res, "/WEB-INF/view/board/insertResult.jsp");

}

}


파일명: BoardInsertMultiResultController.java


[첨부(Attachments)]

BoardInsertMultiResultController-updated.zip






4. 변경 - Util 패키지로 변경 (HttpUtil.java)


다소 코드에 변화가 있었다.


변경 전: com.fileWeb.controller.HttpUtil.java

변경 후: com.fileWeb.util.HttpUtil.java




package com.fileWeb.util;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.PrintWriter;

import java.util.HashMap;

import java.util.Iterator;

import java.util.List;

import java.util.Map;


import javax.servlet.RequestDispatcher;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.servlet.ServletFileUpload;



public class HttpUtil extends HttpServlet {

private static final long serialVersionUID = 1L;

private static String charset = null; // 문자열


private static int SIZETHRESHOLD = 4096;

private static String UPLOAD_FOLDER = "upload";

private static String UPLOAD_TMP_FOLDER = File.separator + "WEB-INF" + File.separator + "temp";

private static long MAX_UPLOAD_SIZE = 3 * 1024 * 1024;

private static Map<String, Object> reqMap = null; // req 정보(MultiRequest)

private static Map<Integer, Map<String, Object>> fileMap = null; // 다중 파일 지원

private static int num = 0;


public static void forward(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

try {

RequestDispatcher dispatcher = req.getRequestDispatcher(path);

dispatcher.forward(req, res);

}catch(Exception e) {

e.printStackTrace();

}

}

public static void uploadFile(HttpServletRequest req, HttpServletResponse res) throws

ServletException, IOException {


fileMap = new HashMap<Integer, Map<String, Object>>(); 

reqMap = new HashMap<>();

num = 1;

        

        PrintWriter out = res.getWriter();

        out.println("<HTML><HEAD><TITLE>Multipart Test</TITLE></HEAD><BODY>");

try {

            

            //디스크상의 프로젝트 실제 경로얻기

            //String contextRootPath = "c:" + File.separator + "upload";

String dirName = UPLOAD_FOLDER ; 

// String dirName = "upload"; 

String contextRootPath = req.getSession().getServletContext().getRealPath("/") + dirName;

            System.out.println("실제경로:" + contextRootPath);

            

            //1. 메모리나 파일로 업로드 파일 보관하는 FileItem의 Factory 설정

            DiskFileItemFactory diskFactory = new DiskFileItemFactory(); // 디스크 파일 아이템 공장

            diskFactory.setSizeThreshold(SIZETHRESHOLD); // 업로드시 사용할 임시 메모리

            // diskFactory.setSizeThreshold(4096); // 업로드시 사용할 임시 메모리

            diskFactory.setRepository(new File(contextRootPath + UPLOAD_TMP_FOLDER)); // 임시저장폴더

            // diskFactory.setRepository(new File(contextRootPath + UPLOAD_TMP_FOLDER)); // 임시저장폴더

            

            //2. 업로드 요청을 처리하는 ServletFileUpload생성

            ServletFileUpload upload = new ServletFileUpload(diskFactory);


            // upload.setSizeMax(3 * 1024 * 1024); //3MB : 전체 최대 업로드 파일 크기

            upload.setSizeMax(MAX_UPLOAD_SIZE); // 전체 최대 업로드 파일 크기

            

            //3. 업로드 요청파싱해서 FileItem 목록구함​​

            List<FileItem> items = upload.parseRequest(req); 


            Iterator<FileItem> iter = items.iterator(); //반복자(Iterator)로 받기​            

            while(iter.hasNext()) { //반목문으로 처리​    

                FileItem item = (FileItem) iter.next(); //아이템 얻기

                 //4. FileItem이 폼 입력 항목인지 여부에 따라 알맞은 처리

                

                if(item.isFormField()){ 

                //파일이 아닌경우

                    processFormField(out, item);

                    

                } else {

                //파일인 경우

                System.out.println("오류:" + item.getName());

               

                // 버그 개선 item 이름값 비어있을 때

                if ( item.getName() != "") {

                processUploadFile(out, item, contextRootPath);

                }

                System.out.println("오류2:");

                }

            }

            

        } catch(Exception e) {

            out.println("<PRE>");

            e.printStackTrace(out);

            out.println("</PRE>");

        }

out.println( "usrID(Map): " + reqMap.get("usrID") );

out.println( "usrPasswd(Map):" + reqMap.get("usrPasswd") );

        

        out.println("</BODY></HTML>");

// req.setAttribute("usrID", reqMap.get("usrID"));

// req.setAttribute("login", 1);//Object Type으로 넘어감

        req.setAttribute("reqMap", reqMap);

        req.setAttribute("fileMap", fileMap);

        

        // 방법3

        for( Integer key : fileMap.keySet() ){


            Map<String, Object> fileMapNode = fileMap.get(key);

            System.out.println( String.format("키 : %s, 값: %s", key, fileMapNode.get("fileName") ));

            

        }

        

// System.out.println("오류3:" + reqMap.get("usrID"));

}


//업로드한 정보가 파일인경우 처리

private static void processUploadFile(PrintWriter out, FileItem item, String contextRootPath)

throws Exception {

Map<String, Object> fileNode = new HashMap<String, Object>();


String dirName = UPLOAD_FOLDER ; 

String name = item.getFieldName(); // 파일의 필드 이름 얻기

String fileName = item.getName(); // 파일명 얻기

// 임시 - 실제 원본 이름 추출

File originalFile = new File(fileName);

String originalFileName = originalFile.getName();

System.out.println("임시:" + originalFileName );

String contentType = item.getContentType(); // 컨텐츠 타입 얻기

long fileSize = item.getSize(); // 파일의 크기 얻기

// 업로드 파일명을 현재시간으로 변경후 저장

String fileExt = fileName.substring(fileName.lastIndexOf("."));

String uploadedFileName = System.currentTimeMillis() + ""; 

System.out.println(fileExt);

System.out.println(uploadedFileName);

// 저장할 절대 경로로 파일 객체 생성

String realUploadFile = File.separator + dirName + File.separator + uploadedFileName;

System.out.println("실제 저장직전폴더:" + contextRootPath + realUploadFile);

File uploadedFile = new File(contextRootPath + realUploadFile);

item.write(uploadedFile); //파일 저장

//========== 뷰단에 출력 =========//

out.println("<P>");

out.println("파라미터 이름:" + name + "<BR>");

out.println("파일 이름:" + fileName + "<BR>");

out.println("콘텐츠 타입:" + contentType + "<BR>");

out.println("파일 사이즈:" + fileSize + "<BR>");

//확장자가 이미지인겨우 이미지 출력

if(".jpg.jpeg.bmp.png.gif".contains(fileExt.toLowerCase())) {

out.println("<IMG SRC='upload/" 

+ uploadedFileName 

+ "' width='300'><BR>");

}

out.println("</P>");

out.println("<HR>");

out.println("실제저장경로 : "+uploadedFile.getPath()+"<BR>");

out.println("<HR>");

// 파일 정보

fileNode.put("name", name);

fileNode.put("fileName", originalFileName);

fileNode.put("contentType", contentType);

fileNode.put("fileSize", fileSize);

fileNode.put("fileExt", fileExt);

fileNode.put("uploadedFileName", uploadedFileName);

fileNode.put("realName", uploadedFile.getName());

fileNode.put("realPath", uploadedFile.getPath());

fileMap.put(num, fileNode);

num++;

}

private static void processFormField(PrintWriter out, FileItem item) 

throws Exception{

String name = item.getFieldName(); //필드명 얻기

Object value = item.getString("UTF-8"); //UTF-8형식으로 필드에 대한 값읽기

// out.println(name + ":" + value + "<BR>"); //출력

reqMap.put(name, value);

}

/*

* 다운로드(Download)

*/

public static void fileDownload(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {


charset = (String) req.getAttribute("charset");

System.out.println(charset);

PrintWriter out = res.getWriter();

// 파일 업로드된 경로

String root = req.getSession().getServletContext().getRealPath("/");

String savePath = root + UPLOAD_FOLDER + File.separator + UPLOAD_FOLDER ;

// String savePath = root + "upload" + File.separator + "upload";

// 서버에 실제 저장된 파일명

String filename = "1601561525229" ;

System.out.println("파일 실제 폴더경로:" + savePath);

System.out.println("실제 파일명:" + filename);

// 실제 내보낼 파일명

String orgfilename = "license한글.txt" ;

req.setCharacterEncoding(charset);

res.setCharacterEncoding(charset);

InputStream in = null;

OutputStream os = null;

File file = null;

boolean skip = false;

String client = "";

try{

    // 파일을 읽어 스트림에 담기

    try{

        file = new File(savePath, filename);

        in = new FileInputStream(file);

    }catch(FileNotFoundException fe){

        skip = true;

    }

    client = req.getHeader("User-Agent");

    // 파일 다운로드 헤더 지정

    res.reset() ;

    res.setContentType("application/octet-stream");

    res.setHeader("Content-Description", "JSP Generated Data");

    if(!skip){

        // IE

        if(client.indexOf("MSIE") != -1){

            res.setHeader ("Content-Disposition", "attachment; filename="+new String(orgfilename.getBytes("KSC5601"),"ISO8859_1"));

        }else{

            // 한글 파일명 처리

            orgfilename = new String(orgfilename.getBytes("KSC5601"),"iso-8859-1");

            res.setHeader("Content-Disposition", "attachment; filename=\"" + orgfilename + "\"");

            res.setHeader("Content-Type", "application/octet-stream; charset=utf-8");

        }  

        res.setHeader ("Content-Length", ""+file.length() );

        os = res.getOutputStream();

        

        byte b[] = new byte[(int)file.length()];

        int leng = 0;

        while( (leng = in.read(b)) > 0 ){

            os.write(b,0,leng);

        }

    }else{

    // 한글 깨짐 - 해결

    res.setContentType("text/html;charset=" + charset);

        out.println("<html><head>");

        out.println("<script language='javascript'>alert('파일을 찾을 수 없습니다.');history.back();</script>");

        out.println("</head><body></body></html>");

    }

    in.close();

    os.close();

}catch(Exception e){

e.printStackTrace();

}

    

}


/*

*  파일 삭제, 폴더 삭제

*/

public static void removeFile(HttpServletRequest req, HttpServletResponse res) 

throws ServletException, IOException{


// 파일 업로드된 경로

String root = req.getSession().getServletContext().getRealPath("/");

String savePath = root + UPLOAD_FOLDER + File.separator + UPLOAD_FOLDER ;

String filename = "1601561525229" ;


        File file = new File(savePath, filename);

        // 파일이 존재할 떄

        if (file.exists()) {

        file.delete();

        }

        

        removeDirectory(req, res, savePath);

}

/*

*/

private static boolean removeDirectory(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

boolean result = false;

File usrDir = new File(path);

if(!usrDir.exists()) {                  // 경로 존재 여부

            result = false;     

        }

else {

        File[] lowFiles = usrDir.listFiles();      // 경로 내의 파일 리스트

        

        // 폴더 삭제

        if ( usrDir.isDirectory() 

        && lowFiles.length == 0 ) {

   

        System.out.println("폴더 삭제처리 완료");

        usrDir.delete();

       

        return true;

       

    }else{

    result = false;

    }

        

        }

        

        return result;

}


}



파일명: HttpUtil.java


[첨부(Attachments)]

HttpUtil-updated.zip




* 맺음글(Conclusion)


파일 업로드, 다운로드, 삭제에 대해서 살펴보았다.

반응형
728x90
300x250

[JSP] 20. MyBatis-3.5.5, HikariCP 3.4.2 연동 - Maven(Servlet) Spring 제거버전 (Oracle 19g) - Java 방식


JSP/Servlet 순수한 방식에서 HikariCP를 연동할 수 있는 방법에 대해서 고민을 하게 된 이유는 Spring Framework의 의존성을 줄일 수 있겠냐는 생각을 가지고 접근하게 되었다.


JSP/Servlet이 현재에도 의외로 많이 사용되고 있다. Spring-Framework 사용 안 한 형태도 동작하는 프로젝트가 많기 때문에 조금 생각해보면 효과적으로 기존 프로젝트도 개선할 수 있을 거 같아서 작성하게 되었다.


기존 프로젝트에도 MyBatis 프레임워크와 HikariCP를 적용할 수 있으니 가능성을 가지고 순수한 JSP에 대해서도 관심을 가져봤으면 하는 바람이다.


커넥션 풀은 현재의 웹 개발에서는 반드시 필요한 존재라고 본다. 

(중요성에 대해서는 별도로 시간이 나면, 조금 더 심화적인 방법으로 연재하도록 하겠다.)


* XML로 설정한 MyBatis에서는 가능한가요?

= 불가능하다. (미지원)



- IDE: Spring-Tool-Suite 4-4.7.2 Releases (2020-06 최신)

- DB: Oracle Databases 19g (2020-09 최신)

- Maven 3.6.3/1.16.0.20200610-1735 (2020-09 최신)

- JAR: javax.servlet-api.4.0.1.jar (2020-09 최신)

         ojdbc8-19.7.0.0.jar (2020-09 최신)

         MyBatis 3.5.5 (2020-09 최신)

         HikariCP 3.4.2 (2020-09 최신)



* 정말로 Spring-Framework는 사용하지 않았나요?

= 그렇다.



관련 글)

1. [JSP] 19. MyBatis-3.5.5 와 Maven / Servlet 연동하기 (Oracle 19g) - Java 방식, https://yyman.tistory.com/1434, 2020-10-01

-> 이 글의 소스 코드 이어서 계속(해당 게시글에서는 변형됨.)



1. 결과


이전의 게시글에서 동작 반응이 다소 변경되었다. (HikariConnection Pool 반응 상태를 추가하였음.



그림 1. HikariPool - 2 반응 추가


사소해보이지만, 무척 중요하다. 동시 접속했을 때, 서버가 죽느냐 사느냐를 결정할 수도 있다.

커넥션 풀은 데이터베이스 접속을 원할하게 해주는 중요한 것이라고 비유해주고 싶다.



그림 2. 프로젝트 구성 모습


* 이전의 프로젝트와 차이점: exmple 탈자를 정정하였음. (example로)

- 패키지명을 잘 조정해서 사용하면 크게 무리없이 동작할 것이다.


1. 폴더 추가

/src/main/resources 폴더가 생성되었다.

- db.properties가 추가되었다.



2. POM.xml - 설정하기


POM.xml의 변화는 HikariCP 3.4.2가 추가되었다.

바뀐 것은 없다. 그래서 잘 따라해야 하는 것이다.


<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>


  <groupId>com.exmplebatis</groupId>

  <artifactId>web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>


  <name>web Maven Webapp</name>

  <!-- FIXME change it to the project's website -->

  <url>http://www.example.com</url>


  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>1.7</maven.compiler.source>

    <maven.compiler.target>1.7</maven.compiler.target>

  </properties>


  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.11</version>

      <scope>test</scope>

    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->

<dependency>

    <groupId>org.mybatis</groupId>

    <artifactId>mybatis</artifactId>

    <version>3.5.5</version>

</dependency>

<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->

<dependency>

    <groupId>com.zaxxer</groupId>

    <artifactId>HikariCP</artifactId>

    <version>3.4.2</version>

</dependency>

<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.7.0.0</version>

</dependency>

  </dependencies>


  <build>

    <finalName>web</finalName>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

      <plugins>

        <plugin>

          <artifactId>maven-clean-plugin</artifactId>

          <version>3.1.0</version>

        </plugin>

        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->

        <plugin>

          <artifactId>maven-resources-plugin</artifactId>

          <version>3.0.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-compiler-plugin</artifactId>

          <version>3.8.0</version>

        </plugin>

        <plugin>

          <artifactId>maven-surefire-plugin</artifactId>

          <version>2.22.1</version>

        </plugin>

        <plugin>

          <artifactId>maven-war-plugin</artifactId>

          <version>3.2.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-install-plugin</artifactId>

          <version>2.5.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-deploy-plugin</artifactId>

          <version>2.8.2</version>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom-update-hikaricp.zip




3. /resources/db.properties 설정하기


db.properties에 관한 것이다.



그림 3. db.properties 설정 내용


jdbcUrl=jdbc:oracle:thin:@localhost:1521:orcl

dataSourceClassName=oracle.jdbc.driver.OracleDriver

dataSource.user=UserName

dataSource.password=Password

cachePrepStmts=true

prepStmtCacheSize=250

prepStmtCacheSqlLimit=2048


파일명: db.properties


[첨부(Attachments)]

db.zip




4. SqlMapSessionFactory.java - com.example.web.db


DB 세션 영역이다.

일부 인터넷 게시글을 찾아보면, SqlSessionFactoryBeans ssfb = new SqlSessionFactoryBeans(); 객체 생성 후에 setDatasource()로 연결시키면 된다고 소개된 글들이 있다.


이 부분은 공식 메뉴얼을 찾아본 바로는 MyBatis에서 Spring을 지원하기 위해서 만든 부분이다.

JSP/Servlet 기반의 프로젝트에서는 지원하지 않는 부분이다. (Beans 처리를 미지원함.)


충분히 시간을 가지고 태스트가 완료된 코드이다.



그림 4. SqlMapSessionFactory.java - 작업 모습



package com.example.web.db;


import java.io.IOException;

import java.io.InputStream;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.util.Properties;


import javax.sql.DataSource;


import org.apache.ibatis.mapping.Environment;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.apache.ibatis.transaction.TransactionFactory;

import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;


import com.example.web.mapper.CompUsersMapper;

import com.zaxxer.hikari.HikariConfig;

import com.zaxxer.hikari.HikariDataSource;


import oracle.jdbc.pool.OracleDataSource;


public class SqlMapSessionFactory {


private static SqlMapSessionFactory factory = new SqlMapSessionFactory();


public static SqlMapSessionFactory getInstance() {

return factory;

}


public static SqlSessionFactory ssf;


    private static String CLASSNAME;

    private static String JDBC_URL;

    private static String USERNAME;

    private static String PASSWORD;

    private static String CACHE_PREP_STMTS;

    

    private HikariDataSource ds;

    private HikariConfig config;



private SqlMapSessionFactory() {

/* HikariCP 로드 */

    InputStream inputStream;

    config = new HikariConfig();

   

    String resource = "db.properties";

    Properties properties = new Properties();

    

    try {


    inputStream = getClass().getClassLoader().getResourceAsStream(resource);

        properties.load(inputStream);


        System.out.println("jdbcurl:" + properties.getProperty("jdbcUrl"));

        System.out.println("className" + properties.getProperty("dataSourceClassName"));


        CLASSNAME = properties.getProperty("dataSourceClassName");

        JDBC_URL = properties.getProperty("jdbcUrl");

        USERNAME = properties.getProperty("dataSource.user");

        PASSWORD = properties.getProperty("dataSource.password");

        CACHE_PREP_STMTS = properties.getProperty("cachePrepStmts");


        config.setDriverClassName(CLASSNAME);

        config.setJdbcUrl( JDBC_URL );

        config.setUsername( USERNAME );

        config.setPassword( PASSWORD );

        config.addDataSourceProperty( "cachePrepStmts" , CACHE_PREP_STMTS );

        config.addDataSourceProperty( "prepStmtCacheSize" , "250" );

        config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );

        ds = new HikariDataSource( config );


    System.out.println("성공:" + ds);


    } catch (IOException e) {

    System.out.println("오류:" + e.getMessage());

        e.printStackTrace();

    }

    

}

   

// iBatis(MyBatis 반환)

public SqlSessionFactory getSqlSessionFactory() {

DataSource hDs = ds;

    // DataSource dataSource = getOracleDataSource();

System.out.println(hDs);

    TransactionFactory transactionFactory = new JdbcTransactionFactory();

    Environment environment = new Environment("development", transactionFactory, hDs);

    Configuration configuration = new Configuration(environment);


    configuration.addMapper(CompUsersMapper.class); // Mapper 클래스

   

//    System.out.println("성공2");

        return new SqlSessionFactoryBuilder().build(configuration);


    }


public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

close(conn, ps); // Recursive 구조 응용(재귀 함수)

} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {

try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {


try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


} // end of if


}


}


파일명: SqlMapSessionFactory.java


[첨부(Attachments)]

SqlMapSessionFactory-update.zip




5. FrontController.java - com.example.web.controller


FrontController에 대한 변화이다.



그림 5. FrontController.java - 작업 모습



package com.example.web.controller;


import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;


import com.example.web.db.SqlMapSessionFactory;

import com.example.web.mapper.CompUsersMapper;

import com.example.web.model.CompUsers;


public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

    SqlSessionFactory factory = null;

    SqlMapSessionFactory sqlMapFactory = null;


/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doAction(request, response);

}


/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doAction(request, response);

}


protected void doAction(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

sqlMapFactory = SqlMapSessionFactory.getInstance();

try {

factory = sqlMapFactory.getSqlSessionFactory();

} catch (Exception e) {

e.printStackTrace();

}

try (SqlSession session = factory.openSession()) {

  CompUsersMapper mapper = session.getMapper(CompUsersMapper.class);

  CompUsers user = mapper.findByUsername("user");

  

  System.out.println("계정명:" + user.getUsername());

  

}

}


}



파일명: FrontController.java


[첨부(Attachments)]

FrontController-update.zip





6. CompUsers.java - com.example.web.model (Model)


이전 게시글과는 변화는 없지만, 다시 재언급한다.


package com.example.web.model;


public class CompUsers {


private String username;

private String password;

private int enabled;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public int getEnabled() {

return enabled;

}

public void setEnabled(int enabled) {

this.enabled = enabled;

}

}



파일명: CompUsers.java


[첨부(Attachments)]

CompUsers-updated.zip


CREATE TABLE comp_users (

username VARCHAR(50) NOT NULL,

password VARCHAR(300) NOT NULL,

enabled INT NOT NULL,

PRIMARY KEY (username)

);


-- 계정

INSERT INTO comp_users (username, password, enabled) VALUES ('user', '$2a$10$x04djNV2e9rpcPPRyXoLk.rMm6iZe2/vYdzpqHQcLeNSYdt7kc30O', 1);

INSERT INTO comp_users (username, password, enabled) VALUES ('admin', '$2a$10$QUddY3O/6ZgkYCR6MFlv9.nqA501Fm0cc/ZxQHX5pwb1o0CYCTiIS', 1);


파일명: CompUsers.sql


[첨부(Attachments)]

comp_users_sql.zip




7. CompUsersMapper.java - com.example.web.mapper (Mapper - DAO)


이전 게시글과 비교했을 때 변화는 없었다.


package com.example.web.mapper;


import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;


import com.example.web.model.CompUsers;


@Mapper

public interface CompUsersMapper {

  @Select("SELECT * FROM comp_users WHERE username = #{username}")

  public CompUsers findByUsername(String username);

  

}



파일명: CompUsersMapper.sql


[첨부(Attachments)]

CompUsersMapper-updated.zip




* 맺음글(Conclusion)


HikariCP 구성과 MyBatis-3.5.5 셋팅에 대해서 JSP/Servlet 방식으로 다뤄보았다.

반응형
728x90
300x250

[JSP] 19. MyBatis-3.5.5 와 Maven / Servlet 연동하기 (Oracle 19g) - Java 방식


MyBatis 관련 이전의 글을 읽어보고 실습을 해본 다음에 경험해봐도 무방할 것으로 보인다.

이번에 소개할 내용은 조금 세련된 방식으로 MyBatis를 적용하는 방법에 대해서 소개하려고 한다.


이 글을 작성하게 된 계기는 MyBatis 공식 사이트에서 제시하는 방법론에 대해서 실질적으로 어떻게 구현하는지 소개해보고 싶어서 그렇다.


이전 스타일에 익숙하다면, 새로운 스타일을 경험하시어 훨씬 생산성이 있는 방법으로 개발을 시도해보면 좋지 않겠냐는 것이다.


1. [JSP] 8. 영속프레임워크 MyBatis를 활용한 CRUD 구현 - JSP와 Oracle (XML 방식), 2020-9-19
https://yyman.tistory.com/1390 

-> XML 방식으로 구현됨.


- IDE: Spring-Tool-Suite 4-4.7.2 Releases (2020-06 최신)

- DB: Oracle Databases 19g (2020-09 최신)

- Maven 3.6.3/1.16.0.20200610-1735 (2020-09 최신)

- JAR: javax.servlet-api.4.0.1.jar (2020-09 최신)

         ojdbc8-19.7.0.0.jar (2020-09 최신)

         MyBatis 3.5.5 (2020-09 최신)



1. 결과


결과를 바탕으로 살펴보면, 이렇게 출력되면 잘 된 것이다.



그림 1. 결과 - DB 연동 모습



그림 2. 결과 - 프로젝트 구성도


작업을 할 내용이다. 매우 간결한 프로젝트 구성이다.



2. 데이터베이스 설계


Spring Security 5의 사용자 계정 테이블을 인용하여 설계하였다.

테이블의 구조는 간단한 형태이다.



그림 3. Oracle SQL Developer - 데이터베이스 설계





그림 4. Oracle SQL Developer - (데이터 내용)



그림 5. Oracle SQL Developer - COMP_USERS (Model)


CREATE TABLE comp_users (

username VARCHAR(50) NOT NULL,

password VARCHAR(300) NOT NULL,

enabled INT NOT NULL,

PRIMARY KEY (username)

);


-- 계정

INSERT INTO comp_users (username, password, enabled) VALUES ('user', '$2a$10$x04djNV2e9rpcPPRyXoLk.rMm6iZe2/vYdzpqHQcLeNSYdt7kc30O', 1);

INSERT INTO comp_users (username, password, enabled) VALUES ('admin', '$2a$10$QUddY3O/6ZgkYCR6MFlv9.nqA501Fm0cc/ZxQHX5pwb1o0CYCTiIS', 1);


파일명: comp_users.sql


[첨부(Attachments)]

comp_users.zip





3. 프로젝트 생성하기


프로젝트 생성에 대해서 소개하겠다.



그림 6. Maven Project 생성하기(1)


File -> New -> Maven Project를 클릭한다.




그림 7. Maven Project 생성하기(2)


프로젝트를 생성할 때 "org.apache.maven.archetype", "maven-archetype-webapp"을 선택한다.

Next를 클릭한다.



그림 8. Maven Project 생성하기(3)


Group Id, Artifact Id를 입력해준다.

Finish를 누른다.




4. 자바 버전 - Build Path, Project Facets 설정하기


프로젝트를 선택한다. 그리고 Properties에 들어가서 설정할 것이다.



그림 9. 프로젝트의 마우스 오른쪽 버튼의 메뉴 모습


프로젝트를 선택한 후 마우스 오른쪽 버튼을 누른다.

Properties를 클릭한다.



그림 10. 프로젝트의 Build Path


Java Build Path를 클릭한다.

JRE System Library를 14버전으로 변경해준다.

Apply를 누른다.




그림 11. 프로젝트의 Project Factes


Project Factes를 클릭한다.

Java의 버전을 14로 변경한다.

Apply를 누른다.



4. MyBatis - 공식 사이트 메뉴얼 읽어보기


공식 사이트에서 지원하는 방식에 대해서 간단하게 소개되어 있다.

아래의 공식 링크를 참고해서 적용할 것이다.


https://mybatis.org/mybatis-3/ko/getting-started.html



그림 12. MyBatis - 시작하기 (Official Site)





5. pom.xml - 설정하기


http://mvnrepository.com에서 Java Servlet API 4.0.1과 MyBatis를 찾아서 추가한다.

Oracle은 Oracle 공식 사이트나 또는 Oracle Databases 19g가 설치되어 있다면, 간단하게 Add Dependency를 통해서 pom.xml에 추가할 수 있다.



그림 13. Servlet API 4.0.1 - Mvnrepository






그림 14. MyBatis 3.5.5 - Mvnrepository



그림 15. Pom.xml 마우스 오른쪽 버튼 메뉴 모습


pom.xml을 마우스 오른쪽 버튼으로 클릭한다.

Maven->Add Dependency를 클릭한다.


(참고로 Oracle Databases 19g가 설치된 경우에서만 가능한 작업이다.)

(설치가 되지 않은 경우라면, Oracle 공식 사이트에서 Oracle JDBC를 내려받기 바란다.

그리고 lib 폴더에 넣어주고 프로젝트에서 셋팅해줘야 한다.)



그림 16. Add-Dependency


Oracle을 검색한다.

com.oracle.database.jdbc  "ojdbc8"을 선택한다.

OK를 누른다.



그림 17. STS 4.4 - pom.xml 모습


<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>


  <groupId>com.examplebatis</groupId>

  <artifactId>web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>


  <name>web Maven Webapp</name>

  <!-- FIXME change it to the project's website -->

  <url>http://www.example.com</url>


  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>1.7</maven.compiler.source>

    <maven.compiler.target>1.7</maven.compiler.target>

  </properties>


  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.11</version>

      <scope>test</scope>

    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->

<dependency>

    <groupId>org.mybatis</groupId>

    <artifactId>mybatis</artifactId>

    <version>3.5.5</version>

</dependency>


<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.7.0.0</version>

</dependency>

  </dependencies>


  <build>

    <finalName>web</finalName>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

      <plugins>

        <plugin>

          <artifactId>maven-clean-plugin</artifactId>

          <version>3.1.0</version>

        </plugin>

        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->

        <plugin>

          <artifactId>maven-resources-plugin</artifactId>

          <version>3.0.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-compiler-plugin</artifactId>

          <version>3.8.0</version>

        </plugin>

        <plugin>

          <artifactId>maven-surefire-plugin</artifactId>

          <version>2.22.1</version>

        </plugin>

        <plugin>

          <artifactId>maven-war-plugin</artifactId>

          <version>3.2.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-install-plugin</artifactId>

          <version>2.5.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-deploy-plugin</artifactId>

          <version>2.8.2</version>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip




6. SqlMapSessionFactory.java - 자바 방식 연결부


자바 방식 연결부를 작성하도록 하겠다.



그림 18. Java Resources의 마우스 오른쪽 클릭 메뉴 모습


Java Resources를 마우스 오른쪽 버튼으로 클릭한다.

New->Class를 클릭한다.




그림 19. SqlMapSessionFactory.java 만들기


Package명과 Name을 입력한다. 

(예: package: com.exmple.web.db)    //    탈자(exmple로 만들었으니 알아서 참고할 것.)

(예: Name: SqlMapSessionFactory)


Finish를 누른다.



그림 20. SqlMapSessionFactory.java


코드를 수정해준다.


package com.exmple.web.db;


import java.io.IOException;

import java.io.InputStream;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;


import javax.sql.DataSource;


import org.apache.ibatis.io.Resources;

import org.apache.ibatis.mapping.Environment;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.apache.ibatis.transaction.TransactionFactory;

import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;


import com.exmple.web.mapper.CompUsersMapper;


import oracle.jdbc.pool.OracleDataSource;


public class SqlMapSessionFactory {


private static SqlMapSessionFactory factory = new SqlMapSessionFactory();


private SqlMapSessionFactory() {}


private final static String driverName = "oracle.jdbc.driver.OracleDriver";

private final static String dbUrl = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";

private final static String userName = "사용자계정명";

private final static String userPassword = "비밀번호";


public static SqlMapSessionFactory getInstance() {

return factory;

}


public static SqlSessionFactory ssf;


    static{


    DataSource dataSource = getOracleDataSource();

    TransactionFactory transactionFactory = new JdbcTransactionFactory();

   

    Environment environment = new Environment("development", transactionFactory, dataSource);

    Configuration configuration = new Configuration(environment);

   

    configuration.addMapper(CompUsersMapper.class); // Mapper 클래스

   

        ssf = new SqlSessionFactoryBuilder().build(configuration);

        

    }

    

// iBatis(MyBatis 반환)

public static SqlSessionFactory getSqlSessionFactory(){

        return ssf;

    }


/*


*     public static DataSource getMySQLDataSource() {

        Properties props = new Properties();


        FileInputStream fis = null;

        MysqlDataSource mysqlDS = null;

        

        try {

            fis = new FileInputStream("db.properties");


            props.load(fis);

            mysqlDS = new MysqlDataSource();

            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));

            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));

            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));

            

        } catch (IOException e) {

            e.printStackTrace();

        }

        return mysqlDS;

        

    }

    */

/*

* Description: 순정 오라클 데이터소스

*/

    private static DataSource getOracleDataSource(){

        

    OracleDataSource oracleDS = null;

        

    try {

            oracleDS = new OracleDataSource();

            oracleDS.setURL(dbUrl);

            oracleDS.setUser(userName);

            oracleDS.setPassword(userPassword);

            

        } catch (SQLException e) {

            e.printStackTrace();

        }

        return oracleDS;


    }


public Connection connect() {


Connection conn = null;


try {

Class.forName(driverName);

conn = DriverManager.getConnection(dbUrl, userName, userPassword);

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

return conn;

}


public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

close(conn, ps); // Recursive 구조 응용(재귀 함수)

} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {

try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {


try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


} // end of if


}


}


파일명: SqlMapSessionFactory.java


[첨부(Attachments)]

SqlMapSessionFactory.zip


비고: MySQL의 지원에 대해서 코드를 적어놓았다.




7. Servlet - 생성하기(Controller)


서블릿을 생성해줄 것이다.


그림 21. Java Resources의 오른쪽 버튼 메뉴 모습


Java Resources를 마우스 오른쪽 버튼으로 클릭한다.

New->Servlet을 클릭한다.



그림 22. Servlet 만들기


Package명은 "com.exmple.web.controller"로 한다.

Class Name은 "FrontController"로 한다.


Finish를 누른다.


(Exmple 오타 -> Example이 맞지만, 이미 생성했으니 그냥 따르는 걸로 하겠음.)



8. web.xml - 수정 작업


Servlet 경로를 살짝 수정해주겠다.



그림 23. web.xml - 수정하기



<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>

  <servlet-name>FrontController</servlet-name>

  <display-name>FrontController</display-name>

  <description></description>

  <servlet-class>com.exmple.web.controller.FrontController</servlet-class>

  </servlet>

  <servlet-mapping>

  <servlet-name>FrontController</servlet-name>

  <url-pattern>/sample</url-pattern>

  </servlet-mapping>

</web-app>



파일명: web.xml


[첨부(Attachments)]

web.zip





9. CompUsers.java - Model(모델)


앞서 설계한 DB Model을 코드로 구현할 것이다.



그림 24. CompUsers.java


package com.exmple.web.model;


public class CompUsers {


private String username;

private String password;

private int enabled;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public int getEnabled() {

return enabled;

}

public void setEnabled(int enabled) {

this.enabled = enabled;

}

}



파일명: CompUsers.java


[첨부(Attachments)]

CompUsers.zip




10. CompUsersMapper.java - Mapper(DAO)


이전 글과는 달리 공식 메뉴얼에서 제시하는 방법을 적용하도록 하겠다.



그림 25. Mapper 클래스 가이드



그림 26. Mapper 클래스 가이드 - 현실의 문제에 맞게 적용하기


package com.exmple.web.mapper;


import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;


import com.exmple.web.model.CompUsers;


@Mapper

public interface CompUsersMapper {

  @Select("SELECT * FROM comp_users WHERE username = #{username}")

  public CompUsers findByUsername(String username);

  

}



파일명: CompUsersMapper.java


[첨부(Attachments)]

CompUsersMapper.zip


대응하는 방식에는 XML Mapper를 적용하는 방법도 있다.
(참고로 자바방식으로 구현했다면, XML Mapper는 사용할 수 없다.)

(DB를 설계하면서 View도 만들 수도 있고 각종 조합을 할 수도 있는데, 엔터티를 잘 파악해서 구현하면 될 것 같다.)


[Spring Boot 버전의 MyBatis 핵심 사용법]


@Mapper

public interface StudentMyBatisRepository {


@Select("select * from student")

public List<Student> findAll();


@Select("SELECT * FROM student WHERE id = #{id}")

public Student findById(long id);


@Delete("DELETE FROM student WHERE id = #{id}")

public int deleteById(long id);


@Insert("INSERT INTO student(id, name, passport) VALUES (#{id}, #{name}, #{passport})")

public int insert(Student student);


@Update("Update student set name=#{name}, passport=#{passport} where id=#{id}")

public int update(Student student);


}




11. FrontController.java - Controller


FrontController의 내용을 수정하도록 하겠다.



그림 27. FrontController.java - 수정하기


package com.exmple.web.controller;


import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;


import com.exmple.web.db.SqlMapSessionFactory;

import com.exmple.web.mapper.CompUsersMapper;

import com.exmple.web.model.CompUsers;


public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

    SqlSessionFactory factory = SqlMapSessionFactory.getSqlSessionFactory();


/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doAction(request, response);

}


/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doAction(request, response);

}


protected void doAction(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

try (SqlSession session = factory.openSession()) {

  CompUsersMapper mapper = session.getMapper(CompUsersMapper.class);

  CompUsers user = mapper.findByUsername("user");

  

  System.out.println("계정명:" + user.getUsername());

  

}

}


}



파일명: FrontController.java


[첨부(Attachments)]

FrontController.zip




12. 맺음글(Conclusion)


MyBatis 3.5.5와 Maven, JSP/Servlet 그리고 Oracle Databases를 쉽고 빠르게 연동하는 방법에 대해서 소개하였다.


1. [JSP] 20. MyBatis-3.5.5, HikariCP 3.4.2 연동 - Maven(Servlet) Spring 제거버전 (Oracle 19g) - Java 방식, 2020-10-01

https://yyman.tistory.com/1435





* 참고 자료(References)


1. MyBatis - 마이바티스 3 | 시작하기, https://mybatis.org/mybatis-3/ko/getting-started.html, Accessed by 2020-10-01, Last Modified 2020-06-05.

- 비고: 사용 방법이 담겨있음.


2. [JSP] 8. 영속프레임워크 MyBatis를 활용한 CRUD 구현 - JSP와 Oracle (XML 방식), https://yyman.tistory.com/1390, Accessed by 2020-10-01, Last Modified 2020-09-19.

- 비고: MyBatis 셋팅 방법을 참고하였으며, XML 방식을 시도하였으나 안 되었다. (Spring Beans가 안 되는 것을 알게 되었음.)


3. [Spring-Framework] 22(번외). STS 4.4 - Spring Boot Starter - MyBatis(Boot용), HikariCP 3.4 사용하기, https://yyman.tistory.com/1432, Accessed by 2020-10-01, Last Modified 2020-10-01.

- 비고: HikariCP의 properties 방식에 대해서 다시 살펴보았다.

반응형
728x90
300x250

[JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2)


2부에서는 페이징, DB구현부, 모델, 서비스 영역에 대해서 소개하겠다.


1. [JSP] 18. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (1)

https://yyman.tistory.com/1428




14. 페이징네이션 


실제경로: /target/generated-sources/annotations/com/smile/web/model

패키지명: com.smile.web.logic

파일명: Paging.java


아마 이거 하나면, 현존에 있는 페이지 기법은 충분히 소화되지 않겠냐는 생각이다.

물론 정답은 아니다. 오류가 있을 수도 있다.


이러한 복잡한 문제에 직면했을 때, 수학을 공부 많이 해야 한다고 생각한다.

머리가 좋으신 분들이 많으시니깐 더 좋은 방법이 있다면, 개선해봐도 좋을 듯 싶다.


package com.smile.web.logic;


public class Paging {

    private long pageSize; // 게시 글 수

    private long firstPageNo; // 첫 번째 페이지 번호

    private long prevPageNo; // 이전 페이지 번호

    private long startPageNo; // 시작 페이지 (페이징 네비 기준)

    private long pageNo; // 페이지 번호

    private long endPageNo; // 끝 페이지 (페이징 네비 기준)

    private long nextPageNo; // 다음 페이지 번호

    private long finalPageNo; // 마지막 페이지 번호

    private long totalCount; // 게시 글 전체 수

    private long dbStartNum; // db 조회 (시작번호)

    private long dbEndNum; // db 조회 (종료번호)


    /**

     * @return the pageSize

     */

    public long getPageSize() {

        return pageSize;

    }


    /**

     * @param pageSize the pageSize to set

     */

    public void setPageSize(long pageSize) {

        this.pageSize = pageSize;

    }


    /**

     * @return the firstPageNo

     */

    public long getFirstPageNo() {

        return firstPageNo;

    }


    /**

     * @param firstPageNo the firstPageNo to set

     */

    public void setFirstPageNo(long firstPageNo) {

        this.firstPageNo = firstPageNo;

    }


    /**

     * @return the prevPageNo

     */

    public long getPrevPageNo() {

        return prevPageNo;

    }


    /**

     * @param prevPageNo the prevPageNo to set

     */

    public void setPrevPageNo(long prevPageNo) {

        this.prevPageNo = prevPageNo;

    }


    /**

     * @return the startPageNo

     */

    public long getStartPageNo() {

        return startPageNo;

    }


    /**

     * @param startPageNo the startPageNo to set

     */

    public void setStartPageNo(long startPageNo) {

        this.startPageNo = startPageNo;

    }


    /**

     * @return the pageNo

     */

    public long getPageNo() {

        return pageNo;

    }


    /**

     * @param pageNo the pageNo to set

     */

    public void setPageNo(long pageNo) {

        this.pageNo = pageNo;

    }


    /**

     * @return the endPageNo

     */

    public long getEndPageNo() {

        return endPageNo;

    }


    /**

     * @param endPageNo the endPageNo to set

     */

    public void setEndPageNo(long endPageNo) {

        this.endPageNo = endPageNo;

    }


    /**

     * @return the nextPageNo

     */

    public long getNextPageNo() {

        return nextPageNo;

    }


    /**

     * @param nextPageNo the nextPageNo to set

     */

    public void setNextPageNo(long nextPageNo) {

        this.nextPageNo = nextPageNo;

    }


    /**

     * @return the finalPageNo

     */

    public long getFinalPageNo() {

        return finalPageNo;

    }


    /**

     * @param finalPageNo the finalPageNo to set

     */

    public void setFinalPageNo(long finalPageNo) {

        this.finalPageNo = finalPageNo;

    }


    /**

     * @return the totalCount

     */

    public long getTotalCount() {

        return totalCount;

    }


    /**

     * @param totalCount the totalCount to set

     */

    public void setTotalCount(long totalCount) {

        this.totalCount = totalCount;

        this.makePaging();

        this.makeDbPaging();

    }


    /**

     * 페이징 생성 (setTotalCount에서 호출함)

     */

    private void makePaging() {

   

        if (this.totalCount == 0) 

        return; 

        

        // 게시 글 전체 수가 없는 경우

        

        if (this.pageNo == 0) 

        this.setPageNo(1); // 기본 값 설정

        

        if (this.pageSize == 0) 

        this.setPageSize(10); // 기본 값 설정


        long finalPage = (totalCount + (pageSize - 1)) / pageSize; // 마지막 페이지

        

        if (this.pageNo > finalPage) 

        this.setPageNo(finalPage); // 기본 값 설정


        if (this.pageNo < 0 || this.pageNo > finalPage) 

        this.pageNo = 1; // 현재 페이지 유효성 체크


        boolean isNowFirst = pageNo == 1 ? true : false; // 시작 페이지 (전체)

        boolean isNowFinal = pageNo == finalPage ? true : false; // 마지막 페이지 (전체)


        long startPage = ((pageNo - 1) / 10) * 10 + 1; // 시작 페이지 (페이징 네비 기준)

        long endPage = startPage + 10 - 1; // 끝 페이지 (페이징 네비 기준)


        if (endPage > finalPage) { // [마지막 페이지 (페이징 네비 기준) > 마지막 페이지] 보다 큰 경우

            endPage = finalPage;

        }


        this.setFirstPageNo(1); // 첫 번째 페이지 번호


        if (isNowFirst) {

            this.setPrevPageNo(1);    // 이전 페이지 번호

        } else {

            this.setPrevPageNo(((pageNo - 1) < 1 ? 1 : (pageNo - 1))); // 이전 페이지 번호

        }


        this.setStartPageNo(startPage); // 시작 페이지 (페이징 네비 기준)

        this.setEndPageNo(endPage); // 끝 페이지 (페이징 네비 기준)


        if (isNowFinal) {

            this.setNextPageNo(finalPage); // 다음 페이지 번호

        } else {

            this.setNextPageNo(((pageNo + 1) > finalPage ? finalPage : (pageNo + 1))); // 다음 페이지 번호

        }


        this.setFinalPageNo(finalPage); // 마지막 페이지 번호

        

    }

    

    private void makeDbPaging() {

   

    long current = this.getPageNo();

    long weight = this.getPageSize();

    long endNum = 0;

   

    this.setDbEndNum( current * weight );

   

    endNum = this.getDbEndNum();

    this.setDbStartNum( (endNum - weight) + 1 );

   

    }


    /**

     * @return the dbStartNum

     */

public long getDbStartNum() {

return dbStartNum;

}



    /**

     * @param dbStartNum the dbStartNum to set

     */

public void setDbStartNum(long dbStartNum) {

this.dbStartNum = dbStartNum;

}


    /**

     * @return dbEndNum

     */


public long getDbEndNum() {

return dbEndNum;

}



    /**

     * @param dbEndNum the dbEndNum to set

     */

public void setDbEndNum(long dbEndNum) {

this.dbEndNum = dbEndNum;

}

    

}


파일명: Paging.java


[첨부(Attachments)]

Paging.zip




15. Board.java - 모델(Model)


실제경로: /target/generated-sources/annotations/com/smile/web/model

패키지명: com.smile.web.model

파일명: Board.java


게시판 모델에 대한 설계이다.


package com.smile.web.model;


import java.sql.Timestamp;


public class Board {


private long id;

private String name;

private String subject;

private String memo;

private long count;

private Timestamp regidate;

public long getId() {

return id;

}

public void setId(long id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getSubject() {

return subject;

}

public void setSubject(String subject) {

this.subject = subject;

}

public String getMemo() {

return memo;

}

public void setMemo(String memo) {

this.memo = memo;

}


public long getCount() {

return count;

}


public void setCount(long count) {

this.count = count;

}


public Timestamp getRegidate() {

return regidate;

}


public void setRegidate(Timestamp regidate) {

this.regidate = regidate;

}

}



파일명: Board.java


[첨부(Attachments)]

Board.zip




16. BoardService.java - Service 영역


실제경로: /target/generated-sources/annotations/com/smile/web/service

패키지명: com.smile.web.service

파일명: BoardService.java


게시판 서비스에 필요한 영역을 구현한 것이다.


package com.smile.web.service;


import java.util.List;


import com.smile.web.model.Board;


public class BoardService {

private static BoardService service = null;

private static BoardServiceImpl dao = null;

private BoardService() {}

public static BoardService getInstance() {


        if(service == null){

        service = new BoardService();

    dao = BoardServiceImpl.getInstatnce();

        }


        return service;

}

public List<Board> getBoardList(long startNum, long endNum){

return dao.getBoardList(startNum, endNum);

}

public long getTotalCount() {

return dao.getTotalCount();

}

public List<Board> getBoardKeywordList(String keyword, long startNum, long endNum){

return dao.getBoardKeywordList(keyword, startNum, endNum);

}


public long getTotalKeywordCount(String keyword) {

return dao.getTotalKeywordCount(keyword);

}

}



파일명: BoardService.java


[첨부(Attachments)]

BoardService.zip





16. BoardServiceImpl.java - Service 영역(DAO)


실제경로: /target/generated-sources/annotations/com/smile/web/service

패키지명: com.smile.web.service

파일명: BoardServiceImpl.java


DAO 영역에 관한 것이다. (실제 DB를 처리하는 영역)


package com.smile.web.service;


import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;


import com.smile.web.db.DBFactory;

import com.smile.web.model.Board;


public class BoardServiceImpl {

private static BoardServiceImpl boardDAO;

private static DBFactory dbPool;

private BoardServiceImpl() {}

public static BoardServiceImpl getInstatnce() {

if ( boardDAO == null ) {

boardDAO = new BoardServiceImpl();

dbPool = new DBFactory();

}

return boardDAO;

}

public List<Board> getBoardList(long startNum, long endNum){

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    List<Board> boardList = new ArrayList<Board>();


    String sql = "select * from (select /*+INDEX_DESC(tbl_board pk_board) */ rownum rn, A.*" + 

    " from board A order by id desc) " + 

    "where rn >= ? and rn <= ?";


    //System.out.println(sql);

    //System.out.println(startNum + "/" + endNum);

   

    try {

    conn = dbPool.getConnection();

    pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1, startNum);

    pstmt.setLong(2, endNum);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

    // 데이터가 존재할 때, 노드 생성

    node = new Board();

   

    node.setId(rs.getLong(2));

    node.setSubject(rs.getNString(3));

    node.setMemo(rs.getNString(4));

    node.setName(rs.getNString(5));

   

    boardList.add(node);

   

    System.out.println("rs:" + rs.getLong(1));

   

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return boardList;

}

public List<Board> getBoardKeywordList(String keyword, long startNum, long endNum){

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    String allKeyword = "%" + keyword + "%";

   

    List<Board> boardList = new ArrayList<Board>();


    String sql = "select * " + 

    "from (select /*+INDEX_DESC(tbl_board pk_board) */ rownum rn, A.* " + 

    "from board A where subject like ? order by id desc) " + 

    "where rn >= ? and rn <= ?";

   

    //System.out.println(sql);

   

    try {

    conn = dbPool.getConnection();

    pstmt = conn.prepareStatement(sql);

    pstmt.setNString(1, allKeyword);

    pstmt.setLong(2, startNum);

    pstmt.setLong(3, endNum);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

    // 데이터가 존재할 때, 노드 생성

    node = new Board();

   

    node.setId(rs.getLong(2));

    node.setSubject(rs.getNString(3));

    node.setMemo(rs.getNString(4));

    node.setName(rs.getNString(5));

   

    boardList.add(node);

   

    //System.out.println("rs:" + rs.getLong(1));

   

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return boardList;

}

public long getTotalCount() {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    long cnt = 0;


    String sql = "select count(*) from board";

   

    System.out.println("sql:" + sql);


    try {

    conn = dbPool.getConnection();

    //System.out.println( conn.getSchema() );

    pstmt = conn.prepareStatement(sql);

   

    rs = pstmt.executeQuery();

    //System.out.println( rs.getFetchSize() );


    if ( rs.next() ) {

    cnt = rs.getLong(1);

        System.out.println("전체갯수:" + cnt + "/" + rs.getLong(1));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return cnt;

}

public long getTotalKeywordCount(String keyword) {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

    Board node = null;

   

    String allKeyword = "%" + keyword + "%";

   

    long cnt = 0;


    String sql = "select count(*) from board where subject like ?";

   

    //System.out.println("sql:" + sql + "/키:" + keyword);


    try {

    conn = dbPool.getConnection();

    /// System.out.println( conn.getSchema() );

    pstmt = conn.prepareStatement(sql);

    pstmt.setNString(1, allKeyword);

   

    rs = pstmt.executeQuery();

    /// System.out.println( rs.getFetchSize() );


    if ( rs.next() ) {

    cnt = rs.getLong(1);

        System.out.println("특정 갯수:" + cnt + "/" + rs.getLong(1));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    dbPool.close(conn, pstmt, rs);

    }

return cnt;

}


}


파일명: BoardServiceImpl.java


[첨부(Attachments)]

BoardServiceImpl.zip



17. 데이터베이스 - 응용 영역


이 부분이 의외로 어려울 수 있다고 본다. 머리를 조금 써야 한다.


[개선 후 SQL 파일 내용]


-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (BOARD)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE board
(
    id NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    subject VARCHAR2(30),
    memo NCLOB,
    count NUMBER,
    regidate DATE
);

-- Sequence 정의
CREATE SEQUENCE board_sequence
START WITH 1
INCREMENT BY 1;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER board_trigger
BEFORE INSERT
    ON board
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT board_sequence.nextval INTO :NEW.ID FROM dual;
END;


/* 데이터 추가 */
INSERT INTO board (name, subject, memo, count, regidate) VALUES ('홍길동', '안녕하세요.', '메모메모', '0', '2020-09-29 11:11:00');

/* 데이터 등록 후 커밋할 것(대량 정보 처리 후) */
COMMIT;


-- 싱글 쿼리 (페이징)
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1

-- 특정 싱글 쿼리 SQL
SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * from board where subject like '%야해해%' order by id desc
) Z WHERE ROWNUM <= 10
) WHERE RNUM >= 1


파일명: board-tbl-oracle_개선후.sql (변경해도 되는 쿼리)  /  board-tbl-oracle_개선전.sql (이 글의 16번 소스코드에 적용된 페이징 쿼리)


(16번 파일의 페이징 쿼리를 수정해서 사용해도 됨.)

-> 개선 전 쿼리라고 해서 파일을 두 개로 두었음.


[첨부(Attachments)]

board-tbl-oracle.zip (업데이트: 2020-10-11)


페이징 로직 설계가 되었다면, 다음 계획해야 할 작업이 실질적으로는 이 작업이다.

의외로 작업을 하다보면, 골치가 아픈 문제들이 많이 생긴다.


[Oracle Databases 코너에 쿼리 관련 작업에 대해서 추가로 보충하여 소개하도록 하겠다.]

- 단일 테이블 게시물 쿼리 방법, 뷰 테이블 쿼리 방법, 테이블 조인(2개 이상) 사용 방법


-> 태스트 결과로는 "Java 페이징 로직"은 크게 문제가 없다. 
    다만, 나머지 부분은 쿼리라고 본다.



[쿼리 관련 - 보충 글]
1. [Oracle Databases] 번외글 - 게시판 페이징 관련 로직 쿼리, 2020-10-11.
- https://yyman.tistory.com/1466




18. 뷰 - css


css 하나 만들어준다. 보안 영역에 만들지 말고 외부 영역에 만들어준다.



그림 23. style.css 프로젝트 위치


생성할 폴더: 

/src/main/webapp/board

/src/main/webapp/board/css


생성할 파일

/src/main/web/app/board/css/style.css


사실 style.css을 먼저 만들고 나서 작업하는 건 절대적으로 아니다.

웹 페이지를 보고 하나 하나 왕복 화면 전환 등 복합적으로 하면서 수많은 태스트과정을 거쳐서 작성하게 된다.

css 작업을 하는데 있어서도 어떻게 하면 깔끔하면서 표준을 지킬지 고민을 많이 해봐야 한다고 본다.


@charset "utf-8";


/* 1. 제목 */

h3{

font-size:20px;

font-family:'Nanum Gothic';

text-align:center;

}


/* 2. 게시판 */

/* 게시판 목록 출력 */

.board_list{

border-top:1px solid #e2e2e2;

border-bottom:1px solid #e2e2e2;

font-size:12px;

font-family:'Nanum Gothic';

width:900px;

margin:auto;

text-align:center;

}


/* 제목 */

.board_list th{


border-right:1px solid #666;

border-bottom:1px solid #666;

font-size:15px;

font-family:'Nanum Gothic';

height:20px;

text-align:center;

background-color:#eeeeee;

}


/* 내용 */

.board_list td{


border-right:1px solid #e2e2e2;

font-size:15px;

font-family:'Nanum Gothic';

height:20px;

text-align:center;

}


/* 3. 페이징 네이션 */

.paginate{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

}


.paginate .first{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .prev{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .next{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .last{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


.paginate .choice{


font-size:20px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

margin-right:10px;

}


/* 4. 링크 */

a{

text-decoration:none;

color:#666;

}


/* 5. 검색 영역 */

.searchArea{


font-size:15px;

font-family:'Nanum Gothic';

font-color:#666;

text-align: center;

}


파일명: style.css


[첨부(Attachments)]

style.zip




19. 뷰(view) - jsp파일 작성하기


이번에 소개할 것은 include를 실제 적용한 코드이다.



그림 24. board 폴더 내 파일들 (작업할 부분)


생성할 폴더: 

/src/main/webapp/WEB-INF/views/

/src/main/webapp/WEB-INF/views/board


생성할 파일

/src/main/webapp/WEB-INF/views/board/list.jsp

/src/main/webapp/WEB-INF/views/board/search.jsp

/src/main/webapp/WEB-INF/views/board/paging.jsp


list.jsp를 호출하면, search.jsp, paging.jsp도 함께 호출이 된다.

기능을 전문적으로 분리하여 설계한 것이다.




20. 뷰(view) - list.jsp


list.jsp에 관한 것이다.

코드를 보고 한눈에 알 수 없는 사람들을 위해서 실제 동작 결과화면을 소개하겠다.



그림 25. list.jsp 화면 영역 구성도



<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ page import = "java.util.*" %>

<%@ page import = "com.smile.web.model.*" %>

<%@ page session="false" %>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>게시물 목록</title>

<style>

@import url('https://fonts.googleapis.com/css?family=Nanum+Gothic:400,700,800');


</style>

<link href="css/style.css" rel="stylesheet" type="text/css" />

</head>

<body>

<%

List<Board> boardList = (List<Board>)request.getAttribute("list");

%>


<h3>게시물 목록</h3>


<!-- 목록 출력 -->

<table class="board_list">

<tr>

<th style="width:15%;">

번호(Num)

</th>

<th>

제목(Subject)

</th>

<th style="width:13%;">

글쓴이(Author)

</th>

<th style="width:13%; border-right:none;">

조회수(Count)

</th>

</tr>

<%

for(Board board:boardList){

%>

<tr>

<td style="width:15%;">

<%= board.getId() %>

</td>

<td>

<%= board.getSubject() %>

</td>

<td style="width:13%;">

<%= board.getName() %>

</td>

<td style="width:13%; border-right:none;">

<%= board.getCount() %>

</td>

</tr>

<%

}

%>

</table>


<!-- 페이징 -->

<jsp:include page="/WEB-INF/views/board/paging.jsp">

<jsp:param name="customURL" value="${pagingUrl}" />

    <jsp:param name="firstPageNo" value="${paging.firstPageNo}" />

    <jsp:param name="prevPageNo" value="${paging.prevPageNo}" />

    <jsp:param name="startPageNo" value="${paging.startPageNo}" />

    <jsp:param name="pageNo" value="${paging.pageNo}" />

    <jsp:param name="endPageNo" value="${paging.endPageNo}" />

    <jsp:param name="nextPageNo" value="${paging.nextPageNo}" />

    <jsp:param name="finalPageNo" value="${paging.finalPageNo}" />

</jsp:include>


<!-- 검색 -->

<jsp:include page="/WEB-INF/views/board/search.jsp" />


</body>

</html>


파일명: list.jsp


[첨부(Attachments)]

list.zip



21. 뷰(view) - paging.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>



<div class="paginate">

    <a href="${param.customURL}page=${param.firstPageNo}" class="first">처음 페이지</a>

    <a href="${param.customURL}page=${param.prevPageNo}" class="prev">이전 페이지</a>

    <span>

        <c:forEach var="i" begin="${param.startPageNo}" end="${param.endPageNo}" step="1">

            <c:choose>

                <c:when test="${i eq param.pageNo}"><a href="${param.customURL}page=${i}" class="choice">${i}</a></c:when>

                <c:otherwise><a href="${param.customURL}page=${i}">${i}</a></c:otherwise>

            </c:choose>

        </c:forEach>

    </span>

    <a href="${param.customURL}page=${param.nextPageNo}" class="next">다음 페이지</a>

    <a href="${param.customURL}page=${param.finalPageNo}" class="last">마지막 페이지</a>

</div>



파일명: paging.jsp


[첨부(Attachments)]

paging.zip




22. 뷰(view) - search.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>


<div class="searchArea">

<form id="searchForm" action="list.do" method='get'>

<select name="type">

<option value="T">제목</option>

<option value="C">내용</option>

<option value="W">작성자</option>

</select>

<input type="text" name="keyword">

<button class="">검색</button>

</form>

</div>


파일명: search.jsp


[첨부(Attachments)]

search.zip




23. 데이터베이스 작업시 성능 - 꼭 파악하면서 작업해보기


오늘 날 게시판 뿐만 아니라 DB의 비중이 매우 중요한 시대에 직면해 있다.

성능 측정 꼭 해보기 바란다.

동작도 물론 중요한데, 결과는 정확한지 등 많이 고민하고 수 십번, 수 백법 이상 찍어봐야 한다.



그림 26. 데이터베이스 작업 모습 - 오라클 SQL Developer




* 맺음글(Conclusion)


페이징네이션 기반의 게시판 프로젝트에 대해서 살펴보았다.




* 참고자료(References)


1. [Oracle] 오라클 데이터타입(DataType) 총정리, https://coding-factory.tistory.com/416, Accessed by 2020-09-29, Last Modified 2019-11-03.


2. [JSP]include 와 forward 의 페이지 이동, https://jerryjerryjerry.tistory.com/31, Accessed by 2020-09-29, Last Modified 2018-04-13.


3. 오라클 페이징 쿼리, 오라클 paging 방법 - 개발자 삽질 일기, https://programmer93.tistory.com/4, Accessed by 2020-09-29, Last Modified 2019.


4. [OracleDB] 페이징(Paging) 처리하는 법, https://m.blog.naver.com/wideeyed/221796538283, Accessed by 2020-09-29, Last Modified 2020-02-04.

반응형
728x90
300x250

[JSP] 17. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (1)


Command 패턴과 Front Controller 패턴을 적용한 페이징네이션과 검색에 대해서 소개하려고 한다.

불필요한 기능은 다 제거하고 순수한 페이징네이션과 검색 그리고 오라클 연동에 대해서 다뤄보았다.


IDE: Spring-Tool Suite 4-4.7.2 RELEASES

Databases: Oracle Databases 19g

 - Maven 3.6.3/1.16.0.20200610-1735

 - javax.servlet-api (4.0.1) - servlet(서블렛)

 - jstl (1.2) - jstl 태그 적용

 - taglibs (1.1.2) - c태그 적용

 - HikariCP (3.4.2) - 커넥션 풀

 - com.oracle.database.jdbc - ojdbc8 (19.7.0.0)



[쿼리 관련해서 조금 더 보충한 글이다.]
1. [Oracle Databases] 번외글 - 게시판 페이징 관련 로직 쿼리, 2020-10-11.
- https://yyman.tistory.com/1466




1. 프로젝트 구조


작성할 프로젝트를 미리 살펴보면 조금 작업해야 할 양이 의외로 상당하다는 것을 알 수 있다.



그림 1, 그림 2. 프로젝트 작성 구조도




2. 게시판 - 설계


데이터베이스 설계는 매우 간단하게 작성하였다.


그림 3. Board - 테이블 설계(1)




그림 4. Board 게시판 설계(2)





3. 사용자 인터페이스 - 결과


이번 주제에서 다뤄볼 프로젝트의 완성 모습이다.

참고로 페이징네이션 로직에 대해서 다 기억할 수 없다.


다만 어떤 흐름인지는 대략적으로 알고는 있으면 하는 바람이다.

워낙 페이징 로직에 대해서 전문적으로 잘 연구하시는 분들이 많이 계시니깐 그건 골고루 참고도 해보고 개선도 해보고 하면 될 것 같다.



그림 5. list.do - 첫 화면



그림 6. list.do - 7번 페이지 / 기초



그림 7. list.do - 10106번 페이지 / 기초


10106번 페이지를 통해서 알 수 있는 것은 약 10106 * 10 = 101,106개의 DB가 존재한다는 것을 알 수 있다.

그림 8부터는 응용부분에 가까워진다.



그림 8. list.do - 키워드 검색 및 페이징 처리(1) / 심화


의외로 응용력이 많이 요구되는 부분이다.



그림 9. list.do - 키워드 검색 및 페이징 처리(2) / 심화


키워드 검색을 했을 때 "친"은 약 13개가 있었다는 사실을 유추할 수 있다.



그림 10. list.do - 키워드 검색 및 페이징 처리(3)


참고로 list.do 반응에 대해서 여러 조건으로 튜닝이 필요하다.



4. 프로젝트 생성하기


프로젝트는 Maven Project로 생성하여 진행하였다.



그림 11. Maven Project 생성하기(1)


File -> New -> Maven Project를 클릭한다.



그림 12. Maven Project 생성하기(2)


org.apache.maven.archetypes | maven-archetype-web을 선택하고 Next를 누른다.



그림 13. Maven Project 생성하기(3)


프로젝트의 Group Id, Artifact Id를 입력한다.

Finish를 누른다.




5. POM.xml - 설정하기


다음은 pom.xml 파일에 대한 설정이다.


<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>


  <groupId>com.boardMaven</groupId>

  <artifactId>web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>


  <name>board Maven Webapp</name>

  <!-- FIXME change it to the project's website -->

  <url>http://www.example.com</url>


  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>1.7</maven.compiler.source>

    <maven.compiler.target>1.7</maven.compiler.target>

  </properties>


  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.11</version>

      <scope>test</scope>

    </dependency>

    

<!-- Servlet -->

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>

<!-- JSTL -->

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>1.2</version>

</dependency>

<!-- taglibs -->

<dependency>

<groupId>taglibs</groupId>

<artifactId>standard</artifactId>

<version>1.1.2</version>

<scope>compile</scope>

</dependency>


<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->

<dependency>

    <groupId>com.zaxxer</groupId>

    <artifactId>HikariCP</artifactId>

    <version>3.4.2</version>

</dependency>

<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.7.0.0</version>

</dependency>

  </dependencies>


  <build>

    <finalName>board</finalName>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->

      <plugins>

        <plugin>

          <artifactId>maven-clean-plugin</artifactId>

          <version>3.1.0</version>

        </plugin>

        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->

        <plugin>

          <artifactId>maven-resources-plugin</artifactId>

          <version>3.0.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-compiler-plugin</artifactId>

          <version>3.8.0</version>

        </plugin>

        <plugin>

          <artifactId>maven-surefire-plugin</artifactId>

          <version>2.22.1</version>

        </plugin>

        <plugin>

          <artifactId>maven-war-plugin</artifactId>

          <version>3.2.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-install-plugin</artifactId>

          <version>2.5.2</version>

        </plugin>

        <plugin>

          <artifactId>maven-deploy-plugin</artifactId>

          <version>2.8.2</version>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip



6. 프로젝트의 Build-Path, Properties 수정하기


프로젝트의 자바 버전에 대한 환경설정이다.



그림 14. 프로젝트의 마우스 오른쪽 버튼 모습


프로젝트를 클릭한다.

마우스 오른쪽 버튼을 누른다.

Properties를 클릭한다.



그림 15. Build-Path 환경설정


JRE System Library를 JavaSE-14버전으로 변경한다. (14버전으로 변경)

Apply를 누른다.



그림 16. Project Facets 환경 설정


Project Facets의 Java 버전을 14로 변경한다.

Apply를 누른다.

Apply and close를 누른다.



7. Resource 폴더와 db.properties 만들기


Resource 폴더와 db.properties를 생성할 것이다.



그림 17. /src/main 폴더 - 오른쪽 버튼 메뉴 모습


src/main 폴더를 마우스 오른쪽으로 클릭한다.

New-> Folder를 클릭한다.


폴더명: Resource



그림 17. /src/main/resource 폴더 - 오른쪽 버튼 메뉴 모습


src/main/resource 폴더를 마우스 오른쪽으로 클릭한다.

New-> File를 클릭한다.


파일명: db.properties



그림 18. /src/main/resource 폴더와 db.properties 모습


성공적으로 만들어진 것을 확인할 수 있다.



그림 19. db.propeties 모습


jdbcUrl=jdbc:oracle:thin:@localhost:1521:orcl

dataSourceClassName=oracle.jdbc.driver.OracleDriver

dataSource.user=userName

dataSource.password=password

cachePrepStmts=true

prepStmtCacheSize=250

prepStmtCacheSqlLimit=2048


파일명: db.propeties


[첨부(Attachments)]

db.zip




9. Servlet 생성하기


servlet 생성하고 나서 web.xml 수정을 진행하면 타이핑을 적게 해도 되는 장점이 생긴다.



그림 20. 프로젝트 오른쪽 버튼 - 메뉴 모습


프로젝트를 선택한다.

마우스 오른쪽 버튼을 클릭한다.

New->Servlet을 클릭한다.



그림 21. Servlet 생성하기


Java package명을 입력한다. ("예: com.smile.web.controller // 이런 형식으로 작성하면 됨.)

Class name을 입력한다. ("예: FrontController" )

Finish를 누른다.



10. web.xml - 수정하기


charset 초기값을 추가하였다.

Servlet 2.5 문서 양식에서 3.1 양식으로 변경하였다.


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 

id="WebApp_ID" version="3.0">

 

  <display-name>Archetype Created Web Application</display-name>

  

  <servlet>

  <servlet-name>FrontController</servlet-name>

  <servlet-class>com.smile.web.controller.FrontController</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet-mapping>

  <servlet-name>FrontController</servlet-name>

  <url-pattern>*.do</url-pattern>

  </servlet-mapping>

  

</web-app>


파일명: web.xml


[첨부(Attachments)]

web.zip




10. 폴더 2개(views, settings), 파일 하나 만들기(root-servlet.xml)


추가적으로 만들어줄 폴더와 파일이 있다.


폴더1: /src/main/webapp/WEB-INF/settings

폴더2: /src/main/webapp/WEB-INF/views

파일: /src/main/webapp/WEB-INF/settings/root-servlet.xml


Settings 폴더는 큰 의미는 없지만, 활용할 여지를 고려하여 생성하였다.
이 프로젝트에서는 root-servlet.xml은 직접 사용되진 않는다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
</beans>


파일명: root-servlet.xml


[첨부(Attachments)]

root-servlet.zip



그림 22. 작업된 프로젝트의 모습




11. FrontController.java - 서블렛(Servlet)


실제경로: /target/generated-sources/annotations/com/smile/web/controller

패키지명: com.smile.web.controller

파일명: FrontController.java


FrontController를 list만 정의하였다.

의외로 코드를 살펴보면, 간단하다는 것을 알 수 있다.


package com.smile.web.controller;


import java.io.IOException;

import java.sql.SQLException;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import com.smile.web.db.DBFactory;


public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

private String charset = null;

/**

* @see HttpServlet#doGet(HttpServletRequest req, HttpServletResponse res)

*/

protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}


/**

* @see HttpServlet#doPost(HttpServletRequest req, HttpServletResponse res)

*/

protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}

// FrontController 패턴 & Command 패턴

protected void doAction(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

charset = sc.getInitParameter("charset");


req.setAttribute("charset", charset);

req.setCharacterEncoding(charset);

res.setContentType("text/html; charset=" + charset);


String uri = req.getRequestURI();

String conPath = req.getContextPath();

String command = uri.substring(conPath.length());


Controller subController = null;


if(command.equals("/board/list.do")){

System.out.println("-----------------------------");

System.out.println("게시판 목록");

System.out.println("-----------------------------");

req.setAttribute("controllerName", "list");

subController = new com.smile.web.controller.board.ListController();

subController.execute(req, res);

}

}


}



파일명: FrontController.java


[첨부(Attachments)]

FrontController.zip




12. Controller.java - 인터페이스


실제경로: /target/generated-sources/annotations/com/smile/web/controller

패키지명: com.smile.web.controller

파일명: Controller.java


인터페이스에 관한 것이다. (forward 정의 관련한 것)


package com.smile.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public interface Controller {

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;


}


파일명: Controller.java


[첨부(Attachments)]

Controller.zip



12. /Board/ListController.java - 클래스


실제경로: /target/generated-sources/annotations/com/smile/web/controller/board

패키지명: com.smile.web.controller.board

파일명: ListController.java


게시판 목록에 관한 컨트롤러 명세이다.


package com.smile.web.controller.board;


import java.io.IOException;

import java.net.URLEncoder;

import java.util.List;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import com.smile.web.controller.Controller;

import com.smile.web.logic.Paging;

import com.smile.web.model.Board;

import com.smile.web.service.BoardService;

import com.smile.web.util.HttpUtil;


public class ListController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

String controllerName = (String) req.getAttribute("controllerName");

BoardService service = BoardService.getInstance();

List<Board> boardList = null;

// long totalCount = boardList.size();

long currentPage = 1; // 기본값

long pageSize = 10;

long totalCount = service.getTotalCount();

long startNum, endNum;

String keyword = null;

String pagingUrl = controllerName + ".do?";

// 페이지 번호 존재할 때

if (req.getParameter("page") != null){

currentPage = Integer.valueOf( req.getParameter("page") );

}

// 키워드가 존재할 때

if (req.getParameter("keyword") != null) {

keyword = req.getParameter("keyword");

totalCount = service.getTotalKeywordCount(keyword);

// 키워드 값이 하나라도 존재할 때

if ( keyword.length() > 0) {

pagingUrl = pagingUrl + "keyword=" + URLEncoder.encode(keyword, "UTF-8") + "&";

}

}

        Paging paging = new Paging();

        /*

        paging.setPageNo(1);

        paging.setPageSize(10);

        paging.setTotalCount(totalCount);

        */

        

        paging.setPageNo(currentPage);

        paging.setPageSize(pageSize);

        paging.setTotalCount(totalCount);


        System.out.println("현재페이지번호:" + currentPage);

        System.out.println("페이지크기:" + pageSize);

        System.out.println("키워드:" + keyword);

        System.out.println("페이징URL:" + pagingUrl);

        

        startNum = paging.getDbStartNum();

        endNum = paging.getDbEndNum();


        

// 키워드가 존재할 때

if (keyword != null) {

// 키워드 값이 하나라도 존재할 때

if ( keyword.length() > 0) {

boardList = service.getBoardKeywordList(keyword, startNum, endNum);

}

else {

boardList = service.getBoardList(startNum, endNum);

}

}

else {

        boardList = service.getBoardList(startNum, endNum);

}

        

        req.setAttribute("paging", paging);

        req.setAttribute("list", boardList);

        req.setAttribute("pagingUrl", pagingUrl);

HttpUtil.forward(req, res, "/WEB-INF/views/board/" + controllerName + ".jsp");

}


}



파일명: ListController.java


[첨부(Attachments)]

ListController.zip



13. HttpUtil.java - 클래스


실제경로: /target/generated-sources/annotations/com/smile/web/util

패키지명: com.smile.web.controller.util

파일명: HttpUtil.java


RequestDispatcher와 forward에 대한 정의이다.

추가로 업로드 기능에 대한 명세도 있다.


package com.smile.web.util;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.PrintWriter;


import javax.servlet.RequestDispatcher;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



public class HttpUtil extends HttpServlet {

private static final long serialVersionUID = 1L;

private static String charset = null;


public static void forward(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

try {

RequestDispatcher dispatcher = req.getRequestDispatcher(path);

dispatcher.forward(req, res);

}catch(Exception e) {

e.printStackTrace();

}

}

public static void fileUpload(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {


charset = (String) req.getAttribute("charset");

System.out.println(charset);

PrintWriter out = res.getWriter();

// 파일 업로드된 경로

String root = req.getSession().getServletContext().getRealPath("/");

String savePath = root + "upload" + File.separator + "upload";

// 서버에 실제 저장된 파일명

String filename = "1600955663095" ;

System.out.println("파일 실제 폴더경로:" + savePath);

// 실제 내보낼 파일명

String orgfilename = "license한글.txt" ;

req.setCharacterEncoding(charset);

res.setCharacterEncoding(charset);

InputStream in = null;

OutputStream os = null;

File file = null;

boolean skip = false;

String client = "";

try{

    // 파일을 읽어 스트림에 담기

    try{

        file = new File(savePath, filename);

        in = new FileInputStream(file);

    }catch(FileNotFoundException fe){

        skip = true;

    }

    client = req.getHeader("User-Agent");

    // 파일 다운로드 헤더 지정

    res.reset() ;

    res.setContentType("application/octet-stream");

    res.setHeader("Content-Description", "JSP Generated Data");

    if(!skip){

        // IE

        if(client.indexOf("MSIE") != -1){

            res.setHeader ("Content-Disposition", "attachment; filename="+new String(orgfilename.getBytes("KSC5601"),"ISO8859_1"));

        }else{

            // 한글 파일명 처리

            orgfilename = new String(orgfilename.getBytes("KSC5601"),"iso-8859-1");

            res.setHeader("Content-Disposition", "attachment; filename=\"" + orgfilename + "\"");

            res.setHeader("Content-Type", "application/octet-stream; charset=utf-8");

        }  

        res.setHeader ("Content-Length", ""+file.length() );

        os = res.getOutputStream();

        

        byte b[] = new byte[(int)file.length()];

        int leng = 0;

        while( (leng = in.read(b)) > 0 ){

            os.write(b,0,leng);

        }

    }else{

    // 한글 깨짐 - 해결

    res.setContentType("text/html;charset=" + charset);

        out.println("<html><head>");

        out.println("<script language='javascript'>alert('파일을 찾을 수 없습니다.');history.back();</script>");

        out.println("</head><body></body></html>");

    }

    in.close();

    os.close();

}catch(Exception e){

e.printStackTrace();

}

    

}


}



파일명: HttpUtil.java


[첨부(Attachments)]

HttpUtil.zip



13. DBFactory.java - 커넥션 풀 적용


실제경로: /target/generated-sources/annotations/com/smile/web/db

패키지명: com.smile.web.controller.db

파일명: DBFactory.java


HikariCP 3.4.2를 적용한 코드이다.


package com.smile.web.db;



import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.Properties;

import java.io.IOException;


import java.io.InputStream;


import java.io.Reader;

import java.util.Properties;

import java.sql.Connection;

import java.sql.SQLException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.zaxxer.hikari.HikariConfig;

import com.zaxxer.hikari.HikariDataSource;


import oracle.jdbc.pool.OracleDataSource;


public class DBFactory {


private static final Logger logger = LoggerFactory.getLogger(DBFactory.class);


    private static String CLASSNAME;

    private static String JDBC_URL;

    private static String USERNAME;

    private static String PASSWORD;

    private static String CACHE_PREP_STMTS;

    private static HikariDataSource ds;


    private HikariConfig config;


    

    public DBFactory() {


    InputStream inputStream;

    config = new HikariConfig();


        String resource = "db.properties";

        Properties properties = new Properties();


        try {


        inputStream = getClass().getClassLoader().getResourceAsStream(resource);

            properties.load(inputStream);


            System.out.println("jdbcurl:" + properties.getProperty("jdbcUrl"));

            System.out.println("className" + properties.getProperty("dataSourceClassName"));


            CLASSNAME = properties.getProperty("dataSourceClassName");

            JDBC_URL = properties.getProperty("jdbcUrl");

            USERNAME = properties.getProperty("dataSource.user");

            PASSWORD = properties.getProperty("dataSource.password");

            CACHE_PREP_STMTS = properties.getProperty("cachePrepStmts");


            config.setDriverClassName(CLASSNAME);

            config.setJdbcUrl( JDBC_URL );

            config.setUsername( USERNAME );

            config.setPassword( PASSWORD );

            

            config.addDataSourceProperty( "cachePrepStmts" , CACHE_PREP_STMTS );

            config.addDataSourceProperty( "prepStmtCacheSize" , "250" );

            config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );

            

            ds = new HikariDataSource( config );

            

        } catch (IOException e) {

            e.printStackTrace();

        }


    }


    public Connection getConnection() throws SQLException {

   

    /*

        try(Connection con = ds.getConnection()){

            System.out.println("연결상태확인:" + con);

            

            String sql = "SELECT * from board where id > 4 and id < 10";

            

            PreparedStatement pstmt = con.prepareStatement(sql);

            ResultSet rs = pstmt.executeQuery();

            

            while (rs.next()) {

                System.out.println("진짜 연결되었는가:" + rs.getString(2));

            }

        }

        catch(Exception e) {

        System.out.println("연결실패확인:" + e.getMessage());

        }

    */

   

        return ds.getConnection();

    }

    

    public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

close(conn, ps); // Recursive 구조 응용(재귀 함수)

} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {


try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {

try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


}

    

}


파일명: DBFactory.java


[첨부(Attachments)]

DBFactory.zip



* 2부에서는 핵심적인 로직 등에 대해서 소개하겠다.


2부에서 만나요.


1. [JSP] 17. JSP/Servlet MVC2 - 페이징네이션과 검색 그리고 오라클 프로젝트 (2), 2020-09-30

https://yyman.tistory.com/1429


반응형
728x90
300x250

[JSP] 16. 쿠키(Cookie) - 프로젝트(생성, 조회, 삭제)


쿠키에 대해서 소개하려고 한다.

쿠키라는 부분은 조작을 해버리면, 보안 자체가 매우 취약해서 사용 안 하는 것을 권장한다.

물론 전혀 사용 안 하는 것은 아니다.


쿠키는 서버 측으로부터 세션을 클라이언트로부터 내려받아서 세션 유지를 처리해주는 기술 중 하나이다.

서버와의 연결이 끊어져도 쿠키를 통해서 정보를 유지할 수 있다.


인터넷 검색을 시도 해보면, "특정 쿠키 변경"도 종종 검색되긴 하지만, 수 차례 태스트 결과로는 변경 작업은 시도 할 수 없다.

시도하려면, 쿠키를 전체 제거하고 다시 생성하는 방법 말고는 딱히 없다고 본다.



1. Cookie란?


쿠키란, 서버가 클라이언트에 저장하는 정보로써 클라이언트 쪽에 필요한 정보를 저장해놓고 필요할 때 추출하는 것을 지원하는 기술이다.


쿠키는 간단하게 구현할 수 있어서 "사용자 인증", "쇼핑몰 구축" 등에도 사용할 수 있지만, 브라우저에 저장되는 한계, 클라이언트가 직접 쿠키를 조작할 수 있기 때문에 보안에 취약해지는 단점이 있다.


- Cookie 생성 = new Cookie(String name, String value)

- 유효시간 설정 = setMaxAge(int expiry) 

- 쿠키 경로 설정 = setPath(String uri)

- 쿠키 도메인 설정 = setDomain(String domain)

- 쿠키 전송 = addCookie(Cookie cookie)


...... 쿠키는 직접 코드를 보는 게 훨씬 이해되는 데 도움된다고 주장한다.



2. Project 생성하기


이번 프로젝트는 꼭 반드시 Maven Project로 생성해야만 동작하는 건 아니다.

pom.xml을 사용하면, 라이브러리 관리가 수월해져서 그렇다.



그림 1. Maven Project 생성하기


org.apache.maven.archetypes  |   maven-archetype-webapp를 선택한 후 Next를 누른다.




그림 2. Maven Project 생성하기


Group ID와 Artifact Id를 입력한다.

Finish를 누른다.



3. pom.xml - 설정하기


이 글에서는 javax.servlet만을 추가하였다.



그림 3. Maven Project 생성하기




4. Servlet 생성하기


몇 가지 Servlet을 생성한다.



Java Resources에서 오른쪽 버튼을 누른다.

New->Servlet을 클릭한다.



그림 4. Servlet 생성 방법1


Java package명과 Class name명을 입력한다.

Finish를 누른다.




그림 5. Servlet 생성 방법2~4(반복)


그림 4의 동일한 방법으로 생성해준다.



5. web.xml - 설정하기


크게 많은 것을 작성한 건 아니다.

servlet 부분은 자동생성된 부분이다.

web-app, init-param 등 몇 가지를 수정, 추가하였다.


<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 

id="WebApp_ID" version="3.0">

 

  <display-name>Archetype Created Web Application</display-name>

  <welcome-file-list>

    <welcome-file>index.html</welcome-file>

    <welcome-file>index.htm</welcome-file>

    <welcome-file>index.do</welcome-file>

    <welcome-file>default.html</welcome-file>

    <welcome-file>default.htm</welcome-file>

    <welcome-file>default.do</welcome-file>

  </welcome-file-list>

  

  <servlet>

  <servlet-name>CookieTest1Servlet</servlet-name>

  <servlet-class>com.mavenCookie.web.CookieTest1Servlet</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet>

  <servlet-name>CookieExtract</servlet-name>

  <servlet-class>com.mavenCookie.web.CookieExtract</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet>

  <servlet-name>CookieAllRemove</servlet-name>

  <servlet-class>com.mavenCookie.web.CookieAllRemove</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet>

  <servlet-name>CookieModify</servlet-name>

  <servlet-class>com.mavenCookie.web.CookieModify</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  

  <servlet-mapping>

  <servlet-name>CookieTest1Servlet</servlet-name>

  <url-pattern>/CookieTest1Servlet</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

  <servlet-name>CookieExtract</servlet-name>

  <url-pattern>/CookieExtract</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

  <servlet-name>CookieAllRemove</servlet-name>

  <url-pattern>/CookieAllRemove</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

  <servlet-name>CookieModify</servlet-name>

  <url-pattern>/CookieModify</url-pattern>

  </servlet-mapping>

</web-app>



파일명: web.xml


[첨부(Attachments)]

web.zip




6. index.jsp


index.jsp 파일이다.

확인 기능을 손쉽게 할 수 있도록 구현하였다.


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8"></meta>

<title>Cookie 태스트</title>

<style>

a{

text-decoration:none;

}

</style>

</head>

<body>


<h2>Hello World!</h2>


<h3>이클립스 웹에서는 확인 불가</h3>

<a href="CookieTest1Servlet">쿠키 생성</a><br/>

<a href="CookieExtract">쿠키 추출</a><br/>

<a href="CookieAllRemove">쿠키 전체 삭제</a><br/>

<a href="CookieModify">쿠키 변경(결론: 불가능)</a><br/>

<div style="width:400px; height:200px; background-color:#e2e2e2">

<h5>참고로 웹 브라우저에서 쿠키를 제거하는 옵션을 켜버리면 유지 자체에 의미가 없어짐.</h5>


</div>

</body>

</html>



파일명: insert.jsp


[첨부(Attachments)]

index.zip




7. Servlet - CookieTest1Servlet


쿠키 생성 방법에 대한 소스이다.


package com.mavenCookie.web;


import java.io.IOException;

import java.io.PrintWriter;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class CookieTest1Servlet extends HttpServlet {

private static final long serialVersionUID = 1L;

       

    public CookieTest1Servlet() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

String charset = sc.getInitParameter("charset");

res.setContentType("text/html;charset=" + charset);

PrintWriter out = res.getWriter();

// 1. 쿠키 생성 - 기본 시간(브라우저 종료시 전송받은 쿠키 사라짐)

Cookie c1 = new Cookie("id", "userID");

c1.setPath("/");

res.addCookie(c1);

// 2. 쿠키 생성 - 쿠키 3시간 설정(60sec * 60Min * 3Hour)

Cookie c2 = new Cookie("mymy", "1234");

c2.setMaxAge(60 * 60 * 3);

c2.setPath("/");

res.addCookie(c2);

// 3. 쿠키 생성 - 쿠키 5일 설정(60sec * 60Min * 24Hour * 5Day)

Cookie c3 = new Cookie("subject", "zaza");

c3.setMaxAge(60 * 60 * 24 * 5);

c3.setPath("/"); // 경로는 루트로 지정함.

res.addCookie(c3);

out.println("쿠키 전송 완료<br/>");

out.println("<a href=\"index.jsp\">이전</a>");

out.close();

}


protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

}


}



파일명: CookieTest1Servlet.java


[첨부(Attachments)]

CookieTest1Servlet.zip




8. Servlet - CookieExtract


쿠키 내용을 조회하는 방법에 관한 것이다.


package com.mavenCookie.web;


import java.io.IOException;

import java.io.PrintWriter;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class CookieExtract extends HttpServlet {

private static final long serialVersionUID = 1L;

       

    public CookieExtract() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

String charset = sc.getInitParameter("charset");

res.setContentType("text/html;charset=" + charset);

PrintWriter out = res.getWriter();

Cookie[] list = req.getCookies();


// 삭제 여부 - 판별

if ( list != null ) {

out.print("유지 상태<br/>");

}else

{

out.print("삭제 완료<br/>");

}

// 쿠키 찾기(seq방식)

out.println("쿠키 검색1<br/>");

for ( int i = 0; list != null && i < list.length; i++ ) {

out.println( list[i].getName() + ":value=" + list[i].getValue() + "<br/>" );

}

// for 문 - 응용2

out.println("쿠키 검색2<br/>");

int i = 0;

for( Cookie cookie : list) {

out.println( list[i].getName() + ":value=" + list[i].getValue() + "<br/>" );

i++;

}

out.println("<a href=\"index.jsp\">이전</a>");

out.close();

}


protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

}


}



파일명: CookieExtract.java


[첨부(Attachments)]

CookieExtract.zip



9. Servlet - CookieAllRemove


쿠키를 전체 삭제하는 방법이다.


package com.mavenCookie.web;


import java.io.IOException;

import java.io.PrintWriter;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class CookieAllRemove extends HttpServlet {

private static final long serialVersionUID = 1L;


    public CookieAllRemove() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

ServletConfig sc = this.getServletConfig();

String charset = sc.getInitParameter("charset");

res.setContentType("text/html;charset=" + charset);

PrintWriter out = res.getWriter();

Cookie[] list = req.getCookies();

for ( int i = 0; list != null && i < list.length; i++ ) {

// 만료처리 전

out.print( "이전 list[" + i + "]: " + list[i].getName() );

out.print( "<br/>" );

// 유효시간 0으로 설정하면 만료처리됨.

Cookie kc = new Cookie(list[i].getName(), null) ;

    kc.setMaxAge(0) ;

    kc.setPath("/");


// 응답에 쿠키 추가

res.addCookie(kc);

kc = null;

}

out.println("<br/>");

out.println("<a href=\"index.jsp\">이전</a>");

out.close();

}


protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

}


}



파일명: CookieAllRemove.java


[첨부(Attachments)]

CookieAllRemove.zip



10. Servlet - CookieModify(안 되는 코드)


안 된다고 표기를 해둔 이유는 setValue 등으로 수정 후 넘기면 될 것 같지만, 전혀 되지 않는다.


package com.mavenCookie.web;


import java.io.IOException;

import java.io.PrintWriter;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


/**

 * Servlet implementation class CookieModify

 */

public class CookieModify extends HttpServlet {

private static final long serialVersionUID = 1L;

       

    public CookieModify() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


ServletConfig sc = this.getServletConfig();

String charset = sc.getInitParameter("charset");

res.setContentType("text/html;charset=" + charset);

PrintWriter out = res.getWriter();

Cookie[] list = req.getCookies();

for ( Cookie cookie : list  ) {

if ( cookie.getName().equals("id")) {

cookie.setValue("haha");

res.addCookie(cookie);

}

}


out.println("<br/>");

out.println("<a href=\"index.jsp\">이전</a>");

out.close();

}


protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

}


}



파일명: CookieModify.java


[첨부(Attachments)]

CookieModify.zip



* 맺음글(Conclusion)


쿠키 프로젝트로 가볍게 쿠키 사용 방법에 대해서 살펴보았다.

반응형
728x90
300x250

[JSP] 15. Jsp/Servlet(MVC) - Session(세션) 프로젝트(로그인) - (2)


1부에 이어서 "JSP/Servlet 기반의 세션 프로젝트"를 진행하도록 하겠다.


[JSP] 14. Jsp/Servlet(MVC) - Session(세션) 프로젝트(로그인) - (1), 2020-09-25

https://yyman.tistory.com/1416




10. View - Login.jsp


/member/login.do에 관한 페이지이다.


생성해야 할 경로 위치: /src/main/webapp/WEB-INF/views/member


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>로그인 - Member Login(세션 - Session)</title>

<style>

a { 

text-decoration:none 

}

body{

font-size:12px;

font-family:'Arial';

}

#memberTbl{

width:700px;

border:1px solid #e2e2e2;

text-align:center;

margin:auto;

}

div{

text-align:center;

}

</style>


</head>

<body>


<div>


<h3>로그인 페이지(Login - Page) - 세션(Session)</h3>

<!-- 본문 -->

<form action="process.do" method="POST">

<table id="memberTbl">

<tr>

<td style="width:20%; text-align:center;">

아이디(userID)

</td>

<td style="border-left:1px solid #e2e2e2; text-align:center;">

<input type="text" name="userID" style="width:90%">

</td>

</tr>

<tr>

<td style="width:20%; border-top:1px solid #e2e2e2; text-align:center;">

비밀번호(password)

</td>

<td style="border-left:1px solid #e2e2e2; border-top:1px solid #e2e2e2; text-align:center;">

<input type="text" name="password" style="width:90%">

</td>

</tr>

<tr>

<td colspan="2" style="border-top:1px solid #e2e2e2; text-align:center;">

<input type="submit" value="로그인(Login)" style="width:90%">

</td>

</tr>

</table>

</form>

</div>


</body>

</html>


파일명: login.jsp


[첨부(Attachments)]

login.zip



11. View - error_alert.jsp


에러 페이지에 관한 것이다.

시중 책을 보면, ServletController 내에 PrintWriter 함수로 구현되어 있는데, 해당 부분을 아예 분리시켰다.


생성해야 할 경로 위치: /src/main/webapp/WEB-INF/views/member



<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!-- 구현 영역(서버 사이드) -->

<%

String msg = (String)session.getAttribute("member_error_msg");

String redirect_url = (String)session.getAttribute("member_redirect_url");

%>


<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>에러 - 페이지(Error - Page / Session)</title>

<script>

alert('<%= msg %>');

location.href('<%= redirect_url %>');

</script>

</head>

<body>


</body>

</html>


파일명: error_alert.jsp


[첨부(Attachments)]

error_alert.zip



12. View - logon.jsp


logon.jsp 페이지에 관한 것이다.


생성해야 할 경로 위치: /src/main/webapp/WEB-INF/views/member


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!-- 구현 -->

<%

String userID = (String)session.getAttribute("userID");

%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>로그인 상태 - <%= userID %> 세션(Session)</title>

<style>

<!-- 바디 영역 -->

body{

font-family:'Arial';

font-size:13px;

}

#memberTbl{

font-family:'Arial';

font-size:13px;

width:700px;

border:2px solid #e2e2e2;

text-align:center;

margin:auto;

}

a { 

text-decoration:none 

}

div{

text-align:center;

}


</style>


<script>

function welcome(){

alert("Hello World");

}


function logout(){


location.href ('logout.do');

}

</script>

</head>

<body>


<div>

<h3>로그인 상태 출력 - 페이지(Session)</h3>


<!-- 화면 출력 -->

<table id="memberTbl">

<tr>

<td style="width:20%">

아이디(ID)

</td>

<td style="border-left:2px solid #e2e2e2;">

<%= userID %>

</td>

</tr>

<tr>

<td colspan="2" style="border-top:2px solid #e2e2e2;">

<a href="javascript:welcome();">인사말 출력</a>

&nbsp;&nbsp;

<a href="javascript:logout();">로그아웃</a>

</td>

</tr>

</table>

</div>


</body>

</html>


파일명: logon.jsp


[첨부(Attachments)]

logon.zip



13. Controller - MemberLoginController.java


MemberLoginController.java에 관한 것이다.

로그인 시작 페이지에 대한 처리 내용을 기술한 영역이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class MemberLoginController implements Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

HttpUtil.forward(req, res, "/WEB-INF/views/member/login.jsp");

}


}



파일명: MemberLoginController.jsp


[첨부(Attachments)]

MemberLoginController.zip




14. Controller - MemberProcessController.java


로그인 과정에 대한 처리 내용을 기술한 영역이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


public class MemberProcessController implements Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


String id;

String passwd;

String charset;

HttpSession session;

charset = (String) req.getAttribute("charset");

req.setCharacterEncoding(charset);

id = req.getParameter("userID");

passwd = req.getParameter("password");

// System.out.println("userID:" + id);

// System.out.println("password:" + passwd);

// 세션 정보

session = req.getSession();

// 세션 생성

if ( id.equals("user") && passwd.equals("1234") ) {

session.setAttribute("userID", id);

session.setAttribute("userName", "회원");


res.sendRedirect("logon.do");

}

else {

session.setAttribute("member_error_msg", "아이디와 비밀번호를 확인하세요.");

session.setAttribute("member_redirect_url", "login.do");

res.sendRedirect("errorAlert.do");

}

}


}


파일명: MemberProcessController.jsp


[첨부(Attachments)]

MemberProcessController.zip



15. Controller - MemberLogonController.java


로그인이 되었을 때 출력되는 화면에 관한 것이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


public class MemberLogonController implements Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


HttpSession session = req.getSession();

String userID = (String) session.getAttribute("userID");

// 세션이 존재할 때

if ( userID != null )

{

System.out.println("세션 존재 - 로그인");

HttpUtil.forward(req, res, "/WEB-INF/views/member/logon.jsp");

}

else {

System.out.println("세션 없음.");


session.setAttribute("member_error_msg", "로그인 상태를 확인해주세요.");

session.setAttribute("member_redirect_url", "login.do");

res.sendRedirect("errorAlert.do");

} // end of if

}


}



파일명: MemberLogonController.jsp


[첨부(Attachments)]

MemberLogonController.zip



16. Controller - MemberLogoutController.java


로그아웃 처리에 관한 기술이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


public class MemberLogoutController implements Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {


HttpSession session = req.getSession();

String userID = (String)session.getAttribute("userID");

// 세션이 존재할 때

if ( userID != null ) {

session.removeAttribute("userID");

session.removeAttribute("userName");

System.out.println("세션 존재: 삭제완료");

res.sendRedirect("login.do");

}

}


}



파일명: MemberLogoutController.jsp


[첨부(Attachments)]

MemberLogoutController.zip




17. Controller - MemberSessionAllKillController.java


세션 종료에 관한 영역이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


public class MemberSessionAllKillController implements Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

HttpSession session = req.getSession();

String userID = (String)session.getAttribute("userID");

// 세션 종료

if ( userID != null ) {

session.invalidate();

System.out.println("세션 종료");

res.sendRedirect("login.do");

}

}


}


파일명: MemberSessionAllKillController.jsp


[첨부(Attachments)]

MemberSessionAllKillController.zip




18. Controller - MemberErrorAlertController.java


Error Alert에 관한 내용을 기술하였다.

특이한 점은 오류가 하나라도 발생하면, 모든 세션을 파괴시키는 형태로 구현하였다.

물론 실제 비즈니스 로직 상에서 오류 하나가 발생했다고 전부 세션을 파괴하는 행위는 하진 않을 것이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


public class MemberErrorAlertController implements Controller {


@Override

public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

HttpUtil.forward(req, res, "/WEB-INF/views/member/error_alert.jsp");

HttpSession session = req.getSession();

session.invalidate();

}


}



파일명: MemberErrorAlertController.jsp


[첨부(Attachments)]

MemberErrorAlertController.zip




19. 결론(Conclusion)


세션 구현 방법론은 알겠는데, 코드로 구현하려고 하면 아이디어가 떠오르지 않을 때 참고해서 사용하면 좋을 것으로 보인다.




* 참고 자료(References)


1. JSP & Servlet 에서 세션 사용하는 방법, https://juns0201.tistory.com/115, Accessed by 2020-09-25, Last Modified 2012-08-07.

-> 추천(50점): 세션 사용 방법에 대해서 자세히 잘 나와있다.


2. <JSP> 세션(Session)을 이용한 로그인 페이지, https://great-yo.tistory.com/73, Accessed by 2020-09-25, Last Modified 2019-02-16,
-> 추천(35점): 세션을 순수한 JSP 기반에서 사용하는 방법에 대해서 잘 나와있다.

3.  7.JSP (session을 이용한 로그인), https://jinseok12.tistory.com/13, Accessed by 2020-09-25, Last Modified 2017-10-06.
-> 추천(20점): 세션 로그인 방법에 대해서 잘 나와있다.



반응형
728x90
300x250

[JSP] 14. Jsp/Servlet(MVC) - Session(세션) 프로젝트(로그인)


Session(세션) 방식으로 로그인 페이지를 처리하는 방법에 대해서 소개하려고 한다.

시중 책보다도 훨씬 쉽고 빠르게 적용할 수 있는 수준으로 작성하기 위해서 노력을 많이 하였으니 참고해주었으면 좋겠다.


적용된 패턴: MVC2 - FrontController, Command 패턴



* Session(세선)이란?



그림 가-1) 세션 객체와 세션 ID


HTTP 기반으로 동작하는 클라이언트가 서버에 정보를 요청할 때 생성되는 "상태정보"를 세션이라고 한다.

세션은 HttpSession이라는 인터페이스 객체로 표현되며, HttpSession 객체는 HttpServletRequest의 getSession()이나 getSession(true) 메소드를 이용하여 생성할 수 있다.


- 활용 예: 로그인, 로그아웃, 장바구니 기능 등 사용자 인증 처리에 사용함.



* HttpSession 메소드


접근자&반환형

메소드

기능

public Object

getAttribute(String name)

HttpSession 객체에 등록된 정보 중 getAttribute() 메소드의 인자값으로

지정된 데이터 값을 반환한다.

public Enumeration

getAttributeNames()

HttpSession 객체에 등록되어 있는 모든 정보의 이름만을 반환한다.

public String

getId()

HttpSession 객체의 지정된 세션 ID를 반환함.

public long

getCreationTime()

HttpSession 객체가 생성된 시간을 밀리초 단위로 반환함.

public long

getLastAccessedTime()

클라이언트 요청이 마지막으로 시도된 시간을 밀리초 단위로 반환됨.

public int

getMaxInactiveInterval()

클라이언트의 요청이 없을 때 서버가 현재의 세션을 언제까지 

유지할지를 초 단위로 반환한다.

기본 유효 시간은 30분으로 지정되어 있다.

public void

invalidate()

현재의 세션을 삭제한다.

public boolean

isNew()

서버 측에서 새로운 HttpSession 객체를 생성한 경우에는 true를 반환,

기존 세션이 유지되는 경우라면 false를 반환

public void

setAttribute(String name, Object value) 

HttpSession 객체에 name으로 지정된 이름으로 value값을 등록한다.

public void

removeAttributes(String name)

HttpSession 객체에서 name으로 지정된 객체를 삭제한다.

public void

setMaxInactiveInterval(int second)

HttpSession 객체의 유지 시간을 설정한다.

지정된 시간이 지나면 HttpSession 객체는 자동 삭제된다.




1. 결과


Session 기반의 로그인 페이지를 아래처럼 만들어보려고 한다.

그림 1. 프로젝트 결과 - 로그인 페이지


그림 2. 프로젝트 결과 - 오류 출력




그림 3. 로그인 상태



그림 4. 자바스크립트 - 알람 메시지 출력



2. 프로젝트 구성도


다음은 작업할 프로젝트에 관한 것이다. 양이 조금 많아서 2~3부 정도로 컨텐츠를 구성하였다.



그림 5. 프로젝트 구성도



3. Maven Project 생성하기


새 프로젝트를 만든다.



그림 6. New-> Maven 프로젝트


Maven 폴더의 "Maven Project"를 선택한다.

"Next"를 누른다.




그림 7. New-> Maven 프로젝트


org.apache.maven.archetypes  |   maven-archetype-webapp를 선택한다.

Next를 누른다.




그림 8. New-> Maven 프로젝트


Group ID와 Artifact ID를 입력한 후 Finish를 누른다.



4. Project Facts, Build Path 설정하기


의외로 프로젝트 속성을 못 찾는 경우가 있어서 그림으로 매우 친절하게 작성하였다.



그림 9. 프로젝트 - 마우스 오른쪽 버튼 모습


프로젝트를 마우스 오른쪽 버튼을 누른다.

Properties를 클릭한다.



그림 10. Build Path 설정 모습


JRE System Library [JavaSE-14] 버전으로 변경해준다.

(Edit 버튼을 눌러서 JRE 클릭 후 변경해주면 됨.)

Apply를 누른다.




그림 11. Project Factes


Project Facet의 Java 항목에 Version을 14로 변경해준다.

Apply를 누른다.



5. pom.xml 설정하기


http://mvnrepository.com에 접속한다.



그림 12. Java Servlet API - 4.0.1 - Mvnrepository.com


servlet을 찾아서 Maven 정보를 복사한다.



그림 13. pom.xml 설정하기


    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>



6. Servlet - FrontController 생성하기


서블릿 생성을 한다.



그림 14. 프로젝트의 "Java Resources" 메뉴


Java Resources를 오른쪽 버튼을 클릭한다.

New->Servlet을 클릭한다.



그림 15. Create Servlet


패키지명(Java Package)은 "com.member.web.controller"로 지정하였다.

클래스명(Class Name)은 "FrontController"를 입력하였다.


Next를 누른다.


(중략)




그림 16. web.xml (src/main/webapp/WEB-INF/web.xml)


web.xml 파일을 수정해준다.


<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 

id="WebApp_ID" version="3.0">


  <display-name>Archetype Created Web Application</display-name>

  

  <welcome-file-list>

    <welcome-file>index.html</welcome-file>

    <welcome-file>index.htm</welcome-file>

    <welcome-file>index.do</welcome-file>

    <welcome-file>default.html</welcome-file>

    <welcome-file>default.htm</welcome-file>

    <welcome-file>default.do</welcome-file>

  </welcome-file-list>

  

  <servlet>

  <servlet-name>FrontController</servlet-name>

  <servlet-class>com.member.web.controller.FrontController</servlet-class>

  <init-param>

<param-name>charset</param-name>

<param-value>UTF-8</param-value>

</init-param>

  </servlet>

  <servlet-mapping>

  <servlet-name>FrontController</servlet-name>

  <url-pattern>*.do</url-pattern>

  </servlet-mapping>

</web-app>


파일명: web.xml


[첨부(Attachments)]

web.zip



7. Controller - HttpUtil.java


forward 함수에 관한 것이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.RequestDispatcher;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class HttpUtil extends HttpServlet {


private static final long serialVersionUID = 1L;

private static String charset = null;


public static void forward(HttpServletRequest req, HttpServletResponse res,

String path) throws ServletException, IOException {

try {


RequestDispatcher dispatcher = req.getRequestDispatcher(path);

dispatcher.forward(req, res);


}catch(Exception e) {

e.printStackTrace();

}

}

}


파일명: HttpUtil.java


[첨부(Attachments)]

HttpUtil.zip



8. Controller(Interface) - Controller.java


Interface Controller를 하나 설계하였다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public interface Controller {


public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;


}


파일명: Controller.java


[첨부(Attachments)]

Controller.zip




9. Controller - FrontController.java


FrontController, Command 패턴에 관한 것이다.


package com.member.web.controller;


import java.io.IOException;


import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class FrontController extends HttpServlet {

private static final long serialVersionUID = 1L;

private String charset = null;

       

    public FrontController() {

        super();

    }


protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doAction(req, res);

}


protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

doGet(req, res);

}

protected void doAction(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

ServletConfig sc = this.getServletConfig();

charset = sc.getInitParameter("charset");


req.setAttribute("charset", charset);

req.setCharacterEncoding(charset);

res.setContentType("text/html; charset=" + charset);


String uri = req.getRequestURI();

String conPath = req.getContextPath();

String command = uri.substring(conPath.length());

Controller subController = null;


if(command.equals("/member/login.do")){

System.out.println("login");

System.out.println("----------------");


subController = new MemberLoginController();

    subController.execute(req, res);


}else if (command.equals("/member/process.do")) {

System.out.println("process");

System.out.println("----------------");


subController = new MemberProcessController();

    subController.execute(req, res);

   

}else if (command.equals("/member/logon.do")) {

System.out.println("logon");

System.out.println("----------------");

subController = new MemberLogonController();

    subController.execute(req, res);

}else if (command.equals("/member/logout.do")) {

System.out.println("logout");

System.out.println("----------------");

subController = new MemberLogoutController();

    subController.execute(req, res);

   

}else if (command.equals("/member/sessionAllKill.do")) {


System.out.println("sessionAllKill");

System.out.println("----------------");

subController = new MemberSessionAllKillController();

    subController.execute(req, res);

   

}else if (command.equals("/member/errorAlert.do")) {


System.out.println("errorAlert");

System.out.println("----------------");

subController = new MemberErrorAlertController();

    subController.execute(req, res);

}

// end of if

}


}


파일명: FrontController.java


[첨부(Attachments)]

FrontController.zip


[비고]

MemberLoginController()

MemberProcessController()

MemberLogonController()

MemberLogoutController()

MemberSessionAllKillController()

MemberErrorAlertController()


번호

구현부

인터페이스

비고

1

MemberLoginController

Controller


2

MemberProcessController

Controller


3

MemberLogonController

Controller

 

4

MemberLogoutController

Controller


5

MemberSessionAllKillController

Controller

 

6

MemberErrorAlertController

Controller

 


약 6개의 Controller들은 추후 생성할 것이다.

다소 이름이 길긴 하지만, 추후 고민할 문제해봐도 되는 문제이고 진행하도록 하겠다.


실제 설계하고 코딩했을 때는 바로 저 코드가 한번에 나오지 않았다는 것이다.

단계, 단계 구간별로 필요에 의해서 하나 하나 만들어진 것이다.



* 2부에서 만나요.


2부에서는 view생성, 나머지 콘트롤러 구현에 대해서 소개하도록 하겠다.


[JSP] 14. Jsp/Servlet(MVC) - Session(세션) 프로젝트(로그인) - (2), 2020-09-25

https://yyman.tistory.com/1417


반응형

+ Recent posts