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


반응형

+ Recent posts