1. 3계층 구조(3 Layer Architecture)
이전에 생성했던 Memo의 경우 Controller를 통해 데이터 송/수신 및 데이터 처리가 모두 이루어진다. 이 프로젝트의 경우 구조가 그리 복잡하지 않기에 알아보기 쉽지만, 이것들이 복잡해질 경우 알아보기 점점 힘들어진다는 단점이 있다. 이런 문제를 해결하기 위해, Controller, Service, Repository 3가지의 처리 역할을 통해 각 역할을 분리하였다.
Controller : 클라이언트의 요청을 받는 역할이다. 요청에 대한 처리는 Service에 일임하고, 받아온 request 데이터가 있다면 함께 넘겨주며, Service에서 처리된 데이터를 클라이언트에 넘겨주는 역할을 한다.
Service : 사용자의 요구 사항을 처리하는 가장 중요한 역할이다. db에 접근할 필요가 있을 경우 Repository에게 요청하며, 현업에서는 가장 중요한 역할인 만큼 서비스 코드가 점점 비대해 지고 있다고 한다.
Repository : db 관리(연결, 해제, 자원 관리)를 담당하며, db CRUD를 담당한다.
이를 통해 Memo의 Controller에서 Service, Repository를 분리해 주었다.
분리한 코드는 아래와 같다.
Controller
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.createMemo(requestDto);
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.deleteMemo(id);
}
requestDto를 받고 이를 Service에 넘겨준다. jdbcTemplate도 함께 넘겨주어 활용할 수 있게 하고, requestDto를 받아오고 생성된 데이터를 출력하는 것 외의 모든 데이터 처리는 Service에게 일임한다.
Service
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo saveMemo = memoRepository.save(memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
public List<MemoResponseDto> getMemos() {
// DB 조회
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
return memoRepository.findAll();
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo memo = memoRepository.findById(id);
// 해당 메모가 DB에 존재하는지 확인
if (memo != null) {
// memo 내용 수정
memoRepository.update(id, requestDto);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo memo = memoRepository.findById(id);
if (memo != null) {
// memo 삭제
memoRepository.delete(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
기존의 Controller에서 데이터를 처리하는 코드들이 전부 Service쪽으로 옮겨졌다. db에 접근하는 것은 Repository에 일임하고, 이 데이터에 대한 처리는 모두 Service에서 담당한 후, Controller로 반환한다.
Repository
public Memo save(Memo memo) {
// db 저장
KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체
String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
jdbcTemplate.update(con -> {
PreparedStatement preparedStatement = con.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, memo.getUsername());
preparedStatement.setString(2, memo.getContents());
return preparedStatement;
},
keyHolder);
// DB Insert 후 받아온 기본키 확인
Long id = keyHolder.getKey().longValue();
memo.setId(id);
return memo;
}
public List<MemoResponseDto> findAll() {
String sql = "SELECT * FROM memo";
return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
@Override
public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
// SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
Long id = rs.getLong("id");
String username = rs.getString("username");
String contents = rs.getString("contents");
return new MemoResponseDto(id, username, contents);
}
});
}
public void update(Long id, MemoRequestDto requestDto) {
String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);
}
public void delete(Long id) {
String sql = "DELETE FROM memo WHERE id = ?";
jdbcTemplate.update(sql, id);
}
public Memo findById(Long id) {
// DB 조회
String sql = "SELECT * FROM memo WHERE id = ?";
return jdbcTemplate.query(sql, resultSet -> {
if (resultSet.next()) {
Memo memo = new Memo();
memo.setUsername(resultSet.getString("username"));
memo.setContents(resultSet.getString("contents"));
return memo;
} else {
return null;
}
}, id);
}
이전에 Controller 부분에서 sql 콘솔 명령어 부분만을 넘겨준 것으로, 그 외의 작업은 일체 하지 않는다.
Controller, Service, Repository 세 객체 모두 JdbcTemplate 필드를 보유하고 있다. 그런데 실질적으로 jdbcTemplate는 Repository를 제외하면 사용할 일이 없다. 그렇기에 Controller와 Service는 이 필드를 갖고 있을 이유는 없지만, 데이터 처리 요청 시에 사용하고 있다. 이를 해결할 방법은 없을까?
답은 간단하다. Controller클래스에는 Service 필드를, Service 클래스에는 Repository 필드를 보유한 후, jdbcTemplate를 입력받는 호출 메소드를 각각의 필드에서 호출하면 된다.
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
this.memoService = new MemoService(jdbcTemplate);
}
Controller는 Service를 보유하고, jdbcTemplate의 호출 메소드는 Service에 일임한다.
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
Service도 동일하게 Repository에 일임한다. Repository는 db와 통신할 때 실제로 jdbcTemplate를 활용하기에 필드와 메소드 변경이 필요하지 않다.