Link Search Menu Expand Document

JSP Application

Table of contents

  1. Typical Components of Web Applications
    1. Web Applications Code for maintenance
      1. How Not to Create Duplicate Code
    2. Key Components of Web Applications
    3. Data Access Object
      1. How DAO approaches Connection
      2. JDBCUtil for Simple close() and rollback() Processing Codes
    4. Implementation of Service Classes
      1. Processing Service Classes and Transactions
      2. Handling Exceptions for Service Classes
    5. Implementation of Components Using Singletone Patterns
    6. Create a Connection Provider that Provides Connection
  2. Implement Guest Book
    1. 방명록을 구성하는 클래스의 구조
    2. 데이터베이스 테이블 생성
    3. 이클립스 프로젝트 생성과 필요 모듈 복사
    4. 커넥션 풀 설정을 위한 DBCPInit 클래스 구현과 web.xml설정

Typical Components of Web Applications

Web Applications Code for maintenance

웹 어플리케이션을 포함한 많은 소프트웨어는 최초 개발 이후에도 지속해서 기능 개선을 해야 함

동일하거나 지슷한 기능을 제공하는 코드가 여러 파일에 중복되는 상황이 발생하면 코드 수정이 어려워짐

따라서 중복 코드를 별도 클래스로 모으고 기능이 필요하면 그 클래스를 사용하도록 구현하는 것이 중요함

JSP는 클래스를 사용하는 방식으로 개발을 진행하면 동일한 코드가 여러 곳에 중복되는 것을 피할 수 있음

6개월 이상 사용되는 웹 어플리케이션은 지속적으로 기능이 추가되고 변경되기 때문에 코드가 중복되지 않도록 노력하는 것이 매우 중요함

How Not to Create Duplicate Code

  1. 화면을 보여주기 위한 코드와 상관없이 사용자의 요청을 처리하는 코드를 별도 클래스로 분리하는 것

  2. DB처리와 같이 여러 기능에서 사용될 수 있는 코드를 별도 클래스로 분리하기

Key Components of Web Applications

웹의 일반적인 구성요소

  • Service 클래스 : 사용자의 요청을 처리하는 기능을 제공, 기능을 제공하기 위한 로직을 구현하며 DAO 클래스를 이용해서 DB연동을 처리. 가입 신청 처리, 글 목록 제공등의 기능을 구현함

  • DAO 클래스 : DB관련 쿼리를 실행 Service클래스들은 데이터를 DB에서 읽어오거나 DB에 데이터를 저장할 때 DAO 클래스를 사용함

  • JSP(View) : Service클래스가 실행한 결과를 화면에 출력하거나 Service가 기능을 수행하는데 필요한 데이터를 전달함

  • MVC 프레임워크 : 사용자의 요청을 Service에 전달하고 Service의 실행 결과를 JSP와 같은 뷰에 전달

    ▸ 스프링 MVC와 같은 프레임워크가 MVC프레임워크에 해당함

Data Access Object

데이터 접근 객체(Data Access Object)의 구현

DAO클래스는 이름 그대로 데이터에 접근할 때 사용하는 객체를 위한 클래스를 의미

일반적으로 한개의 DB테이블 마다 한 개의 DAO 클래스를 작성함

각 DAO클래스는 Insert, select, update, delete쿼리를 실행핸주는 메서드를 제공함

  • insert() : insert 쿼리 실행

  • select() : select 쿼리 실행, 검색 조건에 다라서 두 개 이상의 select()메서드를 제공

  • update() : update 쿼리 실행

  • delete() : delete쿼리 실행

DAO 클래스는 테이블로부터 데이터를 읽어와 자바 객체로 변환하거나 자바 객체의 값을 테이블에 저장

따라서 DAO를 구현하면 테이블의 칼럼과 매핑되는 프로퍼티를 갖는 자바 클래스를 함께 작성함

CRUD

Create-Read-Update-Delete의 약자로 데이터 생성, 읽기, 수정, 삭제를 의미

즉, DB처리를 의미함

How DAO approaches Connection

