반응형
📖 프론트엔드 작업
(1) Qna Type 지정
// IQna.ts : 타입 인터페이스
export default interface IQna {
qno?: any | null,
question: string,
answer: string,
questioner: string,
answerer: string
}
(2) Qna 게시판 CRUD 함수 생성을 위한 QnaService 생성
// QnaService.ts : axios 공통함수 (벡엔드 CRUD 연동함수)
import IQna from "../../types/basic/IQna";
import http from "../../utils/http-common";
// 전체 조회 + like 검색(paging 기능 : page(현재페이지), size(한 페이지당 개수))
// 셀렉트박스 : (question) 입력창 : 질문 like 검색
// 셀렉트박스 : (questioner) 입력창 : 질문자 like 검색
// 변수 : searchSelect(question, questioner)
// 변수 : searchKeyword : 검색어
const getAll = (searchSelect:string, searchKeyword:string, page:number, size:number) => {
return http.get<Array<IQna>>(`/basic/qna?searchSelect=${searchSelect}&searchKeyword=${searchKeyword}&page=${page}&size=${size}`);
};
// 상세 조회
const get = (qno:any) => {
return http.get<IQna>(`/basic/qna/${qno}`);
};
// 저장 함수
const create = (data:IQna) => {
return http.post<IQna>("/basic/qna", data);
};
// 수정 함수
const update = (qno:any, data:IQna) => {
return http.put<any>(`/basic/qna/${qno}`, data);
};
// 삭제 함수
const remove = (qno:any) => {
return http.delete<any>(`/basic/qna/deletion/${qno}`);
};
const QnaService = {
getAll,
get,
create,
update,
remove,
};
export default QnaService;
(3) Qna 화면 및 JS 코딩
// QnaList.tsx : rfce
import { Pagination } from "@mui/material";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import TitleCom from "../../../components/common/TitleCom";
import IQna from "../../../types/basic/IQna";
import QnaService from "../../../services/basic/QnaServics";
function QnaList() {
// 변수 정의
// qna 배열 변수
const [qna, setQna] = useState<Array<IQna>>([]);
// select 태그에 선택된 값을 저장할 변수 : 기본 (question)
const [searchSelect, setSearchSelect] = useState<string>("question");
// 검색어(input) 변수
const [searchKeyword, setSearchKeyword] = useState<string>("");
// todo: 공통 페이징 변수 4개
// todo: 공통 변수 : page(현재페이지번호), count(총페이지건수), pageSize(3,6,9 배열)
const [page, setPage] = useState<number>(1);
const [count, setCount] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(3); // 1페이지당개수
const pageSizes = [3, 6, 9]; // 공통 pageSizes : 배열 (셀렉트 박스 사용)
// 함수 정의
// 화면이 뜰때 실행되는 이벤트 + 감시변수
useEffect(() => {
retrieveQna(); // 전체조회 실행
}, [page, pageSize]);
// 전체조회
const retrieveQna = () => {
// 벡엔드 매개변수 전송 : + 현재페이지(page), 1페이지당개수(pageSize)
QnaService.getAll(searchSelect, searchKeyword, page - 1, pageSize) // 벡엔드 전체조회요청
.then((response: any) => {
const { qna, totalPages } = response.data;
setQna(qna);
setCount(totalPages);
// 로그 출력
console.log("response", response.data);
})
.catch((e: Error) => {
console.log(e);
});
};
// select 태그 수동바인딩
const onChangeSearchSelect = (e: any) => {
setSearchSelect(e.target.value); // 화면값 -> 변수저장
};
// input 태그 수동바인딩
const onChangeSearchKeyword = (e: any) => {
setSearchKeyword(e.target.value); // 화면값 -> 변수저장
};
// todo: 공통 페이징 함수 2개
// todo: handlePageSizeChange(공통) : pageSize 값 변경시 실행되는 함수
// select 태그 수동 바인딩 : 화면값 -> 변수에 저장
const handlePageSizeChange = (event: any) => {
setPageSize(event.target.value); // 1페이지당 개수저장(3,6,9)
setPage(1); // 현재페이지번호 : 1로 강제설정
};
// todo: Pagination 수동 바인딩(공통)
// 페이지 번호를 누르면 => page 변수에 값 저장
const handlePageChange = (event: any, value: number) => {
// value == 화면의 페이지번호
setPage(value);
};
return (
// 여기
<>
{/* 제목 start */}
<TitleCom title="Qna List" />
{/* 제목 end */}
{/* question start(다양한 검색어 부분) */}
<div className="row mb-5 justify-content-center">
<div className="col-md-8">
<div className="input-group mb-3">
{/* 다양한 검색(select : question,questioner) 시작 */}
<div className="col-2">
<select
className="form-select"
onChange={onChangeSearchSelect}
value={searchSelect}
>
<option key="question" value="question">
question
</option>
<option key="questioner" value="questioner">
questioner
</option>
</select>
</div>
{/* 다양한 검색(select) 끝 */}
{/* 검색어(searchKeyword) 입력창(input) 시작 */}
<div className="col-9 w-50 input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by Question"
value={searchKeyword}
onChange={onChangeSearchKeyword}
/>
</div>
{/* 검색어 입력창 끝 */}
{/* 검색버튼 시작 */}
<div className="input-group-append col-md-1">
<button
className="btn btn-outline-secondary"
type="button"
onClick={retrieveQna}
>
Search
</button>
</div>
{/* 검색버튼 끝 */}
</div>
</div>
</div>
{/* question end */}
<div className="col-md-12">
{/* page control start(페이징 html) */}
<div className="mt-3">
{"Items per Page: "}
<select onChange={handlePageSizeChange} value={pageSize}>
{pageSizes.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
<Pagination
className="my-3"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
shape="rounded"
onChange={handlePageChange}
/>
</div>
{/* page control end */}
{/* table start(본문) */}
<table className="table">
<thead>
<tr>
<th scope="col">Question</th>
<th scope="col">Questioner</th>
<th scope="col">Answer</th>
<th scope="col">Answerer</th>
</tr>
</thead>
<tbody>
{qna &&
qna.map((data) => (
// 키값 추가 않하면 react 에서 경고를 추가 : 키는 내부적으로 리액트가 rerending 할때 체크하는 값임
<tr key={data.question}>
<td>{data.question}</td>
<td>{data.questioner}</td>
<td>{data.answer}</td>
<td>{data.answerer}</td>
<td>
<Link to={"/qna/" + data.qno}>
<span className="badge bg-success">Edit</span>
</Link>
</td>
</tr>
))}
</tbody>
</table>
{/* table end */}
</div>
</>
);
}
export default QnaList;
(4) 프론트 엔드 서버 시작 후 화면 확인
📖 벡엔드 작업
(1) Qna Entity 생성
package com.example.simpledms.model.entity.basic;
import com.example.simpledms.model.common.BaseTimeEntity;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.*;
/**
* packageName : com.example.simpledms.model.entity.basic
* fileName : Qna
* author : GGG
* date : 2023-10-24
* description : Qna 엔티티(vo)
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-24 GGG 최초 생성
*/
@Entity
@Table(name="TB_QNA")
@SequenceGenerator(
name = "SQ_QNA_GENERATOR"
, sequenceName = "SQ_QNA"
, initialValue = 1
, allocationSize = 1
)
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
// soft delete
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE TB_QNA SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') WHERE QNO = ?")
public class Qna extends BaseTimeEntity {
// 속성
// 단축키 : ctrl + shift + u
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "SQ_QNA_GENERATOR")
private Integer qno; // 기본키(@Id), 시퀀스
private String question;
private String answer;
private String questioner;
private String answerer;
}
🔎 빌더 패턴?
빌더 패턴은 여러 생성자 인수가 필요한 복잡한 개체를 만드는 방법을 제공하는 소프트웨어 디자인 패턴입니다. 선택적 매개변수가 많은 객체를 생성할 필요가 있을 때 자주 사용됩니다.
빌더 패턴은 여러 생성자 인수가 필요한 복잡한 개체를 만드는 문제를 해결하는 데 사용되는 생성 디자인 패턴입니다. 빌더 패턴은 객체의 구성과 표현을 분리합니다. 이를 통해 동일한 구성 프로세스에서 다른 표현을 만들 수 있습니다.
팩토리 패턴은 동일한 유형의 객체를 생성해야 할 때 사용됩니다. 빌더 패턴은 다른 유형의 객체를 생성해야 할 때 사용됩니다.
빌더 패턴에는 다음과 같은 이점이 있습니다.
- 객체의 구성과 표현을 분리합니다. 이를 통해 동일한 구성 프로세스에서 다른 표현을 만들 수 있습니다.
- 복잡한 개체를 단계별로 생성할 수 있습니다.
- 클라이언트 코드에 영향을 주지 않고 구성 프로세스를 변경할 수 있습니다.
빌더 패턴에는 다음과 같은 단점이 있습니다.
- 많은 상용구 코드로 이어질 수 있습니다.
- 빌더 패턴을 올바르게 사용하지 않으면 결과 코드를 이해하기 어려울 수 있습니다.
🔎 빌더 패턴은 언제 사용할까?
빌더 패턴은 여러 생성자 인수가 필요한 복잡한 객체를 생성해야 할 때 사용해야 합니다. 선택적 매개변수가 많은 객체를 생성할 필요가 있을 때 자주 사용됩니다.
(2) QnaRepository 생성
package com.example.simpledms.repository.basic;
import com.example.simpledms.model.entity.basic.Qna;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* packageName : com.example.simpledms.repository.basic
* fileName : QnaRepository
* author : GGG
* date : 2023-10-24
* description : Qna CRUD Repository
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-24 GGG 최초 생성
*/
@Repository
public interface QnaRepository extends JpaRepository<Qna, Integer> {
// 1) question(질문) like 검색
Page<Qna> findAllByQuestionContaining(String question, Pageable pageable);
// 2) questioner(질문자) like 검색
Page<Qna> findAllByQuestionerContaining(String questioner, Pageable pageable);
}
(3) QnaService 생성
package com.example.simpledms.service.basic;
import com.example.simpledms.model.entity.basic.Qna;
import com.example.simpledms.repository.basic.QnaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
/**
* packageName : com.example.simpledms.service.basic
* fileName : QnaService
* author : GGG
* date : 2023-10-24
* description : Qna 서비스(업무로직 : 비지니스 로직)
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-24 GGG 최초 생성
*/
@Service
public class QnaService {
@Autowired
QnaRepository qnaRepository; // DI
// 1) question like 검색
public Page<Qna> findAllByQuestionContaining(String question, Pageable pageable) {
Page<Qna> page = qnaRepository.findAllByQuestionContaining(question, pageable);
return page;
}
// 2) questioner like 검색
public Page<Qna> find(String questioner, Pageable pageable){
Page<Qna> page = qnaRepository.findAllByQuestionerContaining(questioner, pageable);
return page;
}
}
(4) QnaController 생성
package com.example.simpledms.controller.basic;
import com.example.simpledms.model.entity.basic.Dept;
import com.example.simpledms.model.entity.basic.Qna;
import com.example.simpledms.service.basic.QnaService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : com.example.simpledms.controller.basic
* fileName : QnaController
* author : GGG
* date : 2023-10-24
* description :
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-24 GGG 최초 생성
*/
@RestController
@Slf4j
@RequestMapping("/api/basic")
public class QnaController {
@Autowired
QnaService qnaService; // DI
// 전체조회 + question/questioner like 검색
@GetMapping("/qna")
public ResponseEntity<Object> findAllByContaining(
@RequestParam(defaultValue = "") String searchSelect,
@RequestParam(defaultValue = "") String searchKeyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
// 페이지 변수 저장 (page:현재페이지 번호, size: 한 페이지당 개수)
// 함수 매개변수 : Pageable(위의 값을 넣기)
// 사용법 : Pageable pageable = PageRequest.of(현재페이지번호, 한페이지당개수);
Pageable pageable = PageRequest.of(page, size);
Page<Qna> qnaPage;
if(searchSelect.equals("question")){ // searchSelect가 question 이면
qnaPage
= qnaService.findAllByQuestionContaining(searchSelect, pageable); // question like 검색하기
} else {
qnaPage = qnaService.findAllByQuestionerContaining(searchKeyword, pageable); // 아니면 questioner like 검색하기
}
// question like 검색
// 리액트 전송 : qna배열, 페이징 정보 [자료구조 : Map<키이름, 값>]
Map<String, Object> response = new HashMap<>();
response.put("qna", qnaPage.getContent()); // qna배열 전송
response.put("currentPage", qnaPage.getNumber()); // 현재페이지번호 전송
response.put("totalItems", qnaPage.getTotalElements()); // 총 건수(개수) 전송
response.put("totalPages", qnaPage.getTotalPages()); // 총 페이지수 전송
// 신호 보내기
if (qnaPage.isEmpty() == false) {
// 성공
return new ResponseEntity<>(response, HttpStatus.OK);
} else {
// 데이터 없음
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
} catch (Exception e) {
log.debug(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
(5) 프론트 + 벡엔드 연동확인
1) 전체검색
2) Question like 검색 = 3으로 검색 시
3) Questioner like 검색 = '홍'으로 검색 시
반응형
'Spring Boot > 스프링부트 예제' 카테고리의 다른 글
답변형 게시판 구현 (2) (1) | 2023.10.27 |
---|---|
답변형 게시판 만들기 (1) (1) | 2023.10.26 |
게시판 페이징 처리 (1) | 2023.10.23 |
front + backend 게시판 CRUD 구현 (2) (0) | 2023.10.20 |
JPA - 연관관계 매핑 (0) | 2023.10.19 |