Java - 기본기

26. VO, DTO, Entity, DAO 와 JDBC

TerianP 2022. 4. 22.
728x90

잊어버리기 전에 Java 로 백엔드를 다루면서 가장 중요하고, 기본적인 DAO, DTO, VO 를 정리해두려고 한다. 또 JDBC 도 정리하려고 한다.

 

1. VO : Value Object

일종의 Small Object
값을 사용하기 위해서만 존재하는 객체, ReadOnly

- Value Object 는 값 그 자체를 표현하기 위해 사용하는 객체 => 값을 담는 항아리

-  Read-Only 특징 => 데이터 읽기 중심임으로 데이터를 검증&검사하기 위한 메서드가 존재

- VO 의 가장 큰 특징은 equals() 와 hashcode() 를 오버라이딩해서 사용한다는 점!! 이는 이후 VO 객체마다 같은 객체인지 다른 객체인지를 비교하기 위해서라고 한다.

- Entity 와는 다르다!! 다만, JPA 까지 가면 DB 와 매핑해서 값이 담기고 이를 사용하는 Entity 와 개념이 비슷해짐  

 

 

2. DTO : Data Transfer Object

메모리, 송수신 처리에 있어 효율적으로 데이터 전송하기 위해 사용

 

- DTO 는 계층(Layer) 간 데이터 교환을 위해 사용하는 클래스이다. 이 클래스는 데이터 교환을 위해 사용함으로 별다른 로직을 갖지 않으며, getter/setter 메소드만 갖는다 => 교환만을 위해 존재하는 객체

- 보통 DB 의 데이터를 Service 나 Controller, view 등으로 보낼때 사용되는 객체이다. 가장 무난하게 많이 쓰인다.

- DTO 의 예시 : 이전에 만들었던 자바 음악 게임에서 사용한 DTO 이다. 여기서 보면 알 수 있듯이 별다른 로직은 존재하지 않고, 오직 getter 와 setter 만 존재한다.

- Entity 를 전송에 사용하는 대신 Entity 를 담은 DTO 를 데이터 전송에 사용한다

package DynamicMusic;

public class UserDTO {
    String name;
    int scoreDF;
    int scoreLYS;
    int scoreTFR;

    public UserDTO(String name, int score){
        this.name = name;
        this.scoreTFR = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScoreDF() {
        return scoreDF;
    }

    public void setScoreDF(int scoreDF) {
        this.scoreDF = scoreDF;
    }

    public int getScoreLYS() {
        return scoreLYS;
    }

    public void setScoreLYS(int scoreLYS) {
        this.scoreLYS = scoreLYS;
    }

    public int getScoreTFR() {
        return scoreTFR;
    }

    public void setScoreTFR(int scoreTFR) {
        this.scoreTFR = scoreTFR;
    }
}

 

 

3. Entity

- Entity 는 실제 DB 의 테이블과 1 : 1 로 Mapping 되는 class 로 DB 테이블 내에 존재하는 컬럼만을 속성으로 갖는다. 즉 멤버 변수명과 컬럼명이 일치해야 한다.

- Entity 클래스는 상속을 받거나 구현체여서는 안되며, 테이블 내 존재하지 않는 컬럼을 가져서도 안된다.

- DB 와 1:1 로 Mapping 되는 만큼, 보안상 가장 중요한 위치를 차지하며, 외부에서 해당 클래스 내 변수들에 함부로 접근하지 못하도록 제한하며, 접근하는 데이터들에 대해서도 logic 메소드를 구현해야한다.

package HJproject.Hellospring.domain.member;

import javax.persistence.*;

@Entity(name = "MEMBER")
//@Table(name = "MEMBER") // TABLE 어노테이션 사용해서 테이블 이름을 지정 가능
public class Member {

    // @Id : DB 에서 primary key 로 설정된 컬럼 맵핑
    // @GeneratedValue : pk 값의 생성 방식 -> 현재는 DB 에서 자동 생성임으로 이에 해당하는 Identity
    // springdataJPA 에서는 _ 가 예약어로 되어있어서 사용이 불가능하단다ㅋㅋㅋㅋ => 컬럼명에서는 _ 가능
    // 만약 그래도 쓸꺼면 아래처럼 column 해서 컬럼명을 정해주고 변수명만 바꾸자
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_CODE")
    private Long MEMBERCODE; // 시스템에서 저장 & 식별 구분 하기 위한 code => primary key

