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 제공
      • 데이터 저장소에 독립적인 애플리케이션 개발 가능
  • 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 클래스로 정의하면 되는데, 테이블 정보와 정확히 일치하지 않음. 사용 목적에 맞게 정의하면 됨

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.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();
	}
}