> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/web/back-end/spring-boot/spring-boot-jdbc/dao-pattern.md).

# DAO Pattern

이 문서에서는 DAO(Data Access Object)의 필요성에 대해서 설명한다

## DAO란

**DAO**는 <mark style="color:blue;">`Data Access Object`</mark>의 줄임말로 데이터베이스와 관련된 CRUD 작업을 처리하는 도구를 말한다. **DAO**를 사용하면 데이터베이스 작업을 기능별로 모듈화 할 수 있다.

## DB 정보

[Database 정보](/web/back-end/spring-boot/spring-boot-jdbc/database.md)를 기반으로 진행한다

## DAO 구조

Spring은 다음과 같은 특징을 가지고 있다.

* IoC - 개발자가 사용하고자 하는 객체는 Spring Container에 등록해서 사용해야 한다.
* DI - 등록된 객체들은 서로 주입하여 사용할 수 있다.

따라서 DAO를 Spring에 등록해야 하며, 사용하고자 하는 위치에 주입해야 한다.

## 프로젝트 생성

<div align="left"><figure><img src="https://4208234536-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_TNZwLuHV9ipYLbvRq%2Fuploads%2FIuLzSr0hqWEOYKpi601L%2Fimage.png?alt=media&amp;token=6198ebbf-6b6f-44ea-9103-a5591e35a8ea" alt=""><figcaption></figcaption></figure> <figure><img src="https://4208234536-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_TNZwLuHV9ipYLbvRq%2Fuploads%2Fja0rNcRwUAqxYsORFii1%2Fimage.png?alt=media&amp;token=3350662c-c4c8-4500-8e18-f89988e2fd3d" alt=""><figcaption></figcaption></figure></div>

## application.properties

JDBC 사용을 위해 <mark style="color:blue;">`application.propertiees`</mark> 파일에 다음과 같이 설정한다.

```properties
# 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
```

## DTO 생성

<mark style="color:blue;">`student`</mark>테이블의 데이터를 변환하기 위하여 <mark style="color:blue;">`StudentDto`</mark> 클래스를 생성한다.

{% code title="StudentDto.java" %}

```java
package com.hacademy.boot06.entity;

public class StudentDto {
    private int no;
    private String name;
    private int korean, english, math;
    public StudentDto() {}
    //setter, getter, toString()...
}
```

{% endcode %}

## DAO 생성

DAO는 PSA(Portable Service Abstraction) 구조로 생성한다. 즉, 인터페이스와 클래스로 분리하여 생성한다.

* StudentDao - 인터페이스로 생성하며 기능의 명세를 정의
* StudentDaoImpl - 클래스로 생성하며 인터페이스를 상속받아 기능의 실제 구현을 정의

이와 같이 구현하면 구현체를 교체하는 것이 수월해진다. 이 내용에 대해선 잠시 후에 다룬다.

```java
package com.hacademy.boot06.repository;

public interface StudentDao {
    //추상 메소드(기능 명세) 정의
}
```

```java
package com.hacademy.boot06.repository;

import org.springframework.stereotype.Repository;

@Repository
public class StudentDaoImpl implements StudentDao{
    //기능 명세에 대한 실제 구현 코드
}
```

<mark style="color:blue;">`StudentDaoImpl`</mark>의 상단에 <mark style="color:red;">`@Repository`</mark> 애노테이션이 작성되어 있으며, 이로 인하여 구현체인 <mark style="color:blue;">`StudentDaoImpl`</mark>이 **Spring Container**에 등록된다. <mark style="color:blue;">`StudentDao`</mark>는 인터페이스므로 등록할 수 없다.

등록된 객체간에 **의존성 주입(DI)**&#xC774; 가능하므로 다음과 같이 구현체 클래스에 <mark style="color:blue;">`JdbcTemplate`</mark>을 사용하도록 선언할 수 있다.

```java
@Autowired
private JdbcTemplate jdbcTemplate;
```

## Controller 생성

Student와 관련된 처리를 수행하는 <mark style="color:blue;">`StudentController`</mark>를 생성한다.&#x20;

```java
package com.hacademy.boot06.controller;

import org.springframework.stereotype.Controller;

@Controller
public class StudentController {

}
```

Database 관련 처리를 위하여 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스를 주입하여야 한다. 변수 선언 방법은 두 가지가 있다.

### 클래스 구현체 변수 생성

```java
@Autowired
private StudentDaoImpl studentDao;
```

컨트롤러에 위와 같이 변수를 선언할 경우 주입은 정상적으로 이루어지나 다음과 같은 문제가 발생한다.

* 추후 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스를 다른 클래스로 교체할 경우 해당 변수를 수정해야 한다.

웹 서비스의 특성상 운영기간이 길고 이에 따른 환경변화가 있을 수 있기 때문에 해당 클래스를 직접 주입하는 경우 결합도가 강해져서 확장성이 떨어진다.

<div align="left"><figure><img src="https://4208234536-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_TNZwLuHV9ipYLbvRq%2Fuploads%2F0NloMzgpTJti2XDPC5A7%2Fimage.png?alt=media&amp;token=60283f1d-7581-4abb-8428-9e79f0c6fcea" alt=""><figcaption></figcaption></figure></div>

### 추상 인터페이스 변수 생성

```java
@Autowired
private StudentDao studentDao;
```

컨트롤러에 인터페이스 형태로 변수를 선언할 경우 <mark style="color:red;">`@Autowired`</mark> 애노테이션이 해당 타입과 상속받은 타입에 대한 스캔을 하여 가능한 객체를 주입한다. 따라서 등록된 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 객체가 주입된다.