    // @Column : 컬럼명 코드 변수 매핑 -> name = "컬럼명"
    @Column(name = "MNAME")
    private String MNAME; // 고객 이름


    @Column(name = "MID")
    private String MID; // 고객 id

    @Column(name = "MPASSWD")
    private String MPASSWD; // 고객 passwd

/* 6. 내 마음대로 구현하기 step 2 */

    @Column(name = "MGENDER")
    private String MGENDER;

    @Column(name = "MEMAIL")
    private String MEMAIL;

    @Column(name = "MEMADDRESS")
    private String MEMADDRESS;

    @Column(name = "MRDATE")
    private String RDate;



    public Long getMEMBERCODE() {
        return MEMBERCODE;
    }

    public void setMEMBERCODE(Long MEMBERCODE) {
        this.MEMBERCODE = MEMBERCODE;
    }

    public String getMNAME() {
        return MNAME;
    }

    public void setMNAME(String MNAME) {
        this.MNAME = MNAME;
    }

    public String getMID() {
        return MID;
    }

    public void setMID(String MID) {
        this.MID = MID;
    }

    public String getMPASSWD() {
        return MPASSWD;
    }

    public void setMPASSWD(String MPASSWD) {
        this.MPASSWD = MPASSWD;
    }

    public String getMGENDER() {
        return MGENDER;
    }

    public void setMGENDER(String MGENDER) {
        this.MGENDER = MGENDER;
    }

    public String getMEMAIL() {
        return MEMAIL;
    }

    public void setMEMAIL(String MEMAIL) {
        this.MEMAIL = MEMAIL;
    }

    public String getMEMADDRESS() {
        return MEMADDRESS;
    }

    public void setMEMADDRESS(String MEMADDRESS) {
        this.MEMADDRESS = MEMADDRESS;
    }

    public String getRDate() {
        return RDate;
    }

    public void setRDate(String RDate) {
        this.RDate = RDate;
    }
}

 

Entity 는 VO, DTO 와 다르게 다음 2가지의 특징을 갖는다.

1. Entity 와 DTO 는 분리한다.
Entity 와 DTO 는 분리해서 사용해야한다. 이는 두 클래스가 사용되는 계층이 서로 다르기 때문이다. Entity 클래스는 DB Layer 에서 사용되는데 반해 DTO 는 View Layer 에서 사용된다. 이때 Entity 는 실제 DB 테이블과 매핑되는 만큼 혹시라도 변경하게 되면 다른 여러 클래스들에 영향을 끼치게 된다. 반면 DTO View 와 지속적으로 통신하는 만큼 값이 자주 변경된다.
이로인해 두 클래스는 서로 분리해서 사용해야 한다.

2. Entity 의 Setter 제한과 생성자
앞서 이야기했듯 Entity 는 DB 와 직접적인 통신을 한다. 이 때문에 쉽게 변경되어서는 안되며 값이 일관성있게 유지되어야 한다. 즉 객체 일관성이 유지되어야 하는 만큼 객체의 setter 를 사용하는 대신 클래스의 생성자를 사용해서 값들을 넣어줌으로써 Setter 의 사용을 줄이는 방법을 사용해야한다.

 

4. DAO : 데이터 저장 도구로서의 역할

- DAO 는 DAO 는 데이터 베이스의 data에 접근하기 위한 객체이다. 정확히는 DataBase 에 접근하는 로직처리와 비지니스 로직 처리를 하기 위해 사용한다.

- DB에 대한 접근을 DAO가 담당하도록 하여 데이터베이스 엑세스를 DAO 에서만 하게 되고, 이를 통해 다수의 원격호출을 통한 오버헤드나 DB 호출 문제를 해결 할 수 있다고 한다 => 즉 엄청나게 많은 사용자가 동시에 접속을 하고, 게시판에 들어가고, 글을 쓰면서 생기는 DB 트랜잭션 간 오버헤드를 줄 일 수 있다고 한다.

- 편하게 생각하자면 DB 에 접근하여 직접 쿼리를 날려 CRUD 하는 모든 로직처리가 바로 DAO 객체를 통해서 이루어진다라고 생각하자. 여기서 한가지 더!! Spring JPA 에는 Repository 어노테이션이 붙는 클래스들이 있는데 이 클래스들도 DAO 라고 할 수 있다. 다만 DAO 와 Repository 가 개념적인 측면에서는 살짝 차이를 보인다고는 하는데...일단 넘어가자ㅋㅋ

- 아래는 DynamicMusic 에서 사용했던 DAO 를 가져와보았다. 크게 DB와 연결하는 부분 conn, 내가 작성한 정보와 로그인 실제 DB에 있는 값을 가져와서 비교후 로그인 시켜주는 메소드, 게임이 끝난후 해당 점수를 저장하는 메소드, 게임별로 DB 에 저장된 점수를 가져와서 보여주기 위한 메소드 등이 들어있다. 이렇듯 DAO 는 DB 와 연결을 통해 전반적인 CRUD 를 담당한다.

package DynamicMusic;

import java.sql.*;
import java.util.ArrayList;

public class LoginDAO extends DB_info {