DAO가 쿼리를 실행하려면 Statement / PreparedStatement가 필요한데, 이 두 객체는 Connection객체로부터 구할 수 있음. 따라서 DAO 클래스는 Connection 객체에 접근할 수 있어야 하는데 Connection 객체를 구하는 방법은 다음과 같은 세가지 방식이 있음

  1. DAO 클래스의 메서드에서 직접 Connection 생성

    메서드를 실행할 때 마다 매번 Connection을 생성, 각 메서드가 서로 다른 Connection 객체를 사용한다는 것은 두 메서드를 하나의 트랜잭션으로 처리할 수 없음

    (JDBC기반 트랜잭션은 한 개의 connection 객체에서만 유효하기 때문!)

  2. DAO 객체를 생성할 때 생성자로 Connection을 받기

    하나의 Connection 객체를 사용하기 때문에 JDBC 트랜잭션을 이용할 수 있음. 하지만 매번 새로운 DAO 객체를 생성한다는 단점이 있음

  3. DAO 클래스의 메서드 파라미터로 Conneciton을 전달받기

    두 번째 방식과 마찬가지로 한 개의 Connection 객체를 사용하므로 JDBC 트랜잭션을 사용할 수 있으며 DAO 객체를 매번 생성하지 않아도 된다는 장점이 있음

    하지만 메서도 호출 코드가 다소 길어진다는것과 한 트랜잭션으로 실행해야 할 메서드에 다른 Connectino을 전달할 수도 있음

JDBCUtil for Simple close() and rollback() Processing Codes

connection, statement, resultset과 같은 클래스는 사용이 끝나면 close() 메소드를 호출해서 자원을 반환해야 함

그래서 익셉션 발생 여부에 상관없이 close()할 수 있도록 finally 블록에서 close()메소드를 호출하도록 구현해야 함

finally{
    if(rs != null) try{ rs.close();} catch(SQLExcetion ex){}
    if(pstmt != null) try{ pstmt.close();} catch(SQLExcetion ex){}
    if(conn != null) try{ conn.close();} catch(SQLExcetion ex){}
}

위와 같은 코드를 매번 작성하기 귀찮으므로 JdbcUtril.java파일을 작성해서 중복되는 코드를 피함

jdbcUtil.java

package jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcUtil {

	public static void close(ResultSet rs) {
		if(rs!=null)
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
	}
	
	public static void close(Statement stmt) {
		if(stmt!=null)
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
	}
	
	public static void close(Connection conn) {
		if(conn!=null)
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
	}
	
	public static void rollback(Connection conn) {
		if(conn!=null)
			try {
				conn.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
			}
	}
}

위코드를 사용하면 아래와 같이 간편하게 사용가능함

finally{
    JdbcUtil.close(rs);
    JdbcUtil.close(pstmt);
    JdbcUtil.close(conn);
}

Implementation of Service Classes

DAO가 데이터 접근 기능을 제공한다면 서비스 클래스는 사용자의 요청을 처리하기 위한 기능을 제공함

서비스 클래스는 주로 DAO를 통해서 데이터에 접근하고 기능을 수행하는 데 필요한 로직을 구현함

주요 기능 별로 서비스 클래스를 구현하고, 서비스 클래스에서 제공하기에 알맞은 단위는 게시글 목록 제공, 게시글쓰기, 회원등록, 회원 정보 수정정도가 됨

Processing Service Classes and Transactions

서비스 클래스가 제공하는 기능을 트랜잭션 범위에서 처리해야 한다면 메서드에서 로직을 실행하기 전에 connection.setAutoCommit(false)를 싱행해서 트랜잭션을 시작하고, 모든 코드가 종료되면 Connection.commit()을실행해서 트랜잭션을 커밋해야 함

로직을 수행하는 도중 익셉션이 발생하면 잘못된 데이터가 DB에 반영되지 않도록 catch블로에서 트랜잭션을 롤백해야 함

Handling Exceptions for Service Classes

서비스 클래스의 메서드는 기능을 올바르게 실행했는지 여부를 확인할 수 있도록 기능 실행 과정에 문제가 있으면 관련 익셉션을 발생시켜야 함

논리적으로 잘못된 경우 (존재하지도 않은 게시글을 삭제하려고 할 떄)에도 그에 알맞은 익셉션을 발생시켜서 게시글 삭제 기능을 요청한 코드에 존재하지 않음을 알려야 함

Implementation of Components Using Singletone Patterns

서비스 클래스를 개발했다면 다음과 같이 서비스 클래스를 이용해서 객체를 생성한 뒤에 실행할 메서드를 호출하도록 코드를 작성하게 됨

 ReadArticleService service = new ReadArticleService();
Article article = service.read(messageId);

다수의 서비스 클래스는 객체를 여러 번 만들더라도 실제로 수행하는 기능은 동일한 경우가 많음 = 기능상 차이가 없다면 매번 새로운 서비스 객체를 생성하지 ㅇ낳고 한 개의 객체를 재사용하도록 구현해도 됨, 이때 적용 가능한게 싱글톤