<div align="left"><figure><img src="https://4208234536-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_TNZwLuHV9ipYLbvRq%2Fuploads%2FtSQDro3bSN3FbvkJInGQ%2Fimage.png?alt=media&amp;token=9dfdbb55-e3da-45e3-99e6-926ec76a307c" alt=""><figcaption></figcaption></figure></div>

## StudentDao 메소드

Database 작업을 처리하는 StudentDao에는 기본적으로 다음과 같은 메소드가 포함된다. 필요하다면 다른 메소드를 더 추가하여 사용할 수 있다.

```java
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 구문은 다음과 같다.

```sql
INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH)
VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)
```

구문에 시퀀스를 제외한 이름, 국어점수, 수학점수, 영어점수를 채워야 하므로 다음과 같이 메소드를 구성할 수 있다.

```java
void insert(String name, int korean, int english, int math);
```

하지만 name, korean, english, math는 StudentDto의 필드이므로 StudentDto 객체 형태로 선언하면 한 번의 선언으로 데이터를 묶어서 전달받을 수 있다.

```java
void insert(StudentDto studentDto);
```

처리 코드가 위치하는 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 구현 코드는 다음과 같다.

<pre class="language-java"><code class="lang-java">void insert(StudentDto studentDto){
    String sql = "INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH) "
                + "VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)";
    Object[] param = {
            studentDto.getName(), studentDto.getKorean(), 
<strong>            studentDto.getEnglish(), studentDto.getMath()
</strong><strong>    };
</strong>    jdbcTemplate.update(sql, param);
}
</code></pre>

### 수정 메소드

```sql
UPDATE STUDENT SET NAME=?, KOREAN=?, ENGLISH=?, MATH=? WHERE NO=?
```

특정 대상을 수정하려면 Primary key인 번호(no)가 필요하다. 따라서 번호를 제외한 다른 정보 중 원하는 것을 수정할 수 있어야 하며, 하나만 만들 예정이므로 번호를 제외한 모든 정보가 변경되도록 처리한다. 데이터는 번호, 이름, 국어점수, 수학점수, 영어점수 총 5개가 필요하다. 번호가 존재하지 않을 경우 실행이 되어도 수정된 데이터가 없을 수 있으므로 반환형을 <mark style="color:red;">`boolean`</mark>으로 설정하여 성공/실패 여부를 반환할 수 있도록 구현한다.

객체를 사용하지 않을 경우의 메소드 형태는 다음과 같다.

```java
boolean update(int no, String name, int korean, int english, int math);
```

객체를 사용할 경우의 메소드 형태는 다음과 같으며, 형태가 간결하여 사용을 권장한다.

```java
boolean update(StudentDto studentDto);
```

처리 코드가 위치하는 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 구현 코드는 다음과 같다.

```java
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가 필요하다.&#x20;

```sql
DELETE STUDENT WHERE NO = ?
```

Unique 항목으로도 가능하지만 현재 테이블은 Primary key인 번호(no)를 제외한 다른 Unique 항목이 없으므로 Primary key를 사용한다. 번호에 따라 대상이 존재하지 않아 삭제가 이루어지지 않을 수 있으므로 반환형을 <mark style="color:red;">`boolean`</mark>으로 설정하고 성공/실패 여부를 반환하도록 설정한다.

메소드의 형태는 다음과 같다.

```java
boolean delete(int no);
```

처리 코드가 위치하는 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 구현 코드는 다음과 같다.

```java
public boolean delete(int no) {
    String sql = "DELETE STUDENT WHERE NO = ?";
    Object[] param = {no};
    return jdbcTemplate.update(sql, param) > 0;
}
```

### 조회 메소드

```sql
SELECT * FROM STUDENT
```

전체 조회를 하는 경우는 드물지만 구현한다면 위의 SQL 구문과 같다. 목록의 경우 정렬이 반드시 들어가는것이 좋기 때문에 Primary key인 번호(no)를 이용하여 다음과 같이 정렬 후 조회할 수 있다.

```sql
SELECT * FROM STUDENT ORDER BY NO ASC
```

데이터베이스 조회 결과(<mark style="color:red;">`ResultSet`</mark>)을 List로 변환하기 위해서는 <mark style="color:red;">`RowMapper<T>`</mark> 객체가 필요하다.

```java
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 타입이 반환되도록 다음과 같이 구성한다. 홀더가 없으므로 매개변수는 작성하지 않는다.

```java
List<StudentDto> selectList();
```

처리 코드가 위치하는 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 구현 코드는 다음과 같다.

```java
@Override
public List<StudentDto> selectList() {
    String sql = "SELECT * FROM STUDENT ORDER BY NO ASC";
    return jdbcTemplate.query(sql, mapper);
}
```

### 상세 메소드

상세 조회는 Primary key를 이용하여 **단 하나의 데이터를 조회**한다.

```sql
SELECT * FROM STUDENT WHERE NO = ?
```

따라서 결과는 없거나 혹은 한 개가 나오며, 이에 대한 자료형은 <mark style="color:blue;">`StudentDto`</mark>이다. 따라서 메소드의 형태는 다음과 같다.

```java
StudentDto selectOne(int no);
```

그리고 <mark style="color:blue;">`RowMapper`</mark>처럼 <mark style="color:blue;">`ResultSet`</mark>의 데이터 유무에 따라 <mark style="color:blue;">`StudentDto`</mark>로 변환해줄 <mark style="color:blue;">`ResultSetExtractor<StudentDto>`</mark> 객체가 필요하다.

```java
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;
        }
    }
};
```

처리 코드가 위치하는 <mark style="color:blue;">`StudentDaoImpl`</mark> 클래스의 구현 코드는 다음과 같다.

```java
public StudentDto selectOne(int no) {
    String sql = "SELECT * FROM STUDENT WHERE NO = ?";
    Object[] param = {no};
    return jdbcTemplate.query(sql, extractor, param);
}
```
