본문 바로가기
교재 실습/자바 웹 프로그래밍 Next Step

7.1.1 실습 코드 리뷰 및 JDBC 복습

by Jint 2025. 6. 30.

step2-user-with-mvc-framework 브랜치 소스코드를 확인해 보면 서버가 시작하는 시점에 회원 정보를 저장할 테이블을 초기화하고 있다. 초기화하는 테이블 생성 스크립트는 src/main/resource 디렉토리 아래 jwp.sql 파일에 다음과 같이 구현되어 있다.

 

- src/main/resource/jwp.sql

DROP TABLE IF EXISTS USERS;

CREATE TABLE USERS ( 
    userId    varchar(12)  NOT NULL
  , password  varchar(12)  NOT NULL
  , name      varchar(20)  NOT NULL
  , email     varchar(50)
  , PRIMARY KEY (userId)
);

INSERT INTO USERS VALUES('admin', 'password', '자바지기', 'admin@slipp.net');

 

이 jwp.sql 파일은 톰캣 서버가 시작할 때 초기화하도록 ContextLoaderListener 클래스에 다음과 같이 구현되어 있다.

 

- slipp-jwp-basic/src/main/java/next/support/context/ContextLoaderListener.java

package next.support.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;

import core.jdbc.ConnectionManager;

@WebListener
public class ContextLoaderListener implements ServletContextListener {

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

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(new ClassPathResource("jwp.sql"));
        DatabasePopulatorUtils.execute(populator, ConnectionManager.getDataSource());
        logger.info("Completed Load ServletContext!");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
    
}

 

sql 파일을 읽어 데이터베이스에 대한 초기화 작업을 하기 위해 스프링 프레임워크에서 제공하는 기능을 활용했다(실무 프로젝트는 이런 방식으로 데이터베이스 스키마를 초기화하지 않는다. 실무 프로젝트는 데이터베이스 관리자(DBA)가 테이블 스키마를 관리하거나, Flyway(https://flywaydb.org/) 와 같은 DB Migration 도구를 활용해 테이블 스키마를 관리한다). 이 코드에서 중요하게 볼 부분은 톰캣 서버가 시작할 때 contextInitialized() 메서드를 호출함으로써 초기화 작업을 할 수 있다는 것이다. 이 작업이 가능한 이유는 ContextLoaderListener 가 ServletContextListener 인터페이스를 구현하고 있으며, @WebListener 어노테이션 설정이 있기 때문이다. 서블릿 컨테이너는 ServletContextListener 인터페이스 구현체 중 @WebListener 어노테이션이 설정되어 있으면 서블릿 컨테이너를 시작하는 과정에서 contextInitialized() 메서드를 호출해 초기화 작업을 진행한다. ServletContextListener 에 대한 초기화는 서블릿 초기화보다 먼저 진행된다. 서블릿의 초기화가 해당 서블릿과 관련한 초기화를 담당한다면 ServletContextListener 초기화는 웹 애플리케이션 전체에 영향을 미치는 초기화가 필요한 경우 활용할 수 있다.

실습 소스코드를 보면 next.dao 패키지에 UserDao 클래스를 통해 데이터베이스 접근 로직을 구현하고 있다. 자바 진영은 데이터베이스에 대한 접근 로직 처리를 담당하는 객체를 별도로 분리해 구현하는 것을 추천한다. 이 객체를 DAO(Data Access Object)라고 부른다. 이는 현재 일반적인 패턴으로 널리 활용되고 있어 DAO 패턴을 적용해 구현해본다.

UserDao 는 지금까지 데이터를 저장하기 위해 사용한 DataBase 클래스의 일부 기능만 구현하고 있다. 나머지 메서드는 이후 실습을 통해 구현한다. UserDao 소스코드는 다음과 같다.

 

- slipp-jwp-basic/src/main/java/next/dao/UserDao.java

package next.dao;

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 core.jdbc.ConnectionManager;
import next.model.User;

public class UserDao {

    public void insert(User user) throws SQLException {
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = ConnectionManager.getConnection();
            String sql = "INSERT INTO USERS VALUES (?, ?, ?, ?)";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, user.getUserId());
            pstmt.setString(2, user.getPassword());
            pstmt.setString(3, user.getName());
            pstmt.setString(4, user.getEmail());
            pstmt.executeUpdate();
        } finally {
            if (pstmt != null) {
                pstmt.close();
            }
            if (con != null) {
                con.close();
            }
        }
    }

    public void update(User user) throws SQLException {
        // TODO 구현 필요함.
    }

    public List<User> findAll() throws SQLException {
        // TODO 구현 필요함.
        return new ArrayList<User>();
    }

    public User findByUserId(String userId) throws SQLException {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = ConnectionManager.getConnection();
            String sql = "SELECT userId, password, name, email FROM USERS WHERE userid=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, userId);

            rs = pstmt.executeQuery();

            User user = null;
            if (rs.next()) {
                user = new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email"));
            }
            return user;
        } finally {
            if (rs != null) {
                rs.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (con != null) {
                con.close();
            }
        }
    }

}

 

