잊어버리기 전에 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
'Java - 기본기' 카테고리의 다른 글
웹 네트워크 기본 공부 1) 네트워크와 웹 브라우저의 요청 흐름 (4) | 2022.08.17 |
---|---|
25. Java Swing 과 JFrame, Graphics (0) | 2022.04.05 |
24. Thread - 스레드 다루기(feat. Lotte 번호찍기) (0) | 2022.03.14 |
23. Inner Class, 제네릭 자료형 (0) | 2022.03.13 |
22. 추상 클래스, 인터페이스, static, final (0) | 2022.03.05 |
댓글