본문 바로가기
JAVA

[JAVA] JDBC - DAO, DTO 패턴

by Amy IT 2022. 6. 9.

▶ DAO, DTO 패턴이란?

 

일반적으로 어플리케이션을 개발하려면 GUI(Graphical User Interface) 화면을 구성하는 코드와, GUI 화면에 데이터를 보여주기 위해 DB를 검색하고 GUI 화면에서 새로 발생된 데이터를 DB에 저장하는 등 실제적인 작업을 처리하는 코드가 필요합니다. 이때 GUI 화면을 구성하는 코드를 Presentation Logic(프리젠테이션 로직)이라고 하고, DB를 검색하고 관리하는 등 실제적인 작업을 처리하는 코드를 Business Logic(비즈니스 로직)이라고 합니다. Presentation Logic과 Business Logic을 하나의 클래스로 구현하면 유지보수가 어려워지기 때문에, 여러 클래스로 모듈화시켜 개발하는 것이 바람직합니다. 그중 실제 DB에 접근해서 DB 처리를 하는 클래스를 DAO(Data Access Object) 클래스라고 하고, Logic 간 데이터를 전송하고 반환할 때 사용하는 클래스를 DTO(Data Transfer Object) 클래스라고 합니다. 이렇게 DAO, DTO 클래스를 사용해서 DB 연동 프로그램을 개발하는 것이 보편화되었기 때문에 DAO, DTO 패턴이라고 부릅니다. DAO, DTO 패턴을 적용한 DB 연동 프로그램을 그림으로 표현하면 다음과 같습니다. 

 

 

 

 

▶ 실습

 

1. SELECT

 

scott 계정 dept 테이블의 모든 정보를 SELECT하는 프로그램을 만들어보겠습니다. 진행과정은 다음과 같습니다. 

 

  1. Main에서 Service 객체를 생성하면, Service 생성자는 DAO 객체를 생성하고 드라이버 로딩을 합니다.
  2. Main에서 Service 클래스의 select() 메소드를 호출합니다.
  3. 호출된 Service 클래스의 select() 메소드는 DB에 접속하고, 접속한 후 얻은 Connection 객체를 DAO 클래스의 select() 메소드로 전달합니다.
  4. DAO 클래스의 select() 메소드는 전달받은 Connection 객체를 이용해 실제 DB에 접근하여 SQL문을 실행하고, 실행 결과를 받아와서 Service 클래스로 리턴합니다.
  5. Service 클래스는 다시 Main으로 결과값을 리턴합니다.
  6. Main에서 결과값을 받아 사용할 수 있게 됩니다. 

 

(1) DTO 

 

DTO가 되는 Dept 클래스입니다. Dept 클래스는 dept 테이블의 레코드 하나를 나타내는 모델 클래스입니다. 한 레코드가 가지는 각 컬럼의 정보를 담을 수 있도록 컬럼명, 컬럼타입과 동일한 이름, 타입의 인스턴스 변수를 선언합니다. DB에서 꺼내온 데이터를 DTO를 이용해 저장하여 다른 Logic으로 전송할 수 있게 됩니다. getter/setter 메소드와 생성자도 설정하였습니다.

package com.dto;
public class Dept {
	private int deptno;
	private String dname;
	private String loc;
	public Dept() {
		super();
	}
	public Dept(int deptno, String dname, String loc) {
		super();
		this.deptno = deptno;
		this.dname = dname;
		this.loc = loc;
	}
	public int getDeptno() {
		return deptno;
	}
	public void setDeptno(int deptno) {
		this.deptno = deptno;
	}
	public String getDname() {
		return dname;
	}
	public void setDname(String dname) {
		this.dname = dname;
	}
	public String getLoc() {
		return loc;
	}
	public void setLoc(String loc) {
		this.loc = loc;
	}
	@Override
	public String toString() {
		return "Dept [deptno=" + deptno + ", dname=" + dname + ", loc=" + loc + "]";
	}
}

 

 

(2) Main

 

프로그램의 시작점인 메인입니다. 메인에서는 Service 객체를 사용합니다. Service 객체 생성 후, Service 클래스의 select 메소드를 호출하여 반환된 결과값을 ArrayList<Dept> 타입으로 저장합니다. 메인에 try~catch 문을 작성하면 Service 클래스와 DAO 클래스에서 발생된 예외를 한번에 처리할 수 있게 됩니다. 이후 받아온 list의 데이터를 순회하며 출력하고 있습니다.