UserDao 가 제공하는 기능은 사용자 데이터를 추가하고, 사용자 아이디에 해당하는 사용자 데이터를 조회하는 기능밖에 없다. 구현한 기능은 2가지밖에 없는데 구현할 소스코드는 정말 많다. 회원 데이터를 추가하고, 조회하는 기능을 구현하기 위해 많은 중복 코드가 발생한다.

데이터베이스 관련 설정, 테이블 초기화, DAO 구현 등 기존에 DataBase 클래스를 사용하던 코드를 UserDao 를 사용하도록 변경한다. 먼저 회원 가입을 담당하고 있는 CreateUserController 가 UserDao 를 사용하도록 다음과 같이 변경할 수 있다.

 

- slipp-jwp-basic/src/main/java/next/controller/CreateUserController.java

package next.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import core.db.DataBase;
import core.mvc.Controller;
import next.model.User;

public class CreateUserController implements Controller {

    private static final Logger log = LoggerFactory.getLogger(CreateUserController.class);

    @Override
    public String execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        User user = new User(req.getParameter("userId"), req.getParameter("password"), req.getParameter("name"), req.getParameter("email"));
        log.debug("User : {}", user);
        DataBase.addUser(user);
        return "redirect:/";
    }

}

 

회원목록, 로그인 기능도 위와 같은 방식으로 UserDao 를 사용하도록 리팩토링할 수 있다, 단 DataBase 대신 UserDao 를 사용할 떄 불편한 점 중의 하나는 UserDao 의 모든 메서드가 SqlException 을 throw 하고 있기 때문에 컴파일 에러를 해결하기 위해 try/catch 구문으로 감싸줘야 한다는 것이다. SqlException 에 대한 처리 또한 추후 리팩토링을 통해 제거해본다.

먼저 복습 차원에서 회원 목록과 개인정보 수정 기능을 실습한다. 이미 JDBC API 사용에 익숙하거나, Spring JDBC, iBatis(또는 MyBatis), ORM 프레임워크를 사용해 데이터베이스 접근 로직을 구현하고 있더라도 실습을 진행해본다. JDBC가 처음 등장했을 때 얼마나 힘든 과정으로 개발했는지 경험해보면 지금 사용하고 있는 라이브러리에 고마움을 느낄 것이다.



참고도서 : https://roadbook.co.kr/169

 

[신간안내] 자바 웹 프로그래밍 Next Step

● 저자: 박재성 ● 페이지: 480 ● 판형: 사륙배변형(172*225) ● 도수: 1도 ● 정가: 30,000원 ● 발행일: 2016년 9월 19일 ● ISBN: 978-89-97924-24-0 93000 [강컴] [교보] [반디] [알라딘] [예스24] [인터파크] [샘

roadbook.co.kr

댓글