2022. 8. 15. 15:20ㆍHow to become a real programmer
게시판을 만드는 과정은 아래 7가지 과정으로 축약될 수 있었다.
1) 개발환경 세팅
- IntelliJ Community 설치
- MariaDB 설치
- MySQL Workbench 설치
2) 프로젝트 생성
- IntelliJ Community에서 Spring Boot 프로젝트 생성
- MariaDB Database 스키마 생성
3) 게시물 작성
- MariaDB Database에 'Board' 테이블 생성
- 게시물 작성 폼 생성
- 게시물 작성 처리
4) 게시물 리스팅
- 게시물 리스트 페이지 생성
- 게시물 리스트 페이지에 저장된 게시글 출력
5) 게시물 삭제
- 게시물 삭제 버튼 생성
- 게시물 삭제 처리
6) 게시물 수정
- 게시물 수정 페이지 생성
- 게시물 수정 처리
7) 게시물 페이징
- 게시물 페이징 처리
- 게시물 리스트 페이지에서 페이징 처리
위 과정에서 Controller, Repository, Entity, Service 등의 파일들은 어떤 역할을 하는 것일까.
아래 블로거 분들의 글을 참고하여 설명해보도록 하겠다.
https://whitepro.tistory.com/265
스프링 부트 : 기본 개념 1) Entity, Repository 개념
개인적인 정리와 기록을 위한 글이다. 혹시라도 참고해주시는 분들께서는 정확하지 않은 내용이 많으니 유의해주시길 바란다. 자바 진영의 백엔드 개발을 아무런 지식없이 시작하게 되었다. 현
whitepro.tistory.com
https://goddaehee.tistory.com/203
[스프링부트 (2)] SpringMVC(1) Controller 생성하기
[스프링부트 (2)] SpringMVC(1) Controller 생성하기 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ 스프링 부트 Controller ] 입니다. : ) 아주 간단히 Controller에 대해 정리도 하려고 하니, 실제 스프링..
goddaehee.tistory.com
Application.java?
스프링 부트 프로젝트를 생성할 때 처음부터 탑재되어 있었던 BoardApplication.java 파일은, 만든 서비스를 실행하는 파일이라고 보면 되겠다.
BoardApplication.java 파일 내부를 보면 쉽게 알 수 있다.
ex. BoardApplication.java
package com.study.board;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BoardApplication {
public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
}
SpringApplication.run이라는 명령어가 BoardApplication을 실행한다는 점을 확인할 수 있다.
Controller.java?
컨트롤러는 말 그대로 컨트롤러라고 이해하면 되겠다. 우리가 어떠한 것들을 제어할 때 컨트롤 한다고 하는데, 웹 서비스를 컨트롤 시킬 수 있게(매핑, 선언, 연결 등) 하는 역할이다.
컨트롤러를 잘 이해하려면 MVC 패턴에 대한 이해가 필요하다.
MVC?(Model View Controller)
말 그대로 Model과 View, Controller로 이루어져 서로 상호작용하는 패턴을 의미한다.
여기서,
Model은 데이터를 생성, 표시, 저장, 변경하는 부분을 의미하고
View는 사용자가 실제 보게되는 인터페이스를 의미하며
Controller는 Model과 View를 제어하여 둘의 상호작용을 이루게하는 역할을 한다.
즉, Controller를 통해서 Model에서 데이터를 받아오고 사용자에게 View를 통해서 보여주는 패턴이 MVC 패턴이다.
우리가 만든 Controller.java 파일은 여기서 Controller의 역할을 맡게된다. Controller파일은 중추적인 역할로서 한 파일이 수행할 수 있다. 나머지 Model, View 역할을 하는 파일은 아래 더 자세히 설명하겠지만 각각 Entity나 template 폴더 내 html파일들이 View 역할을 한다고 생각하면 되겠다.
이전에 공공API를 활용한 국민연금사업자 조회 서비스 개발(https://minddokddok.tistory.com/15) 에서는 Flask를 통해 웹서비스를 구성했는데 이때는 app.py라는 파일이 일종의 Controller 역할을 하고, html파일들이 View역할을, DB가 Model 역할을 한다. 스프링 부트에서도 작업이 MVC 패턴으로 분업화 되어있어 여러 사람들이 참여하는 프로젝트에서의 작업이나 수정과정에서의 모호성이 줄어들게 된다.(각각 무엇을 역할하는지 분명히 드러나기 때문에)
Controller에서 사용하는 어노테이션으로는,
@Controller : 해당 파일이 Controller 역할을 함을 알리는 Annotaiton
@RequestMapping : HTTP 요청 타입(대표적 Get, Post)에 따라 매핑시켜주기 위한 어노테이션,
각 타입은 method = ""로 지정해줄 수 있다
ex.
Mapping controller methods with @RequestMapping
@RequestMapping(value = "/users", method = RequestMethod.GET)
public Users getUsers() {
}
@RequestMapping(value = "/users", method = RequestMethod.POST)
public User createUser(User user) {
}
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable("id") String id) {
}
@GetMapping : @RequestMapping의 세분화된 버전으로,
@RequestMapping(method = RequestMethod.GET)
을 대신한다고 생각하면 된다. HTTP의 GET 요청 방식을 수행하는 매핑방식이다.
@PostMapping : 마찬가지로 @RequestMapping의 세분화된 버전으로,
@RequestMapping(method = RequestMethod.POST)
를 대신한다고 생각하면 된다. HTTP의 POST 요청 방식을 수행하는 매핑방식이다.
ex. BoardController.java
package com.study.board.controller;
import com.study.board.entity.Board;
import com.study.board.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
@GetMapping("/board/write") //localhost:8080/board/write
public String boardWriteForm() {
return "boardwrite";
}
@PostMapping("/board/writedo")
public String boardWriteDo(Board board, Model model, MultipartFile file) throws Exception{
boardService.write(board, file);
model.addAttribute("message", "글 작성이 완료되었습니다.");
model.addAttribute("searchUrl", "/board/list");
return "message";
}
@GetMapping("/board/list")
public String boardList(Model model,
@PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable,
String searchKeyword){ // 데이터를 담아 페이지로 보내기 위해 Model 자료형을 인자로 , 검색할 때 (searchKeyword가 있을 떄) 안할 때 구분해 if문 사용
Page<Board> list = null;
if(searchKeyword != null){
list = boardService.boardSearchList(searchKeyword, pageable);
} else {
list = boardService.boardlist(pageable);
}
int nowPage = list.getPageable().getPageNumber() + 1; // 현재 페이지를 가져옴 , 0에서 시작하기에 처리를 위해 + 1
int startPage = Math.max(nowPage - 4, 1); // Math.max(a, b) -- a 와 b 중 큰 값을 반환 --> 그냥 nowPAge - 4만 하면 nowpage가 1인 경우 -3도 가능하기에 이를 방지하기 위함
int endPage = Math.min(nowPage + 5, list.getTotalPages()); // totalPage보다 크면 안되기에 두개 중 최소값 반환하는 Math.min을 사용
model.addAttribute("list", list ); // boardService에서 생성한 boardlist메소드를 통해 list가 반환되는데 해당 list를 "list"라는 이름으로 넘겨주겠다는 것(html에 나올 수 있게)
model.addAttribute("nowPage", nowPage);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "boardlist";
}
@GetMapping("/board/view") // localhost:8080/board/view?id=1
public String boardView(Model model, Integer id) {
model.addAttribute("board", boardService.boardView(id));
return "boardview";
}
@GetMapping("/board/delete")
public String boardDelete(Integer id){
boardService.boardDelete(id);
return "redirect:/board/list"; // 삭제 처리 후, list로 이동 - redirect해줌
}
@GetMapping("/board/modify/{id}") // 여기서 id는 path variable(주소 변수, 경로 변수 )
public String boardModify(@PathVariable("id") Integer id, Model model){
model.addAttribute("board", boardService.boardView(id));
return "boardmodify";
}
@PostMapping("/board/update/{id}")
public String boardUpdate(@PathVariable("id") Integer id, Board board, Model model, MultipartFile file) throws Exception{
Board boardTemp = boardService.boardView(id); // 기존내용 불러와주기
boardTemp.setTitle(board.getTitle());
boardTemp.setContent(board.getContent()); // 수정한 내용을 board 자료형의 boardTemp변수의 title과 content로 설정해준다
boardService.write(boardTemp, file); // 설정한 boardTemp의 내용을 다시 해당 게시물 title과 content에 덮어써주는 코드(수정)
model.addAttribute("message", "글 수정이 완료되었습니다");
model.addAttribute("searchUrl", "/board/list");
return "message";
}
}
이외 어노테이션으로,
@Autowired : 직독직해로, 즉 자동으로 연결시켜주는 것을 의미한다. 스프링은 IoC컨테이너(Inversion of Control)이라는 게 있는데, IoC 컨테이너의 역할은 인스턴스 생성부터 소멸까지의 사이클을 개발자 대신 관리해주는 컨테이너를 의미한다. Autowired는 이러한 IoC컨테이너에 있는 객체와 자동으로 연결시켜주겠다는 어노테이션이다.
@PathVariable : URL 경로에 변수를 담아 주는 것. 위의 예시로 본다면
@PostMapping("/board/update/{id}")
public String boardUpdate(@PathVariable("id") Integer id, Board board, Model model, MultipartFile file) throws Exception{
Board boardTemp = boardService.boardView(id); // 기존내용 불러와주기
boardTemp.setTitle(board.getTitle());
boardTemp.setContent(board.getContent()); // 수정한 내용을 board 자료형의 boardTemp변수의 title과 content로 설정해준다
boardService.write(boardTemp, file); // 설정한 boardTemp의 내용을 다시 해당 게시물 title과 content에 덮어써주는 코드(수정)
model.addAttribute("message", "글 수정이 완료되었습니다");
model.addAttribute("searchUrl", "/board/list");
return "message";
}
에서, 경로의 id가 Integer 자료형의 id 변수임을 선언하는데 사용한다. 이렇게 PathVariable 어노테이션을 활용해서 해당하는 View 파일에서 변수를 사용할 수도 있다.
Entity.java?
Entity는 데이터베이스에 쓰일 필드와 여러 엔터티 간 연관관계를 정의시켜주는 파일이다. 즉, 우리가 Board라고 하는 자료형을 지정시키고 사용하기 위한 정의를 내리는 파일이라고 생각하면 되겠다.
ex. BoardEntity.java
package com.study.board.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
public class Board {
@Id // primary key를 의미
@GeneratedValue(strategy = GenerationType.IDENTITY) // strategy를 GenerationType.IDENTITY로 해준다(MySQL, MariaDB / SEQUENCE는 오라클 용 / AUTO는 자동지정)
private Integer id;
private String title;
private String content;
private String filename;
private String filepath;
}
@Entity : 해당 파일이 Entity를 선언하는 파일임을 알리는 어노테이션
@Data : @Getter, @Setter, @RequiredArgsConstructor, @Tostring 등의 어노테이션을 자동으로 한꺼번에 설정해주는 어노테이션이다. 세부 어노테이션 각각의 의미는 데이터를 조회, 선언 등의 역할을 한다. Lombok 어노테이션이라고 한다.
@Id : PrivateKey를 Id라고 알려주는 어노테이션이다
@GeneratedValue(strategy = GenerationType.IDENTITY) : Id값을 자동으로 +1씩 할당해주는 어노테이션이다.
위와 같은 엔터티를 만들면,
Repository.java?
엔터티에 의해 생성된 DB에 접근하는 메서드(ex. findAll())들을 사용하기 위한 인터페이스이다. 위 엔터티를 생성했으면 조회하거나 값을 넣는 등의 CRUD(Create, Read, Update, Delete)작업 을 해야 쓸모가 있는데, 이것을 어떻게 할 것인지 알려주는 인터페이스라고 생각하면 될 것 같다.
JpaRepository<대상 엔터티, 대상의 PK타입>를 상속받도록 함으로써 기본적인 동작이 가능해진다.
ex. BoardRepository.java
package com.study.board.repository;
import com.study.board.entity.Board;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BoardRepository extends JpaRepository<Board, Integer> {
Page<Board> findByTitleContaining(String searchKeyword, Pageable pageable);
}
여기서 findByTitleContaining 메서드를 선언해주어 Title에 어떠한 텍스트가 포함된 데이터를 몽땅 조회해주는 메서드를 불러와 사용할 수 있게 되어, 검색창에 탑재해 사용했었다.
Service.java?
Service의 역할은, DB로 부터 받아온 데이터를 가공하는 것이다. Service는 아래 예시를 보면 훨씬 더 와닿을 것이다.
ex. BoardService.java
package com.study.board.service;
import com.study.board.entity.Board;
import com.study.board.repository.BoardRepository;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository; // 객체를 생성 - 스프링 부트에서 제공하는 Autowired를 사용하면 스프링이 알아서 읽어와서 자동으로 주입을 해준다 dependency injection(의존성 주입)이라 함
// 글 작성 처리
public void write(Board board, MultipartFile file) throws Exception{ // 오류처리 throws Exception
String projectPath = System.getProperty("user.dir") + "/src/main/resources/static/files"; // 저장경로 지정
UUID uuid = UUID.randomUUID(); // 파일 이름에 붙일 랜덤이름 생성
String fileName = uuid + "_" + file.getOriginalFilename(); // 랜덤이름을 파일네임 앞에 붙인 후 _ 그리고 원래 파일이름으로 파일이름 생성
File saveFile = new File(projectPath, fileName); // 파일을 생성해줄건데, projectPath에 담기고, name이름으로 담긴다는 의미
file.transferTo(saveFile); // 예외처리 필요하기에 throws를 이용, 해주기
board.setFilename(fileName);
board.setFilepath("/files/" + fileName);
boardRepository.save(board); // 이렇게 생성한 서비스는 다시 컨트롤러에서 사용할 것
}
// 게시글 리스트 불러오기 처리
public Page<Board> boardlist(Pageable pageable){
return boardRepository.findAll(pageable); //Board라는 class가 담긴 list를 찾아 반환 , 매개변수가 없는 경우에는 public list이지만, 매개변수로 pageable을 주면 public pableable로 바뀜
}
public Page<Board> boardSearchList(String searchKeyword, Pageable pageable){
return boardRepository.findByTitleContaining(searchKeyword, pageable);
}
// 특정 게시글 불러오기
public Board boardView(Integer id){
return boardRepository.findById(id).get(); // Integer형의 변수를 통해 불러오기에 위에 인자로 Integer자료형의 id를 준다.
}
// 특정 게시글 삭제
public void boardDelete(Integer id){
boardRepository.deleteById(id);
}
}
위 예시를 볼 수 있듯, 각각 메서드를 정의하고 해당 메서드를 실행 시 어떠한 수행을 거치는지 정의하면 Controller에서 이를 실행할 수 있게 된다.
이렇게 실행시키는 Application, MVC모델에서 Model과 View를 컨트롤 하는 Controller, 데이터베이스의 Model을 엔터티로 선언하여 사용할 수 있게 하는 Entity, Entity를 사용할 수 있게 해주는 Repository, Controller가 수행할 수 있는 메서드들을 선언하는 Service까지, 그리고 MVC 에서 View를 담당하는 template 파일까지.
'How to become a real programmer' 카테고리의 다른 글
Github repository와 local 연결하기 (0) | 2022.11.12 |
---|---|
스프링 부트(Spring Boot)로 간단한 게시판 만들기 - 최종 (0) | 2022.08.13 |
스프링 부트(Spring Boot)로 간단한 게시판 만들기 - 4 (0) | 2022.08.07 |
스프링 부트(Spring Boot)로 간단한 게시판 만들기 - 3 (0) | 2022.08.04 |
스프링 부트(Spring Boot)로 간단한 게시판 만들기 - 2 (0) | 2022.08.02 |