본문 바로가기
05_Server (04. JSP 프로그래밍 구현)

[1-2] 회원서비스 개발 세팅 (이어서)

by moca7 2024. 9. 7.

 

 

ㅁ 회원서비스

- 회원서비스 개발 세팅         <- 여기 진행 중.

 

- 회원서비스_로그인
- 회원서비스_로그아웃
- 회원서비스_회원가입페이지로이동

 

- 회원서비스_회원가입요청
- 회원서비스_마이페이지요청
- 회원서비스_정보변경요청
- 회원서비스_비번변경요청
- 회원서비스_회원탈퇴요청

 

 

 

ㅁ 패키지, 클래스 생성

- 소스폴더인 src/main/java에 패키지를 만든다.

- 이 때 세번째 까지는 com.br.web으로 시작한다. 

패키지 첫번째 레벨, 두번째 레벨은 주로 도메인의 역순이다라는 가정 하에 작성했고,

세번째 레벨에 주로 어플리케이션명을 따서 쓴다.

- 지금 web이라 한 이유는 나중에 스프링 프로젝트를 개발하게 되면

context path로 한 게 세번째 레벨로 잡히게 되어 있다.

- 네번째 레벨부터 쪼개진다.

 

 

 

- 위의 4개는 패키지만, 아래 1개는 패키지와 클래스까지 생성한다.

- 패키지 생성 창에 name에 webApp이라고 현재 프로젝트명이 이미 입력되어 있는데 완전히 지우고 패키지명을 작성한다.

- 멤버 패키지 하위로 controller, model.vo, model.service, model.dao 패키지가 필요하다.

view 패키지는 필요 없다. view가 곧 화면이다.

이 4개는 멤버 관련한 패키지이다.

 

- 그리고 jdbc 연동할 거라서 JDBCTemplate 클래스를 보관할

com.br.web.common.template 패키지를 생성한다.

 

- JDBCTemplate 클래스에 매번 반복적으로 작성될 코드를 따로 한번만 정의했다.

JDBCTemplate 클래스에서 Connection 객체 생성, Connection, Statement, Resultset 반납처리, 트랜잭션 처리(commit, rollback) 등을 한다.

 

 

 

 

ㅁ JDBCTemplate 클래스 생성

- com.br.web.common.template 패키지에 JDBCTemplate 클래스를 생성한다.

- 안은 비어 있는데 옛날에 한 '03_jdbc-workspace' > 04_MVC_JDBC_Service.JDBCTemplate > src > com > br > common 에서 JDBCTemplate.java를 이클립스로 가져와서 연다.

 

 

- class {} 중괄호 안의 내용들만 복사 붙여넣기 한다. 

- import들도 다 한다.

import할 때는 Statement는 java.sql.Statement로 한다. 

 

 

 

 

 

