BackEnd/Database Programming
[DB Programming] DAO & DTO Patterns - Persistence/Service(Business)/Presentation Layer
하노정
2022. 11. 8. 10:39
3-tier Application Architecture
Client -> Presentation Layer -> Business/Service Layer -> Persistence Layer(DAO)
- controller는 사이사이에 껴있음
- 바로 밑 Layer만 이용
- 밑 Layer를 호출 시 어떻게 구현됐는지 알 필요 없음
- manager class가 DAO, JSP 사이에 껴있음
DAO Pattern
- 개요
- 영속성 계층(Persistence Layer)
- 애플리케이션의 객체 및 데이터를 영속적으로 저장 관리하는 계층. 프로그램이 중단되어도 유지되며 프로그램 실행 시 다시 사용 가능
- Database, File System, LDAP Server 등 데이터 저장소(Data Source)
- 비즈니스 계층에서 영속성 계층에 접근 시 이용할 수 있는 구성 요소 필요
- 데이터 저장소의 유형에 관계없이 이용할 수 있는 공통의 API 제공
- 데이터 저장소에 독립적인 애플리케이션 개발 가능
- 영속성 계층(Persistence Layer)
- Data Access Object(DAO)
- 데이터 저장소를 공통의 인터페이스를 통해 이용할 수 있도록 해주는 구성 요소
- 데이터 저장소에 대한 접근 및 이용 방법을 추상화하고 캡슐화 함. 자세한 건 숨겨져 있다는 의미.
- 데이터 저장소에 접근하기 위한 API를 갖는 인터페이스 & DAO 인터페이스를 구현한 구현 클래스
- 특정 정보에 접근에 필요한 기능(메소드)들을 하나의 인터페이스로 정의 -> 하나의 인터페이스에 대해 여러 구현 클래스들을 정의해 사용
- DAO 인터페이스 정의
- Data Source에 대해 수행할 작업 선정 - 데이터 삽입/수정/삭제/검색(다양하게 가능)
DTO Pattern
- Data Transfer Object(DTO)
- Java 객체들 사이의 메소드 호출에 필요한 데이터들을 포함하는 객체
- 여러 데이터들을 하나의 DTO로 묶어 다른 객체나 sub-system에 전송
- Business Layer와 Persistence Layer 간의 데이터 전송 뿐만 아니라, Business Layer와 Presentation Layer 사이에서도 이용됨
- JavaBeans 또는 POJO(Plain Old Java Object)로 구현
- 데이터를 저장하기 위한 필드들과 이에 대한 getter/setter 메소드 정의
- 일반적으로 business method는 포함하지 않음
- 네트워크를 통한 전송이나 파일 시스템 저장을 위해서는 데이터 직렬화(serialization)가 가능하도록 java.io.Serializable interface를 구현
- 관련 업무에 필요한 데이터들로 구성
- 일반적으로 데이터들은 여러 테이블에 나뉘어 저장 관리됨
- 전달하고 싶은 정보를 묶어서 DTO 클래스로 정의하면 되는데, 테이블 정보와 정확히 일치하지 않음. 사용 목적에 맞게 정의하면 됨
- Java 객체들 사이의 메소드 호출에 필요한 데이터들을 포함하는 객체
DAO & DTO 사용
- 프로젝트 구조
- Persistence Layer - DAO, util 포함
- persistence.DAOFactory class - DAO를 구현한 클래스가 여러 개 있을 경우, 각 클래스의 객체를 생성해 반환하는 역할을 하는 클래스, DAO를 사용하는 클래스에서 특정 DAO 구현 객체와 밀접하게 결합되는 것을 막기 위한 용도
- persistence.util.JDBCUtil class - JDBC API 사용 시 코드의 중복성과 복잡성을 제거하기 위한 utilty class
- Business/Service Layer - DTO, service 포함
- Presentation Layer - JSP, HTML 포함
- Persistence Layer - DAO, util 포함
- persistence.util.JDBCUtil class
// Java Project 용 JDBCUtil
// DBCP2 관련 jar 파일을 프로젝트에 포함해야 동작함
// commons-dbcp2-X.X.X.jar, commons-pool2-X.X.X.jar, commons-logging-X.X.jar
package model.dao;
import java.sql.*;
public class JDBCUtil {
private static ConnectionManager connMan = new ConnectionManager();
private String sql = null; // 실행할 query문 저장
private Object[] parameters = null;; // PreparedStatement 의 매개변수 값을 저장하는 배열
private static Connection conn = null;
private PreparedStatement pstmt = null;
private CallableStatement cstmt = null;
private ResultSet rs = null;
private int resultSetType = ResultSet.TYPE_FORWARD_ONLY, resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
// 기본 생성자
public JDBCUtil() {
}
// sql 변수 getter
public String getSql() {
return this.sql;
}
// 매개변수 배열에서 특정위치의 매개변수를 반환하는 메소드
private Object getParameter(int index) throws Exception {
if (index >= getParameterSize())
throw new Exception("INDEX 값이 파라미터의 갯수보다 많습니다.");
return parameters[index];
}
// 매개변수의 개수를 반환하는 메소드
private int getParameterSize() {
return parameters == null ? 0 : parameters.length;
}
// sql 및 Object[] 변수 setter
public void setSqlAndParameters(String sql, Object[] parameters) {
this.sql = sql;
this.parameters = parameters;
this.resultSetType = ResultSet.TYPE_FORWARD_ONLY;
this.resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
}
// sql 및 Object[], resultSetType, resultSetConcurrency 변수 setter
public void setSqlAndParameters(String sql, Object[] parameters, int resultSetType, int resultSetConcurrency) {
this.sql = sql;
this.parameters = parameters;
this.resultSetType = resultSetType;
this.resultSetConcurrency = resultSetConcurrency;
}
// 현재의 PreparedStatement를 반환
private PreparedStatement getPreparedStatement() throws SQLException {
if (conn == null) { // Connection이 없을 경우
conn = connMan.getConnection(); // 새로운 Connection 획득
conn.setAutoCommit(false);
}
if (pstmt != null) // Connection이 있을 경우 재사용
pstmt.close(); // 기존 PreparedStatement 객체를 삭제하고
pstmt = conn.prepareStatement(sql, resultSetType, resultSetConcurrency); // 새 PreparedStatement 객체 생성
return pstmt;
}
// JDBCUtil의 쿼리와 매개변수를 이용해 executeQuery를 수행하는 메소드
public ResultSet executeQuery() {
try {
pstmt = getPreparedStatement(); // 위 메소드 호출
for (int i = 0; i < getParameterSize(); i++) { // parameter가 몇 개든 가능
pstmt.setObject(i + 1, getParameter(i)); // 매개변수 설정
}
rs = pstmt.executeQuery(); // 질의 실행 후 ResultSet 객체 참조 반환
return rs;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
// JDBCUtil의 쿼리와 매개변수를 이용해 executeUpdate를 수행하는 메소드
public int executeUpdate() throws SQLException, Exception {
pstmt = getPreparedStatement();
int parameterSize = getParameterSize();
for (int i = 0; i < parameterSize; i++) {
if (getParameter(i) == null) { // 매개변수 값이 널이 부분이 있을 경우
pstmt.setString(i + 1, null);
} else {
pstmt.setObject(i + 1, getParameter(i)); // 매개변수 설정
}
}
return pstmt.executeUpdate(); // DML문 실행 후 count 값 반환
}
// 현재의 CallableStatement를 반환
private CallableStatement getCallableStatement() throws SQLException {
if (conn == null) {
conn = connMan.getConnection();
conn.setAutoCommit(false);
}
if (cstmt != null)
cstmt.close();
cstmt = conn.prepareCall(sql);
return cstmt;
}
// JDBCUtil의 쿼리와 매개변수를 이용해 CallableStatement의 execute를 수행하는 메소드
public boolean execute(JDBCUtil source) throws SQLException, Exception {
cstmt = getCallableStatement();
for (int i = 0; i < source.getParameterSize(); i++) {
cstmt.setObject(i + 1, source.getParameter(i));
}
return cstmt.execute();
}
// PK 컬럼 이름 배열을 이용하여 PreparedStatement를 생성 (INSERT문에서 Sequence를 통해 PK 값을 생성하는 경우)
private PreparedStatement getPreparedStatement(String[] columnNames) throws SQLException {
if (conn == null) {
conn = connMan.getConnection();
conn.setAutoCommit(false);
}
if (pstmt != null)
pstmt.close();
pstmt = conn.prepareStatement(sql, columnNames);
return pstmt;
}
// 위 메소드를 이용하여 PreparedStatement를 생성한 후 executeUpdate 실행
public int executeUpdate(String[] columnNames) throws SQLException, Exception {
pstmt = getPreparedStatement(columnNames); // 위 메소드를 호출
int parameterSize = getParameterSize();
for (int i = 0; i < parameterSize; i++) {
if (getParameter(i) == null) { // 매개변수 값이 널이 부분이 있을 경우
pstmt.setString(i + 1, null);
} else {
pstmt.setObject(i + 1, getParameter(i));
}
}
return pstmt.executeUpdate();
}
// PK 컬럼의 값(들)을 포함하는 ResultSet 객체 구하기
public ResultSet getGeneratedKeys() {
try {
return pstmt.getGeneratedKeys();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// 자원 반환
public void close() {
if (rs != null) {
try {
rs.close();
rs = null;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
pstmt = null;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
if (cstmt != null) {
try {
cstmt.close();
cstmt = null;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
public void commit() { // 트랜잭션 commit 수행
try {
conn.commit();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public void rollback() { // 트랜잭션 rollback 수행
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
// DataSource 를 종료
public void shutdownPool() {
this.close();
connMan.close();
}
// 현재 활성화 상태인 Connection 의 개수와 비활성화 상태인 Connection 개수 출력
public void printDataSourceStats() {
connMan.printDataSourceStats();
}
}