public class ReadArticleService{
    
    //유일한 객체를 정적 필드에 저장
    private static ReadArticleService instance = new ReadArticleService();
    
    //유일한 객체에 접근할 수 있는 정적 메서드 정의
    public static ReadArticleService getInstance(){
        reutrn instance;
    }
    
    //생성자를 private으로 설정해서 외부에서 접근하지 못함
    private ReadArticleService(){}
    
}

Create a Connection Provider that Provides Connection

JDBC API를 이용해서 DB 프로그래밍을 하려면 다음과 같이 Connection 객체를 먼저 생성해야 함

Connection conn = null;
try{
    conn = DriverManager.getConnection(jdbcUrl, userId, userPassword);
}

DB연동이 필요한 모든 코드에서 DriverManager.getConnection() 메서드를 사용해서 connection 객체를 구하면 그 안의 매개변수 코드가 중복이 됨

또한 url을 변경해야 하거나 커넥션 풀을 DBCP에서 다른 라이브러리로 변경해야 한다면 모든 코드에서 DriverManager.getConnection()부분을 수정해야 하는 부담이 발생함

따라서 JDBC URL과 같은 DB연결 설정 정보를 더욱 쉽게 변경하려면 DriverManager.getConnection()을 직접 호출해서 Connection 객체를 구하기보다는 Connection을 제공해주는 기능을 별도의 클래스로 분리해주어야 함

ConnectionProvider.java

public class ConnectionProvider {
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
        "jdbc:apache:commons:dbcp:guestbook"
          )
    }
}

위와같이 작성해놓으면 Connection 이 필요한 코드는 ConnectionProvider.getConnection()메서드를 사용해서 Connection을 구할 수 있음

이제, JDBC URL을 변경하거나 커넥션 풀 방식을 변경해야 할 떄 , Connection을 사용하는 모든 코드를 변경할 필요 없이 ConnectionProvide.getConnection()메서드만 변경해주면 됨

Connection conn = null;
try{
     conn = ConnectionProvider.getConnection();
}

Implement Guest Book

방명록은 많이 구현해봐서 책의 내용만 정리해놓고 나머지 내용은 뒤 examples에 넣어놨음!

MySQL로 방명록 예제를 작성함

방명록을 구성하는 클래스의 구조

  1. 서비스 관련 클래스가 제공하는 기능 : messageDAO와 ConnectionProvider을 이용해서 필요한 기능을 구현함

    • GetMessageListService : 요청한 페이지번호에 포함된 메시지 목록을 구함

    • WriteMessageService : 메시지를 작성하는 기능을 제공함

    • DeleteMessageService : 작성한 메시지를 삭제하는 기능을 제공함

  2. DAO관련 클래스가 제공하는 기능

    • MessageDAO : Guestbook_Message 테이블에 대한 쿼리를 실행
  3. JDBC Connection 관련 클래스

    • ConnectionProvider : Connection을 제공함

    • DBCPInit : DBCP 초기화 서블릿

    • JdbcUtil : Connection을 위한 보조 기능을 제공

데이터베이스 테이블 생성

  1. 데이터베이스 생성

     create database guestbook default character set utf8;
    
  2. 사용자 생성 및 권한부여

     create user 'jspexam'@'localhost' identified by 'jsppw';
     grant all privileges on guestbook. * to 'jspexam' @ 'localhost';
    
     create user 'jspexam'@'%' identified by 'jsppw';
     grant all privileges on guestbook. * to 'jspexam' @ '%';
    
  3. guestbook 데이터베이스에 테이블을 생성할 차례

    message_id는 알아서 증가하게 생성함

     create table guestbook_message(
         message_id int not null auto_increment primary key,
         guest_book varchar(50) not null,
         password varchar(10) not null,
         message text not null
     ) engine = InnoDB default character set = utf8
    

이클립스 프로젝트 생성과 필요 모듈 복사

  1. dynamic web project 만들고 tomcat runtime 설정함

  2. jar파일 lib폴더에 복사

    dbcp2, logging, pool2, mysql-connector, jstl(표준태그)

커넥션 풀 설정을 위한 DBCPInit 클래스 구현과 web.xml설정

DBCPInit클래스는 jdbc드라이버를 로딩하고 커넥션 풀을 초기화함

DBCPInit.servlet

