안녕하세요 코이킹입니다.
이번 포스트부터는 직접 게시판의 기능을 구현하는 과정에 대한 내용이 되겠습니다.
1. 목표
- 스프링 부트를 사용해서 웹 어플리케이션을 만드는 흐름에 대해서 이해할 수 있다.
- 스프링 부트를 사용해서 DB의 데이터를 웹에 표시하는 간단한 웹 어플리케이션을 만들 수 있다.
2. 어떻게 구현할지에 대한 설명
스프링 부트로 구현된 웹 어플리케이션은 주요 컴포넌트의 계층구조로 이루어진 경우가 많습니다.
아래의 주요 컴포넌트가 어떤 역할을 하는지의 가볍게 알고 구현하는 순서를 보신 후 바로 구현에 들어가도록 하겠습니다.
1) 주요 컴포넌트에 대해서
(1) 템플릿 | 유저가 보는 화면. 화면을 통해 컨트롤러로 요청을 보내고, 요청에대한 응답을 바탕으로 화면이 갱신되는등의 상호작용이 일어나는 계층. Thymeleaf를 사용하여 구현. |
(2-1) 엔티티(도메인) | DB의 테이블과 매핑되는 객체. |
(2-2) DTO | 각 계층간에 데이터를 주고받을 때 사용하는 객체. |
(3) 컨트롤러 | 화면에서 보내온 요청을 받아 유효성검사를 한 후, 유저로부터의 처리를 서비스 계층에 위임하여, 위임한 처리가 완료되면, 유저가 보는 화면에 응답을 보내주는 역할을 한다. |
(4) 서비스 | 컨트롤러(또는 다른 서비스 컴포넌트)에서 위임된 유저로부터의 요청을 처리해주는 역할을 하는 계층. |
(5) 리포지토리 | 데이터베이스와 같은 데이터 저장소에 접근하는 계층 |
2) 어플리케이션을 만드는 흐름
(1) → (2) → (3) → (4) → (5)
저의 경우 코드의 변화에 따라 화면이 어떻게 변하는지 최대한 빠르게 확인이 가능하도록 개발하는 것을 선호하기에 위와 같은 순서로 구현하고 있습니다.
일단 화면을 만들어야 어떤 데이터들을 사용해서 비지니스 로직을 처리해야 할지 느낌이 오기 때문입니다.
화면을 만든 후에는 DB와 계층 간에 데이터를 주고받는 데 사용할 엔티티와 DTO를 만듭니다.
그 후 컨트롤러, 서비스, 리포지토리를 구현합니다.
컨트롤러부터 구현하는 이유는 컨트롤러를 구현하고 프로젝트를 실행하면 화면과 상호작용을 할 수 있는 어플리케이션으로서 동작이 가능하기에 화면의 변화를 테스트하면서 개발이 가능하기 때문입니다.
3. 소스코드와 해석
1) 템플릿 : /template-springboot/src/main/resources/templates/board/list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, inital-scale=1.0"/>
</head>
<body>
<div>
<hr/>
<div>
<table class="table" border="1">
<thead class="table-dark">
<tr>
<th>No</th>
<th>Contents Type</th>
<th>Title</th>
<th>CreatedTime</th>
<th>UpdatedTime</th>
<th>Likes</th>
</tr>
</thead>
<tbody>
<tr th:each="board: ${boardList}">
<td th:text="${board.boardNo}"></td>
<td th:text="${board.type}==1 ? 'Normal' : 'MemberShip'"></td>
<td th:text="'[' + ${board.title} + ']'"></td>
<td th:text="${board.createdTime}"></td>
<td th:text="${board.updatedTime}"></td>
<td th:text="${board.likes}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
- 2행 : 타임리프를 사용하겠다는 선언
- 5행 : 모바일 등 화면의 크기가 변하는 각각의 장치를 인식해서 페이지를 표시하도록 하겠다는 선언
- 24행 : 타임리프에서의 반복문 사용. 'boardList'라는 리스트 데이터에서 데이터를 하나씩 꺼내와 리스트의 데이터수만큼 반복. 하나씩 꺼내오는 데이터를 'board'로 선언하여 사용
- 25~30행 : board데이터의 필드를 출력
- 26행 : 삼항 연산자를 사용하여 조건부 데이터 출력
- 27행 : 문자열 데이터 더하기, 대괄호 문자를 ('[', ']' ) board의 필드 데이터와 결합해서 출력
2) 엔티티(도메인) : /template-springboot/src/main/java/com/sb/template/entity/Board.java
package com.sb.template.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.AllArgsConstructor;
@Entity
@Table(name = "board")
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "board_no")
private Integer boardNo;
@Column(name = "type")
private Integer type;
@Column(name = "title")
private String title;
@Column(name = "contents")
private String contents;
@Column(name = "member_id")
private String memberId;
@Column(name = "created_time")
private Date createdTime;
@Column(name = "updated_time")
private Date updatedTime;
@Column(name = "likes")
private Integer likes;
@Column(name = "dis_likes")
private Integer dislikes;
}
- 17행 : 이 클래스가 엔티티라고 선언해주는 어노테이션
- 18행 : 데이터 데이스의 테이블과 매칭 해주는 어노테이션
- 19행 : 클래스의 필드 값을 가져오고, 설정하는 getter/setter 메서드를 자동생성
- 20행 : 멤버 필드를 초기화하지 않는 생성자를 자동생성
- 21행 : 모든 멤버 필드를 초기화하는 생성자를 자동생성
- 22행 : 모든 멤버 필드 값을 출력하는 toString메서드를 자동생성
- 25행 : 클래스상에 기본키를 명시하는 어노테이션
- 26행 : 자동으로 값을 생성하도록 명시하는 어노테이션. strategy를 위의 코드와 같이 설정하면, auto increment 칼럼으로 인식하게 됨(기존 값에 +1).
위의 설정은 MySQL/MariaDB에서 사용하기 위한 설정으로 오라클의 경우 @SequenceGenerator, @GeneratedValue의 strategy값을 SEQUENCE로 설정한다.
- 27행 : 데이터 베이스의 테이블과 1:1로 매칭 하게 설정해주는 어노테이션
※ 테이블 정보
첨부한 board.sql을 phpMyAdmin에서 export 하시면 테스트용 데이터를 세팅할 수 있습니다.
3) 컨트롤러 : /template-springboot/src/main/java/com/sb/template/controller/BoardController.java
package com.sb.template.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.sb.template.entity.Board;
import com.sb.template.service.BoardService;
@Controller
@RequestMapping(path = "/board")
public class BoardController {
@Autowired
private BoardService boardService;
@RequestMapping(method = RequestMethod.GET, value = {"list", "/", ""})
public String viewBoardList(Model model) {
List<Board> boardList = boardService.getAllBoard();
model.addAttribute("boardList", boardList);
return "board/list";
}
}
- 14행 : 이 클래스가 컨트롤러라는 것을 선언하는 어노테이션
- 15행 : URL의 경로를 설정하는 어노테이션, 이 클래스에 선언하면 클래스 안의 @ReqeustMapping어노테이션이 붙은 요청 처리 메서드의 URL 상위 경로에 'board'가 포함되게 함.
- 18행 : 스프링이 생성해준 서비스 컴포넌트를 의존성 주입하기 위한 어노테이션
- 21행 : 유저로부터의 요청을 처리하는 메서드에 선언해준다. HTTP메서드의 방식 중 GET 요청을 처리한다. value의 값으로 배열을 선언해주면 복수 패턴의 URL을 매핑할 수 있게 된다.
- 24~25행 : 서비스에 유저로부터의 요청에 대한 처리를 위임하여, 처리결과인 게시글 목록 리스트를 가져온다. 그 후 model객체에 데이터를 'boardList'라는 이름을 붙여 화면에 넘겨준다.
※ model.addAttribute를 사용할 경우 웹 페이지의 스코프 중 Request스코프에 데이터를 넘기게 됨.
- 27행 : 'board/list'에 해당하는 html템플릿을 지정하여 유저의 화면에 표시한다.
단순히 문자열만으로 템플릿을 지정할 수 있는 이유는 스프링 부트의 '뷰 리졸버'가 화면 처리를 해주기 때문,
application.properties 또는 application.yml에 prefix, suffix설정만 해주면 화면 표시는 별다른 설정을 해주지 않아도 된다.
4) 서비스 : /template-springboot/src/main/java/com/sb/template/service/BoardService.java
package com.sb.template.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sb.template.entity.Board;
import com.sb.template.repo.BoardRepository;
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
public List<Board> getAllBoard() {
List<Board> res = boardRepository.findAll();
if (res == null) return null;
return res;
}
}
- 11행 : 이 클래스가 서비스라는 것을 알려주는 어노테이션
- 14행 : 리포지토리 컴포넌트를 의존성 주입하기 위한 어노테이션
- 19~22행 : 리포지토리를 통해 DB로부터 글목록 데이터를 가져와, 데이터가 Null이 아니면, 데이터를 리턴한다.
5) 리포지토리 : /template-springboot/src/main/java/com/sb/template/repo/BoardRepository.java
package com.sb.template.repo;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.sb.template.entity.Board;
public interface BoardRepository extends JpaRepository<Board, Integer> {
public Optional<Board> findByBoardNo(Integer boardNo);
}
- 9행 : JpaRepository를 상속하면 @Repository어노테이션을 따로 붙이지 않아도 스프링이 알아서 이 인터페이스를 리포지토리로 등록해 준다.
- 11행 : BoradNo컬럼을 키로 하여 데이터를 가져오는 메서드. 'findBy컬럼명' 으로 메서드를 만들면 JPA가 자동으로 쿼리를 생성해준다. 'findBy컬럼명And컬럼명'과 같이 복수의 컬럼을 키로 하여 데이터를 가져오는 것도 가능.
6) 설정 파일 : /template-springboot/src/main/resources/application.yml
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
thymeleaf:
prefix : classpath:templates/
suffix : .html
cache : false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/boot_test?useSSL=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: 1234
hikari:
auto-commit: false
maximum-pool-size: 30
minimum-idle: 10
max-lifetime: 1200000
connection-timeout: 20000
idle-timeout: 300000
jpa:
open-in-view: true
hibernate:
ddl-auto: update #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
properties:
hibernate.format_sql: true
hibernate.jdbc.lob.non_contextual_creation: true
1~8행 : 서버 설정. 포트번호, 프로그램을 기동 했을 때의 최상위 패스의 설정 등
11~14 : 타임리프의 템플릿 매핑 설정
16~27 : DB 연동 설정, 상용 환경에서는 절대 루트 유저를 통해 DB에 접속하면 안 되며, 비번을 1234같이 간단하게 해선 안 된다.
29~28 : JPA설정. ddl-auto는 자동으로 테이블을 생성하고, 갱신할 것인지를 설정하는 것인데 create를 설정하게 되면 애플리케이션 기동시 DB의 데이터가 날아갈 수 있으므로 조심해서 사용해야 한다.
show-sql을 true로 설정하면 JPA가 생성하는 SQL을 확인할 수 있다.
4. 동작확인
5. 데이터 흐름
스프링 부트로 구현된 어플리케이션에서 유저로부터 요청이 있을 시의 데이터 처리 흐름은 기본적으로 아래의 그림과 같습니다.
소스코드는 필요할 때 가져다 사용하면 되지만 최소한 스프링 부트 어플리케이션의 데이터 흐름은 이해하고 넘어가시는 편이 앞으로 계속될 구현을 이해하시는데 도움이 될 것 같습니다.
흐름을 말로 간략히 표현하면 다음과 같습니다.
① 유저로부터 요청을 컨트롤러에서 수신합니다.
② 컨트롤러는 유저의 요청에 대한 처리(비지니스 로직)를 서비스에 위임합니다.
③ 서비스에서 유저의 요청에 대한 처리가 이루어집니다. 데이터베이스의 데이터가 필요한 경우라면 레포지토리에 데이터 처리를 위임합니다.
④ 레포지토리에서 DB에 접근하여 데이터 처리를 합니다.
⑤ 글목록 요청에 대해서는 DB의 데이터를 가져오는 처리가 수행됩니다.
⑥ 레포지토리는 서비스에 DB로부터 가져온 데이터를 리턴합니다.
⑦ 서비스에서 컨트롤러로 DB로부터 가져온 데이터를 리턴합니다.
⑧ 컨트롤러에서는 리퀘스트 스코프에 데이터를 담아 요청에 해당하는 타임리프 템플릿을 유저가 보는 브라우저로 리턴합니다.
6. 전체 소스코드
https://github.com/leeyoungseung/template-springboot/tree/feature/02_basic_board
글목록 구현은 이것으로 마치겠습니다.
다음 포스트는 글 작성 구현에 대한 내용이 되겠습니다.
'프로그래밍 > Springboot-토이프로젝트' 카테고리의 다른 글
【게시판-04】글상세보기 (0) | 2022.09.09 |
---|---|
【게시판-03】글작성 (1) | 2022.09.08 |
【게시판-번외02】설계 (0) | 2022.07.09 |
【게시판-번외01】요건정의 (0) | 2022.07.08 |
【게시판-01】프로젝트 생성 (0) | 2022.07.07 |
댓글