    // DB 연결을 위한 statement, resultSet
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    String sql;

    // 사용자가 입력한 ID, PW
    private String loginID;
    private String loginPW;

    // DB에서 가져온 ID PW
    private int getMemberCODE;
    private String getID;

    public LoginDAO() {
        try {
            conn = DriverManager.getConnection(getHjDB(), getUser(), getPasswd());
        } catch (SQLException e) {
            System.out.println("DB conn 에러 : " + e.getMessage());
            e.printStackTrace();
        }
    }

    public boolean loginStart() {
        sql = "SELECT MEMBER_CODE, MID, MPASSWD FROM MEMBER WHERE MID = ? AND MPASSWD = ?";


        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, loginID);
            pstmt.setString(2, loginPW);

            rs = pstmt.executeQuery();

//                System.out.println("현재 멤버 코드 : "+getMemberCODE);
//                System.out.println("현재 아이디 : "+getID);
//                System.out.println("현재 패스워드 : "+getPW);


            if (rs.next()) {
                getMemberCODE = rs.getInt("MEMBER_CODE");
                getID = rs.getString("MID");

                System.out.println("로그인 정보 확인 로그인 완료");

                return true;
            }

            return false;

        } catch (Exception e) {
            System.out.println("SQL 에러 발생 : " + e.getMessage());

            return false;
        }

    }


    public void scoreUpdate(int highscore, int highcombo, String musicName) {

//        System.out.println("최고 스코어 : "+highscore);
//        System.out.println("최고 콤보 : "+highcombo);

        try{

            if (musicName.equals("DAYBREAK FRONTLINE")) {
                sql = "UPDATE DMUSIC SET DF_SCORE = ?, DF_COMBO = ? WHERE MEMBER_CODE = ?";


                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, highscore);
                pstmt.setInt(2, highcombo);
//                pstmt.setInt(3, getMemberCODE);
                pstmt.setInt(3, getMemberCODE);


                pstmt.executeUpdate();
//                conn.commit();
                System.out.println("점수 입력 완료");

            } else if (musicName.equals("Lose Yourself - Eminem")) {
                sql = "UPDATE DMUSIC SET LYS_SCORE = ?, LYS_COMBO = ? WHERE MEMBER_CODE = ?";


                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, highscore);
                pstmt.setInt(2, highcombo);
                pstmt.setInt(3, getMemberCODE);

                pstmt.executeUpdate();
//                conn.commit();
            } else if (musicName.equals("TheFatRat - The Calling")) {
                sql = "UPDATE DMUSIC SET TFR_SCORE = ?, TFR_COMBO = ? WHERE MEMBER_CODE = ?";


                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, highscore);
                pstmt.setInt(2, highcombo);
                pstmt.setInt(3, getMemberCODE);

                pstmt.executeUpdate();
