DAO Pattern
이 문서에서는 DAO(Data Access Object)의 필요성에 대해서 설명한다
Last updated
이 문서에서는 DAO(Data Access Object)의 필요성에 대해서 설명한다
Last updated
DAO는 Data Access Object
의 줄임말로 데이터베이스와 관련된 CRUD 작업을 처리하는 도구를 말한다. DAO를 사용하면 데이터베이스 작업을 기능별로 모듈화 할 수 있다.
Database 정보를 기반으로 진행한다
Spring은 다음과 같은 특징을 가지고 있다.
IoC - 개발자가 사용하고자 하는 객체는 Spring Container에 등록해서 사용해야 한다.
DI - 등록된 객체들은 서로 주입하여 사용할 수 있다.
따라서 DAO를 Spring에 등록해야 하며, 사용하고자 하는 위치에 주입해야 한다.
JDBC 사용을 위해 application.propertiees
파일에 다음과 같이 설정한다.
# jdbc
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=hacademy
spring.datasource.password=hacademy
# actuator
management.endpoints.web.exposure.include=mappings,beans,env
student
테이블의 데이터를 변환하기 위하여 StudentDto
클래스를 생성한다.
package com.hacademy.boot06.entity;
public class StudentDto {
private int no;
private String name;
private int korean, english, math;
public StudentDto() {}
//setter, getter, toString()...
}
DAO는 PSA(Portable Service Abstraction) 구조로 생성한다. 즉, 인터페이스와 클래스로 분리하여 생성한다.
StudentDao - 인터페이스로 생성하며 기능의 명세를 정의
StudentDaoImpl - 클래스로 생성하며 인터페이스를 상속받아 기능의 실제 구현을 정의
이와 같이 구현하면 구현체를 교체하는 것이 수월해진다. 이 내용에 대해선 잠시 후에 다룬다.
package com.hacademy.boot06.repository;
public interface StudentDao {
//추상 메소드(기능 명세) 정의
}
package com.hacademy.boot06.repository;
import org.springframework.stereotype.Repository;
@Repository
public class StudentDaoImpl implements StudentDao{
//기능 명세에 대한 실제 구현 코드
}
StudentDaoImpl
의 상단에 @Repository
애노테이션이 작성되어 있으며, 이로 인하여 구현체인 StudentDaoImpl
이 Spring Container에 등록된다. StudentDao
는 인터페이스므로 등록할 수 없다.
등록된 객체간에 의존성 주입(DI)이 가능하므로 다음과 같이 구현체 클래스에 JdbcTemplate
을 사용하도록 선언할 수 있다.
@Autowired
private JdbcTemplate jdbcTemplate;
Student와 관련된 처리를 수행하는 StudentController
를 생성한다.
package com.hacademy.boot06.controller;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
}
Database 관련 처리를 위하여 StudentDaoImpl
클래스를 주입하여야 한다. 변수 선언 방법은 두 가지가 있다.
@Autowired
private StudentDaoImpl studentDao;
컨트롤러에 위와 같이 변수를 선언할 경우 주입은 정상적으로 이루어지나 다음과 같은 문제가 발생한다.
추후 StudentDaoImpl
클래스를 다른 클래스로 교체할 경우 해당 변수를 수정해야 한다.
웹 서비스의 특성상 운영기간이 길고 이에 따른 환경변화가 있을 수 있기 때문에 해당 클래스를 직접 주입하는 경우 결합도가 강해져서 확장성이 떨어진다.
@Autowired
private StudentDao studentDao;
컨트롤러에 인터페이스 형태로 변수를 선언할 경우 @Autowired
애노테이션이 해당 타입과 상속받은 타입에 대한 스캔을 하여 가능한 객체를 주입한다. 따라서 등록된 StudentDaoImpl
클래스의 객체가 주입된다.
Database 작업을 처리하는 StudentDao에는 기본적으로 다음과 같은 메소드가 포함된다. 필요하다면 다른 메소드를 더 추가하여 사용할 수 있다.
public interface StudentDao {
void insert(StudentDto studentDto);//등록(C)
boolean update(StudentDto studentDto);//수정(U)
boolean delete(int no);//삭제(D)
List<StudentDto> selectList();//목록(R)
StudentDto selectOne(int no);//상세(R)
}
등록을 위한 SQL 구문은 다음과 같다.
INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH)
VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)
구문에 시퀀스를 제외한 이름, 국어점수, 수학점수, 영어점수를 채워야 하므로 다음과 같이 메소드를 구성할 수 있다.
void insert(String name, int korean, int english, int math);
하지만 name, korean, english, math는 StudentDto의 필드이므로 StudentDto 객체 형태로 선언하면 한 번의 선언으로 데이터를 묶어서 전달받을 수 있다.
void insert(StudentDto studentDto);
처리 코드가 위치하는 StudentDaoImpl
클래스의 구현 코드는 다음과 같다.
void insert(StudentDto studentDto){
String sql = "INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH) "
+ "VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)";
Object[] param = {
studentDto.getName(), studentDto.getKorean(),
studentDto.getEnglish(), studentDto.getMath()
};
jdbcTemplate.update(sql, param);
}
UPDATE STUDENT SET NAME=?, KOREAN=?, ENGLISH=?, MATH=? WHERE NO=?
특정 대상을 수정하려면 Primary key인 번호(no)가 필요하다. 따라서 번호를 제외한 다른 정보 중 원하는 것을 수정할 수 있어야 하며, 하나만 만들 예정이므로 번호를 제외한 모든 정보가 변경되도록 처리한다. 데이터는 번호, 이름, 국어점수, 수학점수, 영어점수 총 5개가 필요하다. 번호가 존재하지 않을 경우 실행이 되어도 수정된 데이터가 없을 수 있으므로 반환형을 boolean
으로 설정하여 성공/실패 여부를 반환할 수 있도록 구현한다.
객체를 사용하지 않을 경우의 메소드 형태는 다음과 같다.
boolean update(int no, String name, int korean, int english, int math);
객체를 사용할 경우의 메소드 형태는 다음과 같으며, 형태가 간결하여 사용을 권장한다.
boolean update(StudentDto studentDto);
처리 코드가 위치하는 StudentDaoImpl
클래스의 구현 코드는 다음과 같다.
public boolean update(StudentDto studentDto) {
String sql = "UPDATE STUDENT "
+"SET NAME=?, KOREAN=?, ENGLISH=?, MATH=? "
+"WHERE NO=?";
Object[] param = {
studentDto.getName(), studentDto.getKorean(),
studentDto.getEnglish(), studentDto.getMath(),
studentDto.getNo()
};
return jdbcTemplate.update(sql, param) > 0;
}
특정 대상을 삭제하려면 Primary key가 필요하다.
DELETE STUDENT WHERE NO = ?
Unique 항목으로도 가능하지만 현재 테이블은 Primary key인 번호(no)를 제외한 다른 Unique 항목이 없으므로 Primary key를 사용한다. 번호에 따라 대상이 존재하지 않아 삭제가 이루어지지 않을 수 있으므로 반환형을 boolean
으로 설정하고 성공/실패 여부를 반환하도록 설정한다.
메소드의 형태는 다음과 같다.
boolean delete(int no);
처리 코드가 위치하는 StudentDaoImpl
클래스의 구현 코드는 다음과 같다.
public boolean delete(int no) {
String sql = "DELETE STUDENT WHERE NO = ?";
Object[] param = {no};
return jdbcTemplate.update(sql, param) > 0;
}
SELECT * FROM STUDENT
전체 조회를 하는 경우는 드물지만 구현한다면 위의 SQL 구문과 같다. 목록의 경우 정렬이 반드시 들어가는것이 좋기 때문에 Primary key인 번호(no)를 이용하여 다음과 같이 정렬 후 조회할 수 있다.
SELECT * FROM STUDENT ORDER BY NO ASC
데이터베이스 조회 결과(ResultSet
)을 List로 변환하기 위해서는 RowMapper<T>
객체가 필요하다.
private RowMapper<StudentDto> mapper = new RowMapper<StudentDto>() {
@Override
public StudentDto mapRow(ResultSet rs, int rowNum) throws SQLException {
StudentDto studentDto = new StudentDto();
studentDto.setNo(rs.getInt("no"));
studentDto.setName(rs.getString("name"));
studentDto.setKorean(rs.getInt("korean"));
studentDto.setEnglish(rs.getInt("english"));
studentDto.setMath(rs.getInt("math"));
return studentDto;
}
};
메소드는 List 타입이 반환되도록 다음과 같이 구성한다. 홀더가 없으므로 매개변수는 작성하지 않는다.
List<StudentDto> selectList();
처리 코드가 위치하는 StudentDaoImpl
클래스의 구현 코드는 다음과 같다.
@Override
public List<StudentDto> selectList() {
String sql = "SELECT * FROM STUDENT ORDER BY NO ASC";
return jdbcTemplate.query(sql, mapper);
}
상세 조회는 Primary key를 이용하여 단 하나의 데이터를 조회한다.
SELECT * FROM STUDENT WHERE NO = ?
따라서 결과는 없거나 혹은 한 개가 나오며, 이에 대한 자료형은 StudentDto
이다. 따라서 메소드의 형태는 다음과 같다.
StudentDto selectOne(int no);
그리고 RowMapper
처럼 ResultSet
의 데이터 유무에 따라 StudentDto
로 변환해줄 ResultSetExtractor<StudentDto>
객체가 필요하다.
private ResultSetExtractor<StudentDto> extractor = new ResultSetExtractor<StudentDto>() {
@Override
public StudentDto extractData(ResultSet rs) throws SQLException, DataAccessException {
if(rs.next()) {
StudentDto studentDto = new StudentDto();
studentDto.setNo(rs.getInt("no"));
studentDto.setName(rs.getString("name"));
studentDto.setKorean(rs.getInt("korean"));
studentDto.setEnglish(rs.getInt("english"));
studentDto.setMath(rs.getInt("math"));
return studentDto;
}
else {
return null;
}
}
};
처리 코드가 위치하는 StudentDaoImpl
클래스의 구현 코드는 다음과 같다.
public StudentDto selectOne(int no) {
String sql = "SELECT * FROM STUDENT WHERE NO = ?";
Object[] param = {no};
return jdbcTemplate.query(sql, extractor, param);
}