- 아래는 JDBCTemplate.java다

 

 
package com.br.web.common.template;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCTemplate {
   
    // Connection 생성해서 반환
    public static Connection getConnection() {
       
        Properties prop = new Properties();
 
       
        // 읽어들이고자 하는 파일 : driver.properties (src/main/java 안 x, src/main/webapp/WEB-INF/classes 안 o)
        //                          해당 파일의 물리적인 경로
        String filePath = JDBCTemplate.class.getResource("/db/config/driver.properties").getPath();
        // "C:\workspaces\05_jspServlet-workspace\webApp\src\main\webapp\WEB-INF\classes\db\config\driver.properties
 
       
        try {
            prop.load(new FileInputStream(filePath));
        }catch(IOException e) {
            e.printStackTrace();
        }
       
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
       
        Connection conn = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
       
        return conn;
    }
   
    // ResultSet 객체를 반납(close) 처리
    public static void close(ResultSet rset) {
        try {
            if(rset != null && !rset.isClosed()) {
                rset.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
   
    // Statement 관련 객체를 반납 처리
    public static void close(Statement stmt) {
        try {
            if(stmt != null && !stmt.isClosed()) {
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
   
    // Connection 객체를 반납 처리
    public static void close(Connection conn) {
        try {
            if(conn != null && !conn.isClosed()) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
   
    // commit 처리
    public static void commit(Connection conn) {
        try {
            if(conn != null && !conn.isClosed()) {
                conn.commit();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
   
    // rollback 처리
    public static void rollback(Connection conn) {
        try {
            if(conn != null && !conn.isClosed()) {
                conn.rollback();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
 

 

 

- JDBCTemplate.java에는 다 static 메소드로 뒀었다.

service에서 커넥션 객체를 생성해서 넘겼는데

그때마다 매번 어떤 url 드라이버를 쓸거고 계정은 뭐고 비밀번호는 뭔지 매번 작성하기 번거로워서 메소드로 정의해놨었다. 

- getConnection 메소드 호출만으로 db와 접속된 커넥션 객체를 돌려받을 수 있었다.

 

 

- 접속할 db에 대한 정보(접속할 db 환경정보)가 자바 코드에 쓰여져 있어서는 안 돼서

그때 당시에는 driver.properties 파일에 작성을 했었다.

 

 

- 그런데 현재 아직 driver.properties 파일도 안만들었고, 경로도 다르게 제시할 예정이다.

 

 

 

 

ㅁ DB 관련 외부 문서들 작성

- 옛날엔 resources라는 일반 폴더를 두고 파일을 만들었지만, 이제 실제 서버 배포까지 생각해야 한다.

- 소스폴더인 src/main/java에 2개의 패키지를 추가로 만든다.

 

 

- db.config 패키지에 db 환경설정 문서를 보관할 예정이다.

- db.mappers 패키지에 쿼리문들을 작성한 xml 파일을 보관할 예정이다.

 

 

 

 

- db.config에는 driver.properties 문서를 둔다. 

- db.mappers에는 회원 관련한 쿼리를 작성할 member-mapper.xml 파일에 쿼리문을 작성할 예정이다.

공지사항 관련한 쿼리를 작성할 notice-mapper.xml 파일,

게시글 관련한 쿼리를 작성할 board-mapper.xml 파일을 둘 예정이다.

 

 

 

 

- src/main/java 이 소스폴더에서 작업한 내역들은 항상 webapp > WEB-INF > classes 폴더 안에 동기화가 진행될 예정이다.

자바 파일들은 컴파일해서 보관이 되고, src/main/java에서 작업한 일반 파일들도 동기화가 진행된다. 

- 서버에 배포할 때 같이 내보내야 한다. 그래서 db 관련 문서들도 소스폴더에 두는 것이다.

 

 

 

 

- db.config 패키지 우클릭 - new - file 눌러서 파일 이름으로 driver.properties를 입력하고 finish로 만든다.

 

 

 

 

- properties 파일의 특징으로는 key=value 형식으로 단순히 텍스트를 작성할 수 있었다.

그래서 db 환경설정 관련 문서를 따로  파일로 두었다.

 

 

 

- driver 클래스의 경로, 접속할 db의 url 주소, 접속할 계정명, 비밀번호를 작성했었다.

- 드라이버 경로 oracle.jdbc.driver.OracleDriver는 ojdbc6.jar 파일이 제공하는 클래스다.

그래서 저 클래스를 정상적으로 찾기 위해서는 ojdbc6 라이브러리가 연동되어 있어야 한다. 

- localhost가 db가 구축되어 있는 서버의 ip 주소다. 

현재 각자 로컬 db를 쓰고 있다. 그래서 localhost를 제시한다.

- 그리고 아까 SERVER라는 계정을 만들고 거기에 DB를 구축했었다. 

- (참고) properties 파일은 git에 안올린다. 이런 환경설정 관련 파일은 git에 올리면 안 된다. 

다른 팀원은 db를 구축할 때 SERVER라는 계정이 아닌 다른 계정으로 구축했었을 수 있다. 

환경설정은 각자 PC 환경에 맞춰서 자기가 알아서 설정하는 거다.

이런 파일은 서로 공유하면 안되어서 gitignore로 properties 파일을 제외했다.

 

 

 

 

 

ㅁ 다시 JDBCTemplate 클래스로 돌아온다.

 

 

- JDBCTemplate 클래스의 getConnection() 메소드에서 driver.properties 파일을 읽어들어야 한다.

그때 당시 저렇게 경로를 기술했다.

그래서 저 파일에 기술되어 있는 key-value 세트들을 Properties 객체에 읽어들였다.

그리고 그 Properties에 key값을 제시해서 value값을 뽑았다.

그걸 이제 Connection 객체에 연결할 때 사용되게끔 작업을 했었다.

- 그런데 이제 읽어들일 파일에 대한 경로를 저렇게 써선 안 된다.

 

 

 

 

- 읽어들이고자 하는 파일은 driver.properties 파일이 맞다.

우리가 실제 작업하고 있는건 src/main/java/db.config 패키지 안에 있는 driver.properties에서 작업을 하고 있다.

- 그런데 실제 읽어들여야 하는 파일은 방금 만든 src/main/java/db.config 패키지의 driver.properties 파일이 아니다.

실제 얘가 classes 폴더에도 똑같이 만들어지는데 그 파일을 읽어들여야 한다.

- 이클립스의 패키지 익스플로러에는 webApp/src/main/webapp/WEB-INF/classes 폴더가 안보인다. 탐색기에선 보인다.

 

 

 

 

- 탐색기로 webApp/src/main/webapp/WEB-INF/classes를 가보면 지금 만든 패키지대로 똑같이 폴더가 만들어져 있다.

- 현재 작업하고 있는 건 src/main/java인데 똑같은 구조로 classes라는 폴더에 동기화가 진행되고 있다. 

 

 

- db 안에 config 들어가보면 아까 만들었던 driver.properties가 동기화가 진행되어 여기에도 똑같이 존재한다. 

- 우리가 실제 읽어들여야 하는 파일은 src/main/java/db.config의 driver.properties 파일이 아니라,

webApp/src/main/webapp/WEB-INF/classes/db.config의 driver.properties 파일이다.

서버에 배포했을 때는 이 파일을 읽어들여야 한다.

- 우리가 src/main/java에서 java 파일을 작업해도 실제 실행되는건 webapp 폴더의 class 파일들이다.

 

 

 

 

 

 

- WEB-INF/classes에 있는 파일의 물리적인 경로를 알아내서    prop.load(new FileInputStream(  여기  ));

여기에 반영을 시켜야 한다.

- 이 파일의 물리적인 경로를 알아내서 String 타입의 filePath라는 변수에 기록해둔다. 

 

- 지금 JDBCTemplate.java에서 자바 코드를 작성하고 있다.

얘도 컴파일이 진행되서 .class 파일로 변환되어 classes 폴더에 담긴다.  사실상 컴파일된 파일이 실행되는 것이다.

- "JDBCTemplate.class" 하면 webApp/src/main/webapp/WEB-INF/classes/com/br/web/common/template 폴더의 "JDBCTemplate.class" 파일을 가리킨다.

 

- 여기에 "getResource()"로 경로를 작성한다. driver.properties 파일을 찾기 위한 일부 경로를 작성한다.

여기서의 슬래시는 현재 이 (컴파일된) 클래스 파일이 위치해 있는 폴더의 루트 폴더를 가리킨다. 

루트폴더가 바로 classes 폴더다.

 

 

 

 

- getResource() 메소드의 괄호 안의 경로 중 슬래시가 "classes" 폴더를 가리킨다.

- classes 폴더 안에 db 폴더 있었고, 그 안에 또 config 폴더 있었고, 그 안에 driver.properties가 있었다.

- 내가 찾고자 하는 파일의 일부 경로를 제시하면 그 파일을 찾을 수 있다.

 

- getResource() 메소드에 의해 driver.properties 파일이 찾아지고, 이 파일의 물리적인 경로를 알아내고자 한다면 getPath() 메소드까지 호출해야 한다.

 

- 현재 변수 filePath에

"C:\workspaces\05_jspServlet-workspace\webApp\src\main\webapp\WEB-INF\classes\db\config\driver.properties"

이런 문자열이 담겨있다.

- 지금이야 윈도우 환경이어서 c드라이브부터의 경로지, 실서버에서는 리눅스 환경으로 구성이 되어있을 것이다.

그때 이 코드가 실행되었을 때는 또 다른 경로다. 그래서 정적으로 작성해 둘 수가 없다. 

 

 

- getResource() 메서드를 사용하면, 클래스 경로를 기준으로 파일의 경로를 상대적으로 찾기 때문에 운영체제에 무관하게 작동합니다. 즉, 운영체제에 상관없이 WEB-INF/classes 아래의 경로만 정확하게 지정하면 됩니다.
- getPath() 메서드를 사용하여 경로를 가져오면 운영체제마다 다른 물리적 경로가 반환됩니다. 예를 들어, Windows에서는 C:\, Linux에서는 /home/ 등의 경로가 반환됩니다.

 

 

 

- 정리하자면 읽어들이고자 하는 파일의 물리적인 경로를 알아내는 방법은,

컴파일된 클래스 파일의 루트 폴더가 곧 classes 폴더고,

그 안에 위치해 있는 "/db/config/driver.properties" 이 파일을 찾겠다 일부 경로를 제시하고,

getPath() 메소드로 해당 파일의 물리적인 경로를 알아낼 수 있다.

- getResource() 메소드에 인자로 제시하는 일부경로에 문제가 있다면 getResource() 메소드는 null을 반환한다. 

그때는 null.getPath()가 진행돼서 null pointer exception이 발생할 수 있다.

 

 

 

- 그리고 prop.load(new FileInputStream(filePath));를 하면 된다.

이러면 prop에 driver.properties에 작성한 key-value 세트들이 담긴다.

 

- 이전에 jdbc 수업 때는 서버의 개념이 없었기에 일반 폴더에다가 driver.properties를 두고 일반 폴더의 경로를 가리켜서 곧바로 읽어들였었다. 

- 실제로는 서버에 배포하기 때문에 배포된 서버로부터 경로를 알아와야 한다.

그래서 동적으로 알아오는 구문이 작성되어야 한다. 

 

 

 

 

 

ㅁ src/main/java/db.mappers 패키지에 회원 관련한 쿼리를 작성한 xml 파일을 만든다.

- src/main/java/db.mappers 패키지 우클릭 - new - xml 파일에서 member-mapper.xml이라는 이름으로 xml 파일을 만든다.

- 여기에 이제 sql문을 작성한다.

로그인 요청시 실행할 sql문이나 회원 가입 요청시 실행할 sql문, 정보 변경시 등등 crud 구문을 xml 파일에 작성한다.

- 이 sql문들을 dao 단에서 읽어들여서 쿼리 가져와서 실행시킨다.

 

 

 

- 이 xml 파일도 이 문서의 유형을 properties 문서 유형으로 해야하기 때문에

!DOCTYPE properties 하고 그 뒤에, SYSTEM하고 어떤 url 주소를 썼었다.

- url의 dtd는 뭐냐면 현재 이 xml 문서 내의 프로퍼티스 관련된 태그들만 작성했는지 내부적으로 유효성 체크를 해준다고 했었다.

혹시라도 유효하지 않은 태그가 작성되었다면 빨간줄로 오류를 알려준다.

 

 

 

- 그리고 전체 감싸는 태그로 <properties> 태그여야 한다고 했었다.

제일 루트 엘리먼트가 <properties> </properties> 였었다.

 

 

 

- 그리고 이 <properties> </properties>태그 안에 여러 entry들을 key=value 형식으로 작성해서 쿼리를 작성했었다.

 

 

 

 

 

 

ㅁ src/main/java의 com.br.web.member.model.vo 패키지에 Member라는 vo 클래스를 만든다.

 

 

 

- 아래는 Member.java다.

 
package com.br.web.member.model.vo;

import java.sql.Date;

public class Member {
   
    private int userNo;
    private String userId;
    private String userPwd;
    private String userName;
    private String phone;
    private String email;
    private String address;
    private String interest;
    private Date enrollDt;
    private Date modifyDt;
    private String status;
   
    public Member() {}

    public Member(int userNo, String userId, String userPwd, String userName, String phone, String email,
            String address, String interest, Date enrollDt, Date modifyDt, String status) {
        super();
        this.userNo = userNo;
        this.userId = userId;
        this.userPwd = userPwd;
        this.userName = userName;
        this.phone = phone;
        this.email = email;
        this.address = address;
        this.interest = interest;
        this.enrollDt = enrollDt;
        this.modifyDt = modifyDt;
        this.status = status;
    }

    public Member(String userId, String userPwd, String userName, String phone, String email, String address,
            String interest) {
        super();
        this.userId = userId;
        this.userPwd = userPwd;
        this.userName = userName;
        this.phone = phone;
        this.email = email;
        this.address = address;
        this.interest = interest;
    }

    public Member(String userId, String userName, String phone, String email, String address, String interest) {
        super();
        this.userId = userId;
        this.userName = userName;
        this.phone = phone;
        this.email = email;
        this.address = address;
        this.interest = interest;
    }

    public int getUserNo() {
        return userNo;
    }

    public void setUserNo(int userNo) {
        this.userNo = userNo;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getInterest() {
        return interest;
    }

    public void setInterest(String interest) {
        this.interest = interest;
    }

    public Date getEnrollDt() {
        return enrollDt;
    }

    public void setEnrollDt(Date enrollDt) {
        this.enrollDt = enrollDt;
    }

    public Date getModifyDt() {
        return modifyDt;
    }

    public void setModifyDt(Date modifyDt) {
        this.modifyDt = modifyDt;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Member [userNo=" + userNo + ", userId=" + userId + ", userPwd=" + userPwd + ", userName=" + userName
                + ", phone=" + phone + ", email=" + email + ", address=" + address + ", interest=" + interest
                + ", enrollDt=" + enrollDt + ", modifyDt=" + modifyDt + ", status=" + status + "]";
    }
   
}

 

 

- 이 클래스가 어떤 필드를 가져야 할지는 테이블을 먼저 만들고 그 컬럼에서 따오면 된다.

sql mapper의 Member 테이블에서 열 목록을 그대로 복사해서 이클립스에서 붙여넣기 한다.

그 컬럼명을 참고해서 USER_NO 옆에 private int userNo; 이런식으로 작성하고,

USER_NO 쪽은 다 지우면 된다.

- Date는 import할때 java.sql의 클래스를 import 해야 한다.

그래야 db로부터 조회된 DATE 컬럼의 값을 담을 수 있다.

 

- 날짜 두 개와 회원번호 빼고는 다 String형이다.

- 변수 선언을 다 하고서는 생성자, 게터세터, toString 메소드까지 만들어 준다. 

- db로부터 조회된 데이터가 Member 클래스의 각 필드에 담길텐데 잘 담겼는지 출력을 통해 확인해보기 위해 toString 메소드도 꼭 있어야 한다.

 

 

 

 

ㅁ - src/main/java의 com.br.web.member.model.service 패키지에 MemberService 클래스를 만든다.

 

 

- com.br.web.member.model.service 패키지에 MemberService 클래스를 만든다.

 

 

 

 

 

 

 

- com.br.web.member.model.dao 패키지에 MemberDao 클래스를 만든다.

- Dao 클래스는 Data access object다. 

db에 접근해서 sql문을 실행해서 데이터를 주고받는다.

jdbc의 주된 과정이 dao에서 진행된다.

 

 

 

 

 

- dao에서 실행시킬 sql문은 db.mappers라는 패키지에 member-mapper.xml 파일에 작성할 예정이다.

저 파일에서 쿼리를 가져와서 프로퍼티에 읽어들인다.

- 그래서 dao에서는 프로퍼티스 객체를 전역에서 쓸 수 있게끔 전역변수로 선언한다.

그리고 이 프로퍼티스 객체 변수에 member-mapper.xml 파일에 기술되어 있는 key-value 세트들이 들어와야 한다.

 

 

 

 

 

- MemberDao 객체가 생성될 때 기본 생성자가 실행되므로,

기본 생성자에 xml파일을 읽어들여서 프로퍼티스 객체 변수에 담기게 하는 구문을 작성한다.

- 쿼리문이 작성되어 있는 member-mapper.xml 파일을 읽어들여야 한다.

여기서도 JDBCTemplate.java에서 했던 것처럼 똑같이 getResource() 메소드와 getPath() 메소드를 사용한다.

- dao 패키지에서도 src/main/java/db.mappers 패키지 안에 있는 member-mapper.xml 파일이 아니라,

webApp/src/main/webapp/WEB-INF/classes/db/mappers 폴더의 member-mapper.xml 파일을 불러와야 한다.

 

- MemberDao.class하면 컴파일된 클래스를 가리킨다. 

getResource 메소드를 사용하면 찾고자하는 파일의 일부 경로 제시가 가능하다. 

여기서의 슬래시가 현재 컴파일된 클래스의 루트폴더(classes)이다.

 

- 쿼리문은 properties 파일에서 읽어들이는게 아니고 xml 파일에서 읽어들이므로

load() 메소드가 아닌 loadFromXml ()메소드로 담는다.

 

 

 

- 아래는 현재 MemberDao.java다.

 

 
package com.br.web.member.model.dao;

import static com.br.web.common.template.JDBCTemplate.close;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

import com.br.web.member.model.vo.Member;

public class MemberDao {
   
    private Properties prop = new Properties();
   
    public MemberDao() {
       
        String filePath = MemberDao.class.getResource("/db/mappers/member-mapper.xml").getPath();
       
        try {
            prop.loadFromXML(new FileInputStream(filePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
       
    }
   
   
}