| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- static #자바 메모리 구조 #멤버 변수
- software enginner
- object 클래스 # java
- java #예외처리 #throw #throws
- 브루트 포스법
- 서버 엔지니어
- server engineer
- 나는야 4학년 #5학년 까지 가보자구
- level3
- server developer
- Next.js
- tibero 7.23
- 백엔드 개발자 로드맵
- level2
- java #추상클래스
- heap area #stack area #static area #jvm
- 이분탐색
- 넥슨개발자컨퍼런스
- 올 겨울은 조금 따뜻할 것 같다.
- Spring
- ndc2025
- 정보처리기사 실기 #정처기 실기 #2024년 2회 #정처기 2024년 2회 #공부법 # 꿀팁
- tmax tibero
- 주니어 백엔드 개발자
- 25304번
- 자바 #자바문법 #자바기초 #참조형 #기본형
- 서버 개발자
- 2798블랙잭
- 단계10
- 반복문
- Today
- Total
개발자 쿠키
[Spring] Spring Boot 게시판 API - [작성, 조회, 수정, 삭제, 검색, 페이징] 본문
개발환경
IDEA: intelliJ IDEA
Java 17
Gradle
Spring Boot 3.0.4
MySQL 8.0 CE
프로젝트 구조

- 게시판 목록 페이지
- 게시글 목록표시
- 게시글의 제목으로 검색할 수 있는 검색창
- 새 게시글을 작성할 수 있는 링크
- 게시글 작성 페이지
- 제목, 내용, 작성자 필드가 있는 입력 양식이 있음
- 게시글을 저장하고 목록 페이지로 돌아갈 수 있는 버튼이 있음
- 게시글 수정 페이지
- 제목, 내용, 작성자 필드가 있는 입력 양식이 있음
- 변경된 내용을 저장하고 목록 페이지로 돌아갈 수 있는 버튼이 있음
- 게시글 상세 페이지
- 게시글의 제목, 내용, 작성자 표시
- 댓글 목록 표시
- 댓글 작성을 위한 입력 양식
- 각 댓글을 삭제할 수 있는 버튼

