프론트
공통 변수 : page(현재페이지), count(총페이지건수), pageSize(3,6,9 배열)
1. 폴더 생성 후 실습 파일 넣기
2. basic./Dept 프론트 & 백엔드 (전체 조회+페이징) 연동
3. 공통 변수 : page(현재페이지), count(총페이지건수), pageSize(3,6,9 배열)
4. DeptList.tsx 전체조회페이지 + 페이징 벡엔드 매개변수 전송 : + 현재페이지(page), 1페이지당개수(pageSize)
5. page, pageSize - Pagination 수동 바인딩
더보기
// DeptList.tsx : rfce
// 전체조회페이지 + 페이징
import TitleCom from "../../../components/common/TitleCom";
import { Pagination } from "@mui/material";
import { Link } from "react-router-dom";
import { useState, useEffect } from "react";
import IDept from "../../../types/basic/IDept";
import DeptService from "../../../services/basic/DeptService";
function DeptList() {
// 변수 정의
// 부서 배열 변수
const [dept, setDept] = useState<Array<IDept>>([]);
// 검색어 변수
const [searchDname, setSearchDname] = useState<string>("");
// 공통 변수 : 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페이지당개수
// pageSizes : 배열 (셀렉트 박스 사용)
const pageSizes = [3, 6, 9];
// 함수 정의
// TODO: 1) 컴포넌트가 mounted 될때 한번만 실행됨 : useEffect(() => {실행문},[])
// TODO: 2) 컴포넌트의 변수값이 변할때 실행됨 : useEffect(() => {실행문},[감시변수])
useEffect(() => {
retrieveDept(); // 전체 조회
}, [page, pageSize]);
// 전체조회 함수
const retrieveDept = () => {
// TODO: 벡엔드 매개변수 전송 : + 현재페이지(page), 1페이지당개수(pageSize)
DeptService.getAll(searchDname, page - 1, pageSize) // 벡엔드 전체조회요청
.then((response: any) => {
// 벡엔드 성공시 실행됨
// es6(모던js) 문법 : 객체 분해 할당
// const dept = response.data.dept; // 부서배열
// const totalPages = response.data.totalPages; // 전체페이지수
const { dept, totalPages } = response.data;
// dept 저장
setDept(dept);
setCount(totalPages);
// 로그 출력
console.log("response", response.data);
})
.catch((e: Error) => {
// 벡엔드 실패시 실행됨
console.log(e);
});
};
// 검색어 수동 바인딩 함수
const onChangeSearchDname = (e: any) => {
const searchDname = e.target.value;
setSearchDname(searchDname);
};
// 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="Dept List" />
{/* 제목 end */}
{/* dname start */}
<div className="row mb-5 justify-content-center">
{/* w-50 : 크기 조정, mx-auto : 중앙정렬(margin: 0 auto), justify-content-center */}
<div className="col-12 w-50 input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by dname"
value={searchDname}
onChange={onChangeSearchDname}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={retrieveDept}
>
Search
</button>
</div>
</div>
</div>
{/* dname end */}
{/* paging 시작 */}
<div className="mt-3">
{"Items per Page: "}
<select onChange={handlePageSizeChange} value={pageSize}>
{pageSizes.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
{/* TODO: 사용법 : count={1페이지당개수} , page={현재페이지번호} */}
<Pagination
className="my-3"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
shape="rounded"
onChange={handlePageChange}
/>
</div>
{/* paging 끝 */}
{/* table start */}
<div className="col-md-12">
{/* table start */}
<table className="table">
<thead className="table-light">
<tr>
<th scope="col">Dname</th>
<th scope="col">Loc</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{dept &&
dept.map((data) => (
<tr key={data.dno}>
<td>{data.dname}</td>
<td>{data.loc}</td>
<td>
<Link to={"/dept/" + data.dno}>
<span className="badge bg-success">Edit</span>
</Link>
</td>
</tr>
))}
</tbody>
</table>
{/* table end */}
</div>
{/* table end */}
</>
);
}
export default DeptList;
백엔드
1.application.properties - 백엔드 (url 허용 설정 및 오라클DB log4j 적용 오라클 설정)
2. config./WebConfig url 허용 설정
3.model./common BaseTimeEntity JPA 에서 자동으로 생성일자/수정일자 만들어주는 클래스
4. model./entity 실습 파일 넣기
5. 모델 클래스 * Soft Delete는 물리적인 데이터 삭제로 발생할 수 있는 문제를 방지하고 쉽게 복원할 필요가 있거나 삭제된 데이터들을 보관하여 데이터로써 활용할 필요나 가치가 있는 경우에 사용 * 실무에서는 법적으로 개인자료일 경우 3년 또는 1년이상 데이터를 보관할 의무가 있고 어길수 법적 문제가 생길 수 있음 -> 그래서 soft delete 방식을 대부분 구현하고 있음
6. JPA CRUD 인터페이스 - dname like : 쿼리메소드 + 페이징(리턴 : page, 매개변수 : pageable) 7. 서비스 클래스 - 전체 조회 + 페이징 - dname like 조회 + 페이징 8. 컨트롤러 - 전체 조회 + dname like 검색 공통 @RequestParam - 페이지 변수 저장 함수 매개변수(page:현재페이지번호, size:1페이지당개수) - 리액트 전송 : 부서 배열, 페이징 정보 [자료구조 : Map<키이름, 값>]
더보기
# 서버 포트
server.port=8000
# 오라클 설정 : log4j 적용
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:oracle:thin:@localhost:1521/xepdb1
spring.datasource.username=scott
spring.datasource.password=!Ds1234567890
# 오라클 설정 ( 오라클 클라우드 전자지갑 설정 ) : log4j 적용
# spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
# jdbc:log4jdbc:oracle:thin:@orcl_medium?TNS_ADMIN=전자지갑경로
# TODO : 맥용 예시 spring.datasource.url=jdbc:log4jdbc:oracle:thin:@orcl_low?TNS_ADMIN=/Users/kangtaegyung/eWallet/Wallet_orcl/
# TODO : 윈도우즈 용 예시 spring.datasource.url=jdbc:log4jdbc:oracle:thin:@orcl_low?TNS_ADMIN=C:/Work/96_eWallet/Wallet_orcl/
# spring.datasource.username=scott
# spring.datasource.password=!Ds1234567890
# jpa 설정
spring.jpa.hibernate.ddl-auto=none
#spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
spring.jpa.show-sql=true
# sql log 찍기
spring.jpa.properties.hibernate.format_sql=true
#Logging Setting , hibernate info 레벨 로깅 설정 : debug, trace 등
logging.level.org.hibernate=info
# batch size 설정 : 연관관계 설정 시 N+1 문제 최소화
# 여러 개의 SELECT 쿼리들을 하나의 IN 쿼리로 만들어줍
spring.jpa.properties.hibernate.default_batch_fetch_size=1000
# 1) resource/data.sql 자동 실행 ( DML 실행 )
# -> data.sql ( dml 실행 ), schema.sql ( ddl 실행 )
spring.jpa.defer-datasource-initialization=true
# 2) resource/data.sql 자동 실행 ( DML 실행 )
# -> data.sql ( dml 실행 ), schema.sql ( ddl 실행 )
spring.sql.init.mode=always
# sql 에러 무시하고 스프링 서버 로딩
spring.sql.init.continue-on-error=true
# 자바 소스 변경시 스프링 서버 자동 재시작
spring.devtools.restart.enabled=true
# TODO : HikariCP settings : DB 커넥션 풀(설정 안하면 기본 10개) => 기본 1개로 제한되어서
# TODO : 오라클 DB 사용 시 다른 사람들과 중복(제한) 접속 피하기 위해 아래 코드 3줄을 꼭 넣어야 함
spring.datasource.hikari.minimumIdle=1
spring.datasource.hikari.maximumPoolSize=1
spring.datasource.hikari.poolName=HikariPoolBooks
# TODO : file upload 최대 size 설정(설정 안하면 기본 1M)
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
더보기
package com.example.simpledms.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* packageName : com.example.dongsungsi.controller
* fileName : WebConfig
* author : kangtaegyung
* date : 2022/06/14
* description : url 허용 설정
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022/06/14 kangtaegyung 최초 생성
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 아래 url 허용
.allowedOrigins("http://localhost:3000")
// .allowedOrigins("http://192.168.35.192:3000/")
// Todo: 아래 추가해야 update, delete, insert, select 가 cors 문제가 안생김
.allowedMethods(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()
);
}
}
더보기
package com.example.simpledms.model.common;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.SQLDelete;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* packageName : com.example.jpaexam.model
* fileName : BaseTimeEntity
* author : kangtaegyung
* date : 2022/10/16
* description : JPA 에서 자동으로 생성일자/수정일자 만들어주는 클래스
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022/10/16 kangtaegyung 최초 생성
*/
@Getter
@Setter
// @MappedSuperclass : JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우
// 필드들(createdDate, modifiedDate)도 칼럼으로 인식하도록 한다.
@MappedSuperclass
// @EntityListeners(AuditingEntityListener.class) : BaseTimeEntity 클래스에
// Auditing 기능을(자동 생성일, 수정일) 포함시킨다.
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
private String insertTime;
private String updateTime;
private String deleteYn;
private String deleteTime;
@PrePersist
//해당 엔티티 저장하기 전
void onPrePersist(){
this.insertTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
this.deleteYn = "N";
}
@PreUpdate
//해당 엔티티 수정 하기 전
void onPreUpdate(){
this.updateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
this.insertTime = this.updateTime;
this.deleteYn = "N";
}
}
더보기
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.modelexam.model
* fileName : Dept
* author : kangtaegyung
* date : 2022/10/12
* description : 부서 모델 클래스
* 요약 :
* Soft Delete는 물리적인 데이터 삭제로 발생할 수 있는 문제를 방지하고 쉽게 복원할 필요가 있거나 삭제된 데이터들을 보관하여 데이터로써 활용할 필요나 가치가 있는 경우에 사용
* 실무에서는 법적으로 개인자료일 경우 3년 또는 1년이상 데이터를 보관할 의무가 있고 어길수 법적 문제가 생길 수 있음 -> 그래서 soft delete 방식을 대부분 구현하고 있음
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022/10/12 kangtaegyung 최초 생성
*/
@Entity
@Table(name="TB_DEPT")
@SequenceGenerator(
name = "SQ_DEPT_GENERATOR"
, sequenceName = "SQ_DEPT"
, initialValue = 1
, allocationSize = 1
)
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
// soft delete
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE TB_DEPT SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') WHERE DNO = ?")
public class Dept extends BaseTimeEntity {
// 부서넘버
// @Id : Primary Key 에 해당
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "SQ_DEPT_GENERATOR"
)
private Integer dno;
// 부서이름
private String dname;
// 부서위치
private String loc;
}
더보기
package com.example.simpledms.repository;
import com.example.simpledms.model.entity.basic.Dept;
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
* fileName : DeptRepository
* author : L.DH
* date : 2023-10-23
* description : JPA CRUD 인터페이스
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* ———————————————————————————————
* 2023-10-23 L.DH 최초 생성
*/
@Repository
public interface DeptRepository extends JpaRepository<Dept, Integer> {
// dname like : 쿼리메소드 + 페이징(리턴 : page, 매개변수 : pageable)
Page<Dept> findAllByDnameContaining(String dname, Pageable pageable);
}
더보기
package com.example.simpledms.service.basic;
import com.example.simpledms.model.entity.basic.Dept;
import com.example.simpledms.repository.DeptRepository;
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 : DeptService
* author : L.DH
* date : 2023-10-23
* description : 부서 서비스
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* ———————————————————————————————
* 2023-10-23 L.DH 최초 생성
*/
@Service
public class DeptService {
@Autowired
DeptRepository deptRepository; // DI 가져오기
// 전체 조회 + 페이징
public Page<Dept> findAll(Pageable pageable) {
Page<Dept> page = deptRepository.findAll(pageable);
return page;
}
// dname like 조회 + 페이징
public Page<Dept> findAllDnameContaining(String dname, Pageable pageable) {
Page<Dept> page
= deptRepository.findAllByDnameContaining(dname, pageable);
return page;
}
}
더보기
package com.example.simpledms.controller.basic;
import com.example.simpledms.model.entity.basic.Dept;
import com.example.simpledms.service.basic.DeptService;
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.*;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : com.example.simpledms.controller.basic
* fileName : DeptController
* author : L.DH
* date : 2023-10-23
* description : 부서 컨트롤러
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* ———————————————————————————————
* 2023-10-23 L.DH 최초 생성
*/
@Slf4j
@RestController
@RequestMapping("/api/basic")
public class DeptController {
@Autowired
DeptService deptService; // DI 가져오기
// 전체 조회 + dname like 검색
@GetMapping("/dept")
public ResponseEntity<Object> find(
// TODO : 페이징 처리를 위한 공통 @RequestParam
@RequestParam(defaultValue = "") String dname,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
// TODO: 페이지 변수 저장 (page:현재페이지번호, size:1페이지당개수)
// 함수 매개변수 : Pageable (위의 값을 넣기)
// 사용법 : Pageable pageable = PageRequest.of(현재페이지번호, 1페이지당개수);
Pageable pageable = PageRequest.of(page, size);
// 전체 조회(dname="") + like 검색(dname="S")
Page<Dept> deptPage
= deptService.findAllDnameContaining(dname, pageable);
// 리액트 전송 : 부서 배열, 페이징 정보 [자료구조 : Map<키이름, 값>]
Map<String, Object> response = new HashMap<>();
response.put("dept", deptPage.getContent()); // 부서 배열
response.put("currentPage", deptPage.getNumber()); // 현재 페이지 번호
response.put("totalItems", deptPage.getTotalElements()); // 총 건수(개수)
response.put("totalPages", deptPage.getTotalPages()); // 총 페이지 수
if (deptPage.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);
}
}
} // end of class
추가페이지(저장) 메뉴
* 백앤드
1.application.properties
- 백앤드 (url 허용 설정 및 오라클DB log4j 적용 오라클 설정)
2. config./WebConfig
url 허용 설정
3.model./common
BaseTimeEntity
JPA 에서 자동으로 생성일자/수정일자 만들어주는 클래스
4. model./entity 실습 파일 넣기
5. 모델 클래스
* Soft Delete는 물리적인 데이터 삭제로 발생할 수 있는 문제를 방지하고 쉽게 복원할 필요가 있거나 삭제된 데이터들을 보관하여 데이터로써 활용할 필요나 가치가 있는 경우에 사용
* 실무에서는 법적으로 개인자료일 경우 3년 또는 1년이상 데이터를 보관할 의무가 있고 어길수 법적 문제가 생길 수 있음 -> 그래서 soft delete 방식을 대부분 구현하고 있음
6. JPA CRUD 인터페이스
- dname like : 쿼리메소드 + 페이징(리턴 : page, 매개변수 : pageable)
7. 서비스 클래스
- 전체 조회 + 페이징
- dname like 조회 + 페이징
8. 컨트롤러
- 전체 조회 + dname like 검색 공통 @RequestParam
- 페이지 변수 저장 함수 매개변수(page:현재페이지번호, size:1페이지당개수)
- 리액트 전송 : 부서 배열, 페이징 정보 [자료구조 : Map<키이름, 값>]
'Spring Boot' 카테고리의 다른 글
블로그 테마 선정 및 환경 설치 방법 (0) | 2023.10.31 |
---|---|
[React] 외부(external) JS파일 실습 (0) | 2023.10.30 |
CRUD JPA (0) | 2023.10.16 |
[SpringBoot] 데이터베이스(SQL&Docker) 활용한 수정 함수(업데이트) (0) | 2023.10.13 |
[SpringBoot] 데이터베이스(SQL&Docker) 활용한 저장 함수 (0) | 2023.10.13 |