//                conn.commit();
            }


        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    public ArrayList<UserDTO> scorePanel(String musicName){
        ArrayList<UserDTO> list = new ArrayList<UserDTO>();

        try {
            // 노래에 맞춰 SQL 변경
            if(musicName.equals("DAYBREAK FRONTLINE")) {

                sql = "SELECT M.MNAME AS NAME, D.DF_SCORE AS SCORE FROM MEMBER M NATURAL JOIN DMUSIC D ORDER BY DF_SCORE DESC LIMIT 5";

            }else if(musicName.equals("Lose Yourself - Eminem")){

                sql = "SELECT M.MNAME AS NAME, D.LYS_SCORE AS SCORE FROM MEMBER M NATURAL JOIN DMUSIC D ORDER BY LYS_SCORE DESC LIMIT 5";

            }else if(musicName.equals("TheFatRat - The Calling")){

                sql = "SELECT M.MNAME AS NAME, D.TFR_SCORE AS SCORE FROM MEMBER M NATURAL JOIN DMUSIC D ORDER BY TFR_SCORE DESC LIMIT 5";
            }

            pstmt = conn.prepareStatement(sql);

            rs = pstmt.executeQuery();
//                rs.next();
//                System.out.println(rs.getString("D.MEMBER_CODE"));

            while (rs.next()) {
                String name = rs.getString("NAME");
                int score = rs.getInt("SCORE");

                list.add(new UserDTO(name, score));
//                System.out.println(name+" : "+score);
            }

            return list;

        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }

    }

    private void closeDB() {
        try {
            if (rs != null) {
                rs.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
//        System.out.println("자원 반납 완료");
    }


    public String getLoginID() {
        return loginID;
    }

    public void setLoginID(String loginID) {
        this.loginID = loginID;
    }

    public String getLoginPW() {
        return loginPW;
    }

    public void setLoginPW(String loginPW) {
        this.loginPW = loginPW;
    }

    public String getGetID() {
        return getID;
    }

//    public static void main(String[] args) {
//        LoginDAO dao = new LoginDAO();
////        dao.scoreUpdate(50,50,"DAYBREAK FRONTLINE");
//
//        ArrayList<UserVO> list = dao.scorePanel("TheFatRat - The Calling");
//        for(UserVO user : list){
//            System.out.println(user.getName() + " : "+user.getScoreTFR());
//        }
//
//    }

}

 

4. JDBC

- 사실 JDBC는 이전에도 공부하면서 한번 다루었던 적이 있는데, 당시에는 그냥 그런게 있나보다...하고 넘어갔었기 때문에 정리를 하면서도 솔직히 강의를 따라적는 것 이상이 아니었다. 다만 이제는 그래도 JDBC 를 제대로 사용해서 미니 프로젝트도 진행한 만큼 좀 더 확실히 적어두려고 한다.

- JDBC 에서 가장 중요한 것은 딱 4가지로 다음과 같다. 아래 4가지만 기억하면 솔직히 JDBC 별거 없다!!

  • DB 드라이버, DB 주소, DB 접속 id & passwd  총 4가지를 Connection 객체의 매개변수로 넣어서 conn 객체를 생성 초기화 한다.
  • PrearedStatment 일명 pstmt 에 내가 생성한 SQL query 를 넣고, 쿼리를 날릴 준비를 한다. 이때 sql 문장을 처음부터 완성할 수도 있고, pstmt 의 set 메소드들을 통해 추후 완성도 가능하다.
  • 여기서 부터는 총 2가지 방법인데 첫째로는 내가 실행한 쿼리가 select 문인 경우 쿼리를 통해 얻어낸 데이터를 받아와서 ResulSet 에 저장한 후 사용하는 방법둘째로 update, delete, insert 의 경우 ResultSet 을 사용해서 단순히 쿼리를 실행하고 끝내는 방법이 있다.

이때 제일 중요한 것!!!!! ResultSet 에 값을 저장한 후 출력을 위해서는 꼭!! rs.next() 를 해주어야한다


  • 마지막으로!! close 를 사용하여 맨 마지막으로 사용한 객체부터 순서대로 close 하여 내가 사용한 자원을 반납하는 것이다.

- 아래 2가지의 코드로 위에 4가지 내용에 대해서 설명하겠다.

  • 첫번째 코드는 select 문을 사용하는 코드로 pstmt 의 executeQuery() 메소드를 사용하여 select 결과를 얻어오고, 이에 대한 결과를 ResultSet 에 저장한다. 이후 getString 을 통해서 값을 가져와서 출력하는 과정을 거친다.
  • 먼저 확인해야 하는 것은 sql String 에서 보이는 ?  부분이다. 해당 쿼리는 로그인을 위해서 사용하는 쿼리문으로 로그인 사용자가 바뀔 때마다 쿼리에 담기는 내용도 달라져야 한다. 즉 고정값이 아니라 지속적으로 변하는 변수가 sql 안에 들어와야하는 것이다. ? 는 query 에서의 변수를 담당하며, ? 부분을 ResultSet 의 set 메소드를 사용하여 지정한다.
    • set 메소드의 첫번째 매개변수는 ? 의 순서를 지정한다. 즉 첫번째 오는 ? 에 대해서 지정하려면 1, 두번째 오는 ? 에 대해서 지정하려면 2 가 되는 것이다. 
    • set 메소드의 두번째 매개변수는 내가 넣고자하는 값의 타입을 확인해서 set 메소드를 지정해주면 된다. 즉, int 값을 넣는 경우는 setInt, String 값이면 setString 이 된다.
    • 간단하게 아래 예시를 보면 MID 는 첫번째 ? 가 된다. 이때문에 setString(1, loginID) 가 된 것이고, MPASSWD 는 두번째 ? 이기 때문에 setString(2, loginPW) 가 된 것이다.
sql = "SELECT MEMBER_CODE, MID, MPASSWD FROM MEMBER WHERE MID = ? AND MPASSWD = ?";
            pstmt.setString(1, loginID);
            pstmt.setString(2, loginPW);
  • 다시 ResultSet 의 getString 메소드를 사용해서 결과를 저장한다. 이때 getString 의 메소드는 매개변수로 query문으로 가져온 DB의 컬럼명을 넣는다. 예를들어 아래 query 를 통해서 가져온 컬럼명이 MEMBER_CODE, MID, MPASSWD 이기 때문에 getString 에 매개변수로도 동일한 값을 넣은 것이다.
  • 여기서 제일 중요한 것!!! 값을 제대로 출력하기 위해서는 꼭 rs.next 를 해주어야한다. 이는 ResultSet 에 값이 저장되면 0번째 해당 쿼리로 저장된 값의 0번째 줄 즉, 컬럼명 부분부터 가져오게 된다. 때문에 진짜 Table 에 저장된 값- 1번째 줄-을 가져오기 위해서는 rs.next() 가 필수가 되는 것이다.
    public boolean loginStart() {
        sql = "SELECT MEMBER_CODE, MID, MPASSWD FROM MEMBER WHERE MID = ? AND MPASSWD = ?";


        try {
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, loginID);
            pstmt.setString(2, loginPW);

            rs = pstmt.executeQuery();

            if (rs.next()) {
                getMemberCODE = rs.getInt("MEMBER_CODE");
                getID = rs.getString("MID");

                System.out.println("로그인 정보 확인 로그인 완료");

                return true;
            }

            return false;

        } catch (Exception e) {
            System.out.println("SQL 에러 발생 : " + e.getMessage());

            return false;
        }
    }

  • 다음으로는 점수를 업데이트하는 메소드이다. 여기서 점수를 업데이트하는 메소드이기 때문에 사용되는 query 역시 update 문을 사용한다.
  • 사실 update 문이라고 다를 거는 전혀 없고, 딱 한가지만 주의하면 된다.
  • select 문을 제외한 나머지 query 는 executequery 가 아니라 executeUpdate 문을 사용한다는 점이다. 물론 executequery 를 사용해도 문제될것은 없다고한다. executequery 는 쿼리문을 실행한 후 해당 내용을 가져오는 메소드이기 때문에 query 가 실행된 후 결과를 가져올 필요가 없는 sql 문장들에 대해서는 executeUpdate 를 사용하는 것이다.
  • executeUpdate 의 경우 반환값은 다음과 같다.
    • INSERT, UPDATE, DELETE : SQL QUERY 실행 후 반영된(변경된) 레코드의 건수를 반환 => INT 값
    • CREATE / DROP 구문에 대해서는 -1 반환
    public void scoreUpdate(int highscore, int highcombo, String musicName) {


        try{

            if (musicName.equals("DAYBREAK FRONTLINE")) {
                sql = "UPDATE DMUSIC SET DF_SCORE = ?, DF_COMBO = ? WHERE MEMBER_CODE = ?";


                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, highscore);
                pstmt.setInt(2, highcombo);
                pstmt.setInt(3, getMemberCODE);


                pstmt.executeUpdate();
                System.out.println("점수 입력 완료");

            }

참고

https://velog.io/@gillog/Entity-DTO-VO-%EB%B0%94%EB%A1%9C-%EC%95%8C%EA%B8%B0

 

Entity, DTO, VO 바로 알기

Spring Boot 프로젝트를 진행하면서 JPA를 사용하게 되었다. MyBatis를 쓸 때는 개념적으로 DTO, VO가 그냥 데이터 객체들을 옮겨다 주는 통 정도로만 이해하고 사용했는데, 이번에 JPA를 사용하면서 Entit

velog.io

https://velog.io/@ohzzi/Entity-DAO-DTO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C

 

Entity, DAO, DTO가 무엇이며 왜 사용할까?

개인적으로 Spring Boot를 가지고 CRUD를 구현한 Todo-list를 만들어면서, Spring Data JPA를 사용하게 되었다. JPA를 사용하면서, 생전 처음 보는 Entity, DAO, DTO 개념을 사용하게 되었는데, 앞으로 계속 많이

velog.io

 

댓글