각 디렉터리 역할
- controller
- URL과 실행 함수를 매핑
- 비즈니스 로직이 있는 Service를 호출하여 비즈니스 로직처리
- 반환활 템플릿을 정의 및 json 등으로 응답
- service
- 비즈니스 로직을 구현
- 데이터처리(모델)를 담당하는 repository에서 데이터를 가져와서 controller에 넘겨주거나, 비즈니스 로직을 처리
- domain>entity
- DB 테이블과 매핑되는 객체(Entity)를 정의
- JPA에서는 Entity를 통해 데이터를 조작
- domian>repository
- 데이터를 가져오거나 조작하는 함수를 정의
- interface를 implements하여 미리 만들어진 함수를 사용할 수 있으며, 직접 구현이 가능
- dto
- controller와 service 간에 주고 받을 객체를 정의하며, 최종적으로는 view에 뿌려줄 객체
- Entity와 속성이 같을 수 있으나, 여러 service를 거쳐야 하는 경우 dto의 몸집은 더 커짐
- entity와 dto를 분리한 이유는 Entity는 DB 테이블이 정의되어 있으므로, 데이터 전달 목적을 갖는 객체인 dto를 정의한느 것이 더 좋다.
- static
- css, js, img 등의 정적 자원들을 모아놓은 디렉토리
- templates
- 템플릿을 모아놓은 디렉토리
- Thymeleaft는 HTML을 사용
JPA에서 제공하는 어노테이션들
@Entity
- 테이블과 링크될 클래스임을 나타냄
- 언더스코어 네이밍(_)으로 이름을 매칭
- ex) SalesManager.java → sales_manager table
@Id
- 해당 테이블의 PK 필드를 나타냄
@GeneratedValue(strategy = GenerationType.IDENTITY)
- PK의 생성 규칙을 나타냄
- 기본값으로 AUTO로, MYSQL의 auto_increment와 같이 자동증가하는 정수형 값이 됨
- 기본키로 대체키를 사용할 때, 기본키 값 생성 전략을 명시
@Column
- 테이블의 컬럼을 나타내면, 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 컬럼이 됨
- 사용하는 이유는 기본값 외에 추가로 변경이 필요한 옵션이 있을 경우 사용
- 문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나 타입을 TEXT로 변경하고 싶거나 등의 경우에 사용
Lombok 라이브러리의 어노테이션
@NoArgsConstructor : 기본 생성자 자동 추가
- access = AccessLevel.PROTECTED : 기본생성자의 접근 권한을 protected로 제한
- 생성자로 protected BoardEntity() {} 와 같은 효과
- Entity 클래스를 프로젝트 코드상에서 기본생성자로 생성하는 것은 막되, JPA에서 Entity 클래스를 생성하는 것은 허용하기 위해 추가
@Getter : 클래스내 모든 필드의 Getter 메소드를 자동생성
- @Getter @Setter 모두 해결 : @Data 어노테이션
- @Setter 어노테이션은 setter를 자동생성 해주지만, 무분별한 setter 사용은 안정성을 보장받기 어려우므로 Builder 패턴을 사용
@Builder : 해당 클래스의 빌더패턴 클래스를 생성
- 생성자 상단에 선언시 생성자에 포함된 빌드만 빌더에 포함
TimeEntity : 데이터조작시 자동으로 날짜를 생성해주는 JPA의 Auditing 기능
@MappedSuperclass
- 테이블로 매핑하지 않고, 자식 클래스(엔티티)에게 매핑정보를 상속하기 위한 어노테이션
@EntityListners(AudtingEntityListner.class)
- JPA에게 해당 Entity는 Audtiong 기능을 사용한다는 것을 알리는 어노테이션
@CreatedDate
- 속성을 추가하지 않으면 해당 값은 null이 됨
- Entity가 처음 저장될때 생성일을 주입하는 어노테이션
@LastModifiedDate
- Entity가 수정될때 수정일자를 주입하는 어노테이션
스프링프레임워크에서 Bean을 주입받는 방식
- @Autowired
- setter
- 생성자
Handler 구현
url을 통해 요청을 매핑하는 handler를 @Controller annotation을 이용하여 구현
루트요청(”/”)일 경우, 메인페이지(list.html)로 연결해주고, 글쓰기를 클릭하면 (”/post 요청)
write.html로 연결해줌

제목, 작성자, 내용을 쓰면 데이터 → DB에 저장
‘등록’ 버튼을 누르면, Post방식으로 요청이 온다.
Post방식의 요청을 받아서, 실제로는 Service에서 처리되도록 할 것이다.
Dto를 사용해 Controller와 Service 사이에서 데이터를 주고 받음. Service를 주입받아서 사용

BoardDto와 BoardService는 구현해주어야 한다.
Service는 실제로 비즈니스 로직을 시행해주는 역할을 함
repository를 이용하여 실제로 저장해줘야 함.
Entity
Entity는 DB 테이블과 매핑되는 객체
@Builder는 setter 대신 사용
@Table 애노테이션으로 DB 테이블 이름을 정해줄 수 있지만, 없는 경우 클래스 이름으로 자동 매핑

extends하고 있는 TimeEntity는 데이터 조작 시 자동으로 날짜를 수정해주는 JPA의 Auditing기능을 사용하는 entity

JPA Audting 기능을 사용하기 위해 main 클래스에 @EnableJpaAudting 애노테이션을 붙여준다.

Repository
Repository는 데이터 조작을 담당. interface로 생성하며, JpaRepository를 extends한다.
JpaRepository의 값은 매핑할 entity와 id의 타입이다.

Service
만들어준 repository를 이용하여, service를 구현해보자. Service는 실제 비즈니스 로직을 시행해주는 역할