import java.sql.SQLException;
import java.util.ArrayList;
import com.dto.Dept;
import com.service.OracleService;
public class TestMain {
	public static void main(String[] args) {
		OracleService service = new OracleService();
		ArrayList<Dept> list = new ArrayList<>();
		try {
			list = service.select();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		for (Dept dept : list) {
			System.out.println(dept);
		}
	}
}

 

 

(3) Service 

 

Service 클래스는 Business Logic을 처리하는 클래스로서, 메인 클래스와 DAO 클래스 사이를 연결해 줍니다. DB 연동에 필요한 네 가지 정보를 인스턴스 변수로 저장하고, Service에서 사용할 DAO 객체를 선언합니다. 메인에서 Service 객체를 생성할 때 DAO 객체가 함께 생성되도록 Service 클래스의 기본생성자 내에서 DAO 객체 생성을 합니다. 그리고 Service 객체 생성시 드라이버 로딩 작업까지 완료될 수 있도록 드라이버 로딩 코드도 함께 작성하였습니다. select 메소드 내에서는 저장된 접속 정보를 이용해 DB에 접속한 후, Connection 객체를 얻어옵니다. 얻어온 Connection 객체를 DAO 클래스의 select 메소드를 호출하며 전달하고, 리턴받은 결과값을 list에 저장합니다. 이후 Connection 연결을 해제하고 list를 리턴하고 있습니다. 

package com.service;
import java.sql.*;
import java.util.ArrayList;
import com.dao.OracleDAO;
import com.dto.Dept;
public class OracleService {
	String driver = "oracle.jdbc.driver.OracleDriver"; 
	String url = "jdbc:oracle:thin:@localhost:1521:xe";
	String userid = "scott";
	String passwd = "tiger";
	OracleDAO dao;
	public OracleService() {
		dao = new OracleDAO();
		try {
			Class.forName(driver);
			System.out.println("드라이버 로딩 성공");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	public ArrayList<Dept> select() throws SQLException {
		Connection con = null;
		ArrayList<Dept> list = null;
		try {
			con = DriverManager.getConnection(url, userid, passwd);
			list = dao.select(con); 
		} finally {
			if(con!=null) con.close();
		}
		return list;
	}
}

 

 

(4) DAO

 

DAO 클래스는 실제 DB와 연동하는 클래스입니다. Service 클래스의 select 메소드에서 DAO 클래스의 select 메소드를 호출하며 Connection 객체를 인자로 전달하면, DAO 클래스의 select 메소드에서는 전달받은 Connection 객체를 이용해 SQL문을 실행하고 실행한 결과값을 ResultSet 객체로 받아옵니다. ResultSet 객체에서 레코드를 하나씩 꺼내서 list에 추가시키면, list에는 dept 테이블의 모든 레코드가 하나씩 저장됩니다. 이후 ResultSet, PreparedStatement 연결을 해제하고 list를 리턴하고 있습니다. 

package com.dao;
import java.sql.*;
import java.util.ArrayList;
import com.dto.Dept;
public class OracleDAO {
	public ArrayList<Dept> select(Connection con) throws SQLException {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ArrayList<Dept> list = new ArrayList<>();
		try {
			String sql = "select * from dept";
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();
			while (rs.next()) {
				int deptno = rs.getInt(1);
				String dname = rs.getString(2);
				String loc = rs.getString(3);
				list.add(new Dept(deptno, dname, loc));
			}
		} finally {
			if(rs!=null) rs.close();
			if(pstmt!=null) pstmt.close();
		}
		return list;
	} 
}

 

Main => Service => DAO => DB => DAO => Service => Main 의 과정으로 이어지는 프로그램이 성공적으로 실행 완료되면 다음과 같은 출력 결과를 얻을 수 있게 됩니다.

 

 

 

 

 

2. INSERT

 

이번에는 dept 테이블에 부서 정보를 INSERT 해 보겠습니다. 

 

(2) Main

 

메인에서 Dept 객체 생성 후 Service 클래스의 insert 메소드를 호출하며 생성한 객체를 인자로 전달합니다. 

Dept dept89 = new Dept(89, "개발", "서울");
service.insert(dept89);

 

 

(3) Service 

 

Service 클래스에서는 DB에 접속하고 얻은 Connection 객체를, 메인으로부터 전달받은 Dept 객체와 함께 DAO 클래스의 insert 메소드로 전달합니다. 이번에는 발생하는 예외를 try~catch문으로 직접 처리하고 있습니다. 

public void insert(Dept dept) {
	Connection con = null;
	try {
		con = DriverManager.getConnection(url, userid, passwd);
		dao.insert(con, dept);
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		try {
			if(con!=null) con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

 

(4) DAO

 

DAO 클래스에서는 전달받은 Connection 객체와 Dept 객체를 이용해 SQL문을 실행하여 직접 레코드를 추가합니다. 이후 변경된 레코드 개수를 정수값으로 리턴받고 이를 출력하고 있습니다. 

public void insert(Connection con, Dept dept) {
	PreparedStatement pstmt = null;
	try {
		String sql = "insert into dept (deptno, dname, loc) "
				+ " values (?, ?, ?)";
		pstmt = con.prepareStatement(sql);
		pstmt.setInt(1, dept.getDeptno());
		pstmt.setString(2, dept.getDname());
		pstmt.setString(3, dept.getLoc());
		int result = pstmt.executeUpdate();
		System.out.println(result+" 개의 레코드가 변경되었습니다.");
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		try {
			if(pstmt!=null) pstmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

INSERT가 성공적으로 완료되면 다음과 같은 출력 결과를 얻을 수 있습니다. 확인을 위해 앞서 만들었던 select 메소드를 활용하고 있습니다. 

 

 

 

 

 

3. UPDATE 

 

이번에는 UPDATE 문을 실행하되, 없는 부서 번호를 지정하여 보겠습니다. UPDATE 또는 DELETE문 실행시 해당하는 레코드가 없어 변경된 레코드 개수가 0개가 되어도 예외는 발생하지 않습니다. 이때 다음과 같이 사용자 정의 예외 클래스를 이용해 강제로 예외를 발생시킬 수 있습니다. 

package com.exception;
public class RecordNotFoundException extends Exception { 
	public RecordNotFoundException(String message) {
		super(message);
	} 
}

 

(2) Main

 

없는 부서인 77번 부서를 UPDATE 해 보겠습니다. 강제 발생시킨 예외는 여기서 try~catch문으로 잡을 것입니다.

Dept dept77 = new Dept(77, "영업", "경기");
try {
	service.update(dept77);
} catch (RecordNotFoundException e1) {
	System.out.println(e1.getMessage());
}

 

 

(3) Service

 

마찬가지로 Service 클래스에서는 DAO 클래스의 update 메소드를 호출하며 Connection 객체와 Dept 객체를 인자로 전달합니다. 

public void update(Dept dept) throws RecordNotFoundException {
	Connection con = null;
	try {
		con = DriverManager.getConnection(url, userid, passwd);
		dao.update(con, dept);
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		try {
			if(con!=null) con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

 

(4) DAO

 

전달받은 Connection 객체와 Dept 객체로 SQL문을 실행 후 변경된 레코드 개수를 리턴받아 출력하고 있습니다. 이때 변경된 레코드 개수가 0일 경우 강제로 예외를 발생시킵니다. 발생된 예외를 위임하여 메인에서 처리하게 됩니다. 

public void update(Connection con, Dept dept) throws RecordNotFoundException {
	PreparedStatement pstmt = null;
	try {
		String sql = "update dept "
				+ " set dname=?, loc=? where deptno=?";
		pstmt = con.prepareStatement(sql);
		pstmt.setString(1, dept.getDname());
		pstmt.setString(2, dept.getLoc());
		pstmt.setInt(3, dept.getDeptno());
		int result = pstmt.executeUpdate();
		System.out.println(result+" 개의 레코드가 변경되었습니다.");
		if (result == 0) {
			throw new RecordNotFoundException(dept.getDeptno()+" 레코드가 없습니다.");
		}
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		try {
			if(pstmt!=null) pstmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

실행 결과는 다음과 같습니다. DAO 클래스에서 강제로 발생시킨 예외를 메인에서 처리하여 오류 메세지를 출력한 후 프로그램이 정상 종료되고 있습니다.

 

 

 

 

 

4. 트랜잭션(Transaction, tx) 처리

 

이번에는 트랜잭션 처리를 해 보겠습니다. JDBC에서는 모든 DML 작업이 자동으로 commit 처리됩니다. 따라서 트랜잭션으로 처리해야 하는 작업을 하기 위해서는 명시적으로 auto commit을 비활성화시켜야 합니다. 예를 들어 INSERT와 DELETE 작업을 연이어 처리하는 메소드가 있다고 할 때, INSERT 작업은 성공하고 DELETE 작업은 실패하는 경우가 발생할 수 있습니다. 이때 auto commit을 비활성화시키지 않으면 INSERT 작업이 자동으로 commit 처리되기 때문에, 의도치 않은 상황이 발생할 수 있습니다. 이러한 경우 auto commit을 비활성화시키고 INSERT와 DELETE를 하나의 작업단위로 묶어서 모두 성공하면 commit 하도록 처리하는 것이 필요합니다. 즉, 하나의 작업단위 안에서 어느 하나라도 예외가 발생하면 작업단위 내 모든 작업을 rollback 하도록 하는 것입니다.

 

트랜잭션 처리의 과정은 다음과 같습니다.

 

  1. Connection 객체의 메소드인 setAutoCommit(false) 로 auto commit을 비활성화시킵니다.
  2. SQL문을 실행합니다.
  3. 실행이 정상적으로 완료되면 Connection 객체의 commit() 메소드로 commit 합니다.
  4. 실행 중 예외가 발생하면 catch 블록에서 rollback() 메소드를 이용해 rollback 합니다.
  5. 어느 경우라도 다시 auto commit을 활성화시켜야 하기 때문에, finally 블록에서 setAutoCommit(true) 로 auto commit을 활성화시킵니다.

 

(2) Main

 

메인에서 Service 클래스의 insertDelete 메소드를 호출하며 인자로 INSERT할 Dept 객체와 DELETE할 부서번호를 전달합니다.

service.insertDelete(new Dept(99, "영업", "제주"), 89);

 

 

(3) Service

 

Service 클래스에서는 DB에 접속한 후 얻은 Connection 객체를 이용해 auto commit을 비활성화한 후, DAO 클래스의 메소드들을 호출하고 있습니다. 모든 작업이 성공적으로 완료되면 con.commit()이 실행되겠지만, 어느 하나라도 예외가 발생하면 catch 블록으로 가서 con.rollback()이 실행됩니다. 어떤 경우라도 auto commit을 다시 원래대로 돌려놓아야 하기 때문에 finally 블록에서 auto commit을 다시 활성화하고 있습니다. 

public void insertDelete(Dept dept, int deptno) {
	Connection con = null;
	ArrayList<Dept> list = null;
	try {
		con = DriverManager.getConnection(url, userid, passwd);
		con.setAutoCommit(false); //auto commit 중지
		dao.insert(con, dept); //insert 정상 실행
		dao.delete(con, deptno); //exception 발생 
		con.commit();		
	} catch (SQLException e) {
		if (con!=null) {
			try {
				con.rollback();
				System.out.println("insertDelete catch => Rollback");
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}
	} finally {
		try {
			con.setAutoCommit(true);
			con.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}	
}

 

 

(4) DAO

 

insert 메소드는 위와 동일하게 하고, delete 메소드를 다음과 같이 작성하여 예외를 발생시켜 보겠습니다.

public void delete(Connection con, int deptno) throws SQLException {
	PreparedStatement pstmt = null;
	try {
		String sql = "delete fro dept where deptno=?"; //예외 발생
		pstmt = con.prepareStatement(sql);
		pstmt.setInt(1, deptno);
		int num = pstmt.executeUpdate();
		System.out.println("삭제된 레코드 개수: "+num);
	} finally {
		try {
			if(pstmt!=null) pstmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

 

실행 결과가 다음과 같이 출력됩니다. 확인을 위해 select 메소드를 이용해 모든 레코드 정보를 출력하고 있습니다. 먼저 호출된 insert 메소드는 정상적으로 수행 완료되어 insert 실행 결과 1이 출력되고 있습니다. 그러나 delete 메소드 내에서 예외가 발생하여 이후 코드가 수행되지 않고 catch 블록에서 rollback()이 실행되었습니다. 이에 따라 dept 테이블 내에 추가하고자 했던 99번 부서가 추가되지 않은 것을 확인할 수 있습니다.

 

 

 

이상으로 JDBC 프로그램을 개발할 때 사용하는 DAO, DTO 패턴의 기본 개념과 프로그래밍 과정을 여러 경우로 나누어 알아보았습니다. 

 

 

'JAVA' 카테고리의 다른 글

[JAVA] MyBatis - HashMap 사용  (0) 2022.06.16
[JAVA] MyBatis 시작하기  (0) 2022.06.15
[JAVA] JDBC - PreparedStatement  (0) 2022.06.07
[JAVA] JDBC 기본 개념과 프로그래밍 단계  (0) 2022.06.06
[JAVA] 컬렉션 - Map 계열  (0) 2022.06.04

댓글