package com.lec.web.jdbc;

 import java.sql.DriverManager;

 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;

 import org.apache.commons.dbcp2.ConnectionFactory;
 import org.apache.commons.dbcp2.DriverConnectionFactory;
 import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
 import org.apache.commons.dbcp2.PoolableConnection;
 import org.apache.commons.dbcp2.PoolableConnectionFactory;
 import org.apache.commons.dbcp2.PoolingDriver;
 import org.apache.commons.pool2.impl.GenericObjectPool;
 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

 public class DBCPInit extends HttpServlet {

     @Override
     public void init() throws ServletException {
         loadJDBCDriver();
         initConnectionPool();
     }

     private void loadJDBCDriver() {
         String driverClass = getInitParameter("jdbcdriver");	

         try {
             //커넥션풀이 내부에서 사용할 JDBC드라이버 로딩
             Class.forName(driverClass);
         } catch (ClassNotFoundException e) {
             throw new RuntimeException("JDBC 드라이버 로딩 실패!!");		
         }
     }

     private void initConnectionPool() {

         String url =  getInitParameter("url");
         String usr =  getInitParameter("user");
         String pwd =  getInitParameter("pass");

         // A. Connection Pool 생성
         // 1. 커넥션풀이 새로운 커넥션을 생성할 떄 사용하는 커넥션팩토리를 생성
         ConnectionFactory connFactory = new DriverManagerConnectionFactory(url, usr, pwd);

         // 2. PoolableConnection을 생성하는 객체생성. DBCP는 커넥션풀에 커넥션을 보관할 떄 PoolableConnection을 사용
         // 이 클래스는 내부적으로 커넥션을 보관하고 있으며 커넥션풀을 관리하는데 필요한 기능을 제공
         // 예를 들어서 connection을 close하면 커넥션을 완전히 종료하지 않고 커넥션풀에 connection을 반환
         PoolableConnectionFactory poolFactory = new PoolableConnectionFactory(connFactory, null);

         // 3. 커넥션이 유효한지여부를 검사하기 위한 SQL을 지정
         poolFactory.setValidationQuery("select 1"); // 커넥션유효성을 검사 : mariadb or mysql;
         // poolFactory.setValidationQuery("select * from dual"); // oracle

         // B. 커넥션플의 설정정보
         // 1. 설정정보를 관리할 객체를 생성
         GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();		
         // 2. 커넥션의 검사주기
         poolConfig.setTimeBetweenEvictionRunsMillis(1000l * 60l * 5l); // 사용가능한 검사주기
         // 3. 풀에 보관중인 커넥션유효여부를 검사할지 안할지 여부설정
         poolConfig.setTestWhileIdle(true);
         // 4. 커넥션의 최소 갯수
         poolConfig.setMinIdle(4);
         // 5. 커넥션의 최대 갯수
         poolConfig.setMaxIdle(50);

         // C. 커넥션풀을 생성
         // 1. PoolableConnection을 생성할 떄 사용할 커넥션팩토리와 설정정보를 이용해서 커넥션풀을 생성
         GenericObjectPool<PoolableConnection> connPool = new GenericObjectPool<>(poolFactory, poolConfig);
         // 2. 생성한 커넥션풀을 연결
         poolFactory.setPool(connPool);

         // D. 커넥션풀을 제공할 JDBC드라이버 등록
         try {
             Class.forName("org.apache.commons.dbcp2.PoolingDriver");
             PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
             String poolName = getInitParameter("poolName");
             driver.registerPool(poolName, connPool);
         } catch (Exception e) {
             throw new RuntimeException();
         }	
     }
 }

톰켓을 실행할  dbcpInit을 실행하도록 web.xml파일에 dbcpinit을 등록함

```jsp
 <servlet>
     <servlet-name>DBCPInit</servlet-name>
     <servlet-class>jdbc.DBCPInit</servlet-class>
     <load-on-startup>1</load-on-startup>
 </servlet>

### 메세지 클래스 작성

guestbook_message 테이블에서 읽어온 값을 저장하거나 또는 사용자가 입력한 값을 DAO에 전달할 때 사용되는 클래스인 Message 생성

hasPassword(), matchPassword()는 DeleteMessageService클래스에서 삭제 기능을 구현할 떄 사용됨

### MessageDAO class

guestbook_message 테이블에 대한 CRUD 메서드를 제공함


Table of contents


이 웹사이트는 jekyll로 제작되었습니다. Patrick Marsceill, Distributed by an MIT license.