DTO
Controller와 Service 사이에서 데이터를 주고 받는 DTO를 구현해줘야함.
DTO를 통해 service의 savePost에서 Repository에 데이터를 집어 넣음.

toEntity()는 dto에서 필요한 부분을 빌더패턴을 통해 entity로 만드는 역할
실행

spring.datasource : datasource는 MySQL설정에 관한 것
spring.jpa.show-sql : 콘솔에 JPA 실행 쿼리 출력
spring.jpa.hibernate.ddl-auto : 데이터베이스 초기화 젼락을 설정
- none : 아무것도 실행x
- create : SessionFactory가 시작될 때 기존테이블을 삭제 후 다시 생성
- create-drop : create와 같으나 SessionFactory가 종료될 때 drop을 실행
- update : 변경된 스키마만 반영
- validate : 엔티티와 테이블이 정상적으로 매핑되었는지만 확인
게시글 조회
저장된 데이터를 불러와 조회
Controller에서 ‘/list’ 요청을 받았을 때, list.html로 매핑

list.html에서는 boardList라는 것을 정보로 출력. 따라서, Controller에서 boradList를 넘겨줘야 DB에 저장된 데이터를 볼 수 있음.

Controller
Model을 통해 View에 데이터를 전달해줌.
BoardDto를 이용해 DB에 저장된 데이터를 List로 불러올 것이다. 실제 로직은 Service에서 구현
만든 List를 boardList라는 이름으로 View에 전달

Service
Service에서 getBoardList를 구현해준다. getBoardList는 DB에 저장되어 있는 전체 데이터를 불러옴
repository에서 모든 데이터를 가져와, 데이터 만큼 반복하면서, BoardDto 타입의 List에 데이터를 파싱하여 집어넣고, 완성된 BoardDto 타입의 List를 리턴

실행결과

게시글 Detail 페이지
각 게시글 별 Detail페이지 구현. detail.html로 연결
detail.html은 boardDto라는 값으로 데이터를 출력

Controller
각 게시글을 클릭하면, ‘/post/id값’으로 요청을 함
따라서, 각 게시글의 id 값을 받아서 해당 게시글의 요소들만 Dto타입으로 만들어서 전달해줘야함

@PathVariable을 통해 요청에 오는 id값을 받아 getPost로 전달. getPost는 각 게시글의 정보를 가져오는 기능인데 Service에서 구현해줄 것이다. model.을 통해 boardDto 타입의 데이터를 view에 전달
Serivce
게시글의 id 값을 받아 해당 게시글의 정보만 repository에서 findById로 가져온다.
그리고, BoardDto 타입으로 만들어 return 해준다.

실행 결과

게시글 수정
Controller
detail 페이지에서 수정 버튼을 누르면 ‘/post/edit/id값’으로 요청이 들어옴
디테일 페이지에서 썼던 getPost를 그대로 사용. 이전 데이터 값을 유지한 상태에서 수정된 부분만 다시 저장해야하기 때문. 게시글의 데이터를 가지고 update.html로 보냄

수정 버튼을 누르면 put 형식으로 ‘/post/edit/id값’으로 요청이 옴

글쓰기 구현시 구현했던 savePost를 이용하여 DB에 새로 저장
게시글 삭제
Controller
디테일 페이지에서 삭제 버튼을 누르면 ‘post/id값’으로 delete 요청이 들어옴
deletePost를 사용해서 게시글 id로 DB에서 게시글을 삭제. 실제 구현은 Service에서 함

Service
repository의 deleteById를 사용해서 넘겨받은 id를 이용해서 DB에서 삭제

검색 기능
검색하기를 누르면 ‘/board/search’ 요청이 들어온다. ‘keyword’라는 이름으로 값을 넘겨줌

Controller
@RequestParam 요청으로 들어온 값 중 ‘keyword’로 넘어온 값을 받아서 해당 키워드가 포함된 리스트를 넘겨주는 search를 사용하고 model을 통해 view에 값을 넘겨준다.
SearchPosts는 Service에서 구현해준다.

Service
repository를 이용하여 title에 keyword 값이 포함된 데이터만 가져온다. findByTitleContaining은 repository에 선언해줘야한다. BoardDto에 데이터를 매핑하여 리턴한다.

Repository
JpaRepository에서는 By 뒷부분은 SQL의 where 조건 절에 해당. 따라서, Containing을 붙여주면 Like 검색이 된다.

페이징
page 링크를 누르면 Get요청으로 page값을 넘겨줌

Controller
메인 페이지를 보여줄 때, 요청에서 page값을 받아 해당 페이지의 게시글만 보여줘야함. 기존의 list controller에서 page값을 받아올 수 있게 추가해줘야함
@RequestParam으로 page값을 받아준다 (기본값 1)
해당 페이지에 해당하는 게시글들만 보여줘야 하기 때문에, getBoardList에도 페이지번호를 줘야함
pageList는 전체 페이지 목록. 게시글 수로 페이지 목록 수가 결정. Service에서 구현해줘야함.

Service
repository에서 게시글을 가져오는 findAll에 Pageable 인터페이스 구현체(PageRequest.of)를 전달해주면 페이징을 할 수 있다.

getPageList는 총 게시글 갯수이다.
getBoardCount로 repository에서 총 데이터(게시글)의 수를 받아오고, 총 게시글을 한 페이지 당 보여지게 할 게시글 수로 나눠준다.(소수점이 있을 경우 올림한다. 왜냐하면 만약 총 게시글 수가 5개이고, 한 페이지 당 보여지게 할 게시글 수가 4개인 경우, 5/4 = 1.25 이다. 즉 올림을 하면 2이다. 총 게시글이 5개면 총 2 페이지가 필요하다. 따라서 올림을 해준다.)
현재 페이지를 기준으로 마지막 페이지 번호 계산하고, 페이지 리스트를 리턴한다.

- getBoardList() 메서드는 기존에 존재하는 메서드, 페이징을 할 수 있게 수정
- boardRepository.findAll(PageRequest.of(pageNum - 1, PAGE_POST_COUNT, Sort.by(Sort.Direction.ASC, "createdDate")));
- repository의 find()관련 메서드를 호출할 때 인터페이스를 구현한 클래스(pagerequest.of())를 전달하면 페이징을 할 수 있음
- 첫 번째 인자
- limit을 의미
- “현재 페이지 번호 -1” 계산한 값, 실제 페이지 번호와 SQL 조회시 사용되는 limit은 다름
- 두 번째 인자
- offset을 의미
- 세 번째 인자
- 정렬 방식을 결정
- createDate 컬럼을 기준으로 오름차순으로 정렬하여 가져옴
- 첫 번째 인자
- 반환된 Page개겣의 getContent() 메서드를 호출하면, 엔티티 리스트로 꺼낼 수 있음
- getBoardCount() 메서드는 신규로 추가한 메서드이며, 전체 게시글 개수를 가져옴
- getPageList() 메서드도 신규로 추가한 메서드이며, 프론트에 노출시킬 페이지 번호 리스트를 계산하는 로직
JPA like query를 사용하는 방법
- StartsWith
- 검색어로 시작하는 Like 검색
- {keyword}%
- EndsWith
- 검색어로 긑는 Like 검색
- %{keyword}
- IgnoreCase
- 대소문자 구분 없이 검색
- Not
- 검색어를 포함하지 않는 검색

'spring' 카테고리의 다른 글
| [Spring] DispatcherServlet (0) | 2024.08.13 |
|---|---|
| [Spring] Spring Boot 게시판 API - 댓글 구현 (0) | 2023.05.10 |
| 스프링 MVC 1편 - [서블릿] #2 (0) | 2022.08.31 |
| 스프링 MVC 1편 - [웹 애플리케이션 이해] #1 (0) | 2022.08.30 |
| Spring - [빈 스코프] #9 (0) | 2022.08.29 |