안녕하세요 ! :)
front + backend 를 통합한 게시판을 생성하는 예제를 만들어보겠습니다.
📖 frontend작업
프론트 작업은 VSCode를 이용하여 프로젝트를 생성하였습니다.
frontend 프로젝트는 React + typescript를 이용하였습니다.
프로젝트 형태는 다음과 같습니다.
src 폴더 아래에는 다음과 같은 파일들이 들어갑니다.
- assets(css파일, 이미지파일, javascript 파일)
- compoents(common(헤더, 푸터, 타이틀))
- page(auth(로그인관련), 각종 메뉴 페이지들)
- common(공통으로 사용할 페이지 관련)
- service(벡엔드에 요청할 서비스 함수작성 파일)
- types(자바의 모델과 같은 파일)
- utils(백앤드 서버와 연동) 파일을 생성하였습니다.
1) 패키지 설치
# 설치 패키지
# 1) 메뉴 라이브러리 설치
npm i react-router-dom
# 2) 벡엔드 연동 라이브러리 설치 : ajax 업그레이드
npm i axios
# 3) pre css 컴파일러 : node-sass -> 더이상 안씀 : sass 설치할것
<!-- npm i node-sass -->
npm install sass
# 4) Material Page component 업그레이드
# 과거 v4 -> v5 변경 설치
npm i @mui/material @emotion/react @emotion/styled
# 4-1) 소스에서 임포트 사용법 : <Pagination />
import Pagination from '@mui/material/Pagination';
# 5) typescript jquery, jqueryui type 넣기
# 5-1) typescript jquery 사용
npm i --save-dev @types/jquery
2) axios 설정
📂 utils > http-commons.ts
이곳에서는 axios를 이용하여 벡엔드와 연동하는 작업을 하는 곳입니다.
import axios from "axios";
// todo : baseURL: "http://스프링ip주소:스프링포트번호/공통url"
export default axios.create({
baseURL: "http://localhost:8000/api",
headers: {
"Content-Type": "application/json"
}
});
base url에는 스프링부트주소를 입력합니다. 주소뒤에는 공통url인 api로 지정하였습니다.
3) 부서(Department) 게시판 생성하기
1) 부서 인터페이스 생성
📂 types > IDept.ts 생성
// 자바의 모델 클래스와 같음
// 인터페이스 : 속성의 자료형을 미리 지정하는 것
export default interface IDept {
dno?: any | null,
dname: string,
loc: string
}
2) 부서 서비스 생성
📂 service > DeptService.ts 생성
// DeptService.ts
// axios 통신을 위한 import 작업
import http from "../utils/http-common";
// type 인터페이스 import
import IDept from "../types/IDept";
// 화살표 함수 단축키 : nfn
/** 전체 조회 요청 함수 */
const getAll = () => {
// 조회요청 : .get("/url")
// 사용법 : http.get<리턴타입>("스프링부트의 컨트롤러url");
return http.get<Array<IDept>>("/dept");
};
/** 상세 조회(1건조회) 요청 함수 : 기본키 */
// 매개변수로는 부서번호(dno) type는 any
const get = (dno: any) => {
return http.get<IDept>(`/dept/${dno}`);
};
/** 저장 요청 함수 */
const create = (data: IDept) => {
return http.post<IDept>("/dept", data);
};
/** 수정 요청 함수 : 기본키와 객체 필요 */
const update = (dno: any, data: IDept) => {
return http.put<any>(`/dept/${dno}`, data);
};
/** 삭제 요청 함수 : 기본키(dno) */
const remove = (dno: any) => {
return http.delete<any>(`/dept/deletion/${dno}`);
};
/** 부서명 검색 요청 함수 */
const findByDname = (dname: string) => {
return http.get<Array<IDept>>(`/dept?dname=${dname}`);
};
// 함수 기능 내보내기
const DeptService = {
getAll,
get,
create,
update,
remove,
findByDname
}
export default DeptService;
3) 부서페이지 생성
📂 page> DeptListNop.tsx 생성
// DeptListNop.tsx
import React from 'react'
function DeptListNop() {
return (
<div>DeptListNop</div>
)
}
export default DeptListNop
3-1) App.ts 페이지 라우터 처리
import React from "react";
// app css import
import "./assets/css/app.css";
import HeaderCom from "./components/common/HeaderCom";
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/auth/Login";
import Register from "./pages/auth/Register";
import ForgotPassword from "./pages/auth/ForgotPassword";
import NotFound from "./pages/common/NotFound";
import DeptListNop from "./pages/dept-nop/DeptListNop";
import EmpListNop from "./pages/emp-nop/EmpListNop";
function App() {
return (
<div className="App">
<HeaderCom />
{/* <!-- 구분 막대 시작 --> */}
<div className="gutter text-center text-muted fade-in-box">
<div>클론 코딩 예제 사이트에 오신 것을 환영합니다.</div>
</div>
{/* <!-- 구분 막대 끝 --> */}
<div id="content-wrapper">
{/* 라우터 정의 시작 */}
<Routes>
{/* login */}
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
{/* dept */}
<Route path="/dept-nop" element={<DeptListNop />} />
{/* emp */}
<Route path="/emp-nop" element={<EmpListNop />} />
{/* NotFound */}
<Route path="*" element={<NotFound />} />
</Routes>
{/* 라우터 정의 끝 */}
</div>
</div>
);
}
export default App;
4) 서버 시작
5) 리스트 페이지 변수 및 함수 정의
// DeptListNop.tsx
import React, { useEffect, useState } from "react";
import TitleCom from "../../components/common/TitleCom";
import { Link } from "react-router-dom";
import IDept from "../../types/IDept";
import DeptService from "./../../services/DeptService";
function DeptListNop() {
// 변수 정의
// todo: 부서 배열 변수
const [dept, setDept] = useState<Array<IDept>>([]);
// todo: 검색어 변수
const [searchDname, setSearchDname] = useState<string>("");
// 함수 정의
// todo: useEffect : 화면이 뜨자마자 실행되는 이벤트 함수(1번)
// 사용법 : useEffect(()=>{실행문},[])
useEffect(() => {
// 전체 조회 실행
retrieveDept();
}, []);
// todo) 검색어 수동 바인딩 함수
const onChangeSearchDname = (e: any) => {
// todo) e.target : input 태그에 현재 걸린 이벤트
// => e.target.value : 현재 조작하는 태그의 value 값
setSearchDname(e.target.value);
};
// todo) 전체 조회 함수
const retrieveDept = () => {
DeptService.getAll() // backend에 전체조회요청
.then((response: any) => {
// todo 성공했을때 처리
setDept(response.data);
// todo 로그 찍기
console.log("response", response.data);
})
.catch((e: Error) => {
// todo 실패했을때 처리
console.log(e);
});
};
// todo) 검색어 조회 함수
const findByDname = () => {
DeptService.findByDname(searchDname) // backend 요청
.then((response: any) => {
// todo 성공했을때 처리
setDept(response.data);
// todo 로그 찍기
console.log("response", response.data);
})
.catch((e: Error) => {
// todo 실패했을때 처리
console.log(e);
});
};
return (
<>
{/* 제목 보여주는 공통 컴포넌트 */}
<TitleCom title="Dept List No Page" />
{/* 제목 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={findByDname}
>
Search
</button>
</div>
{/* 검색버튼 끝 */}
</div>
</div>
{/* dname end */}
{/* 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>
{/* 테이블 제목 끝 */}
{/* TODO: 테이블 데이터 시작 */}
<tbody>
{dept &&
dept.map((data) => (
<tr key={data.dno}>
<td>{data.dname}</td>
<td>{data.loc}</td>
<td>
<Link to={"/dept-nop/" + data.dno}>
<span className="badge bg-success">Edit</span>
</Link>
</td>
</tr>
))}
</tbody>
{/* 테이블 데이터 끝 */}
</table>
{/* table end */}
</div>
{/* table end */}
</>
);
}
export default DeptListNop;
6) 서버 재시작 및 결과 확인
📖 backend 작업
frontend 작업 후 backend 작업을 해봅시다 :)
backend 작업은 인텔리제이에서 프로젝트를 생성하였습니다.
1) backend 프로젝트 생성
프로젝트 이름은 SimpleDMS로 정하고 위치는 프론트엔드 작업물과 같은 폴더 위치로 지정합니다.
JDK는 azul-11 버전, Java 버전은 11버전으로 하고 패키지 생성은 War 파일로 선택하였습니다.
스프링부트 버전은 2.7.16 으로 하고 종속성 추가는
- Spring Boot DevTools
- Lombok
- Spring Web
- Spring Data JPA
- Oracle Driver
를 선택후 프로젝트를 생성합니다.
2) backend 프로젝트 환경설정
2-1) build.gradle 파일에 dependencies 추가
// 오라클 라이브러리( 19c )
implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
implementation 'com.oracle.database.security:osdt_core:19.14.0.0'
// logback , log4jdbc 설정
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
implementation 'ch.qos.logback:logback-classic:1.2.11'
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'org.slf4j:jcl-over-slf4j:1.7.36'
2-2) 프로퍼티 파일 한글 깨짐 방지를 위한 설정
설정 > 에디터 > 파일 인코딩 > 프로퍼티 파일에 대한 디폴트 인코딩을 UTF-8로 저장 후 아래 체크박스를 체크한후 확인
2-3) 프로퍼티 파일 설정
main > resources > application.properties 파일에 설정을 추가합니다.
# 서버 포트
server.port=8000
# 오라클 설정 : log4j 적용
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:oracle:thin:@localhost:오라클서버포트/db명
spring.datasource.username=서버id
spring.datasource.password=서버패스워드
# 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
# HikariCP settings
spring.datasource.hikari.minimumIdle=1
spring.datasource.hikari.maximumPoolSize=1
spring.datasource.hikari.poolName=HikariPoolBooks
2-4) 실습용 스크립트 logback 추가
실습용 스크립트 파일 및 logback 파일을 resources 파일 아래에 추가합니다.
1. data.sql
INSERT INTO TB_DEPT
VALUES (SQ_DEPT.nextval, 'ACCOUNTING', 'NEW YORK','N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_DEPT
VALUES (SQ_DEPT.nextval, 'RESEARCH', 'DALLAS', 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_DEPT
VALUES (SQ_DEPT.nextval, 'SALES', 'CHICAGO', 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_DEPT
VALUES (SQ_DEPT.nextval, 'OPERATIONS', 'BOSTON', 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'SMITH', 'CLERK', 7902, TO_CHAR(to_date('17-12-1980', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 800, NULL,
20, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'ALLEN', 'SALESMAN', 7698, TO_CHAR(to_date('20-2-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1600,
300, 30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'WARD', 'SALESMAN', 7698, TO_CHAR(to_date('22-2-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1250, 500,
30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'JONES', 'MANAGER', 7839, TO_CHAR(to_date('2-4-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 2975, NULL,
20, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'MARTIN', 'SALESMAN', 7698, TO_CHAR(to_date('28-9-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1250,
1400, 30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'BLAKE', 'MANAGER', 7839, TO_CHAR(to_date('1-5-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 2850, NULL,
30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'CLARK', 'MANAGER', 7839, TO_CHAR(to_date('9-6-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 2450, NULL,
10, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'SCOTT', 'ANALYST', 7566, TO_CHAR(to_date('13-07-1987', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 3000,
NULL, 20, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'KING', 'PRESIDENT', NULL, TO_CHAR(to_date('17-11-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 5000,
NULL, 10, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'TURNER', 'SALESMAN', 7698, TO_CHAR(to_date('8-9-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1500, 0,
30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'ADAMS', 'CLERK', 7788, TO_CHAR(to_date('13-07-1987', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1100, NULL,
20, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'JAMES', 'CLERK', 7698, TO_CHAR(to_date('3-12-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 950, NULL,
30, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'FORD', 'ANALYST', 7566, TO_CHAR(to_date('3-12-1981', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 3000, NULL,
20, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
INSERT INTO TB_EMP
VALUES (SQ_EMP.nextval, 'MILLER', 'CLERK', 7782, TO_CHAR(to_date('23-1-1982', 'dd-mm-yyyy'), 'YYYY-MM-DD HH24:MI:SS'), 1300, NULL,
10, 'N', TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'),NULL, NULL);
COMMIT;
2. log4j 프로퍼티 파일
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0
3. logback xml 파일
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- Appenders -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %5p [%c] %m%n</Pattern>
</encoder>
</appender>
<appender name="console-infolog" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %5p %m%n</Pattern>
</encoder>
</appender>
<!-- Logger -->
<logger name="com.example.simpledms" level="DEBUG" appender-ref="console" />
<logger name="jdbc.sqlonly" level="INFO" appender-ref="console-infolog" />
<logger name="jdbc.resultsettable" level="INFO" appender-ref="console-infolog" />
<!-- Root Logger -->
<root level="off">
<appender-ref ref="console" />
</root>
</configuration>
4. schema.sql 파일
DROP SEQUENCE SQ_DEPT;
CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY 10;
DROP SEQUENCE SQ_EMP;
CREATE SEQUENCE SQ_EMP START WITH 8000 INCREMENT BY 1;
DROP TABLE TB_EMP CASCADE CONSTRAINT;
DROP TABLE TB_DEPT CASCADE CONSTRAINT;
CREATE TABLE TB_DEPT
(
DNO NUMBER NOT NULL PRIMARY KEY,
DNAME VARCHAR2(255),
LOC VARCHAR2(255),
DELETE_YN VARCHAR2(1) DEFAULT 'N',
INSERT_TIME VARCHAR2(255),
UPDATE_TIME VARCHAR2(255),
DELETE_TIME VARCHAR2(255)
);
CREATE TABLE TB_EMP
(
ENO NUMBER NOT NULL PRIMARY KEY,
ENAME VARCHAR2(255),
JOB VARCHAR2(255),
MANAGER NUMBER,
HIREDATE VARCHAR2(255),
SALARY NUMBER,
COMMISSION NUMBER,
DNO NUMBER,
DELETE_YN VARCHAR2(1) DEFAULT 'N',
INSERT_TIME VARCHAR2(255),
UPDATE_TIME VARCHAR2(255),
DELETE_TIME VARCHAR2(255)
);
2-5) com.example.simpledms > 패키지 폴더 생성
아래와 같이 설정, 컨트롤러, 모델, 레포지토리, 서비스 폴더를 생성합니다.
3) 파일 생성
3-1) model > BaseTimeEntity.java 생성
📂 model > BaseTimeEntity.java
package com.example.simpledms.model;
import lombok.Getter;
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
// @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";
}
}
3-2) model > Dept/Emp.java 생성
📂 Dept.java
package com.example.simpledms.model;
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 : 부서 모델 클래스
* 요약 :
* Hard Delete : 실제 데이터를 삭제하는 행위
* 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
// 사용법 : 1) @Where(clause = "DELETE_YN = 'N'") : select 될 때 조건("DELETE_YN = 'N'")을 강제로 붙여줌
// 2) @SQLDelete(sql = "대체 sql문") : 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"
)
@Column
private Integer dno;
// 부서이름
@Column
private String dname;
// 부서위치
@Column
private String loc;
}
📂 Emp.java
package com.example.simpledms.model;
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.*;
@Entity
@Table(name = "TB_EMP")
@SequenceGenerator(
name = "SQ_EMP_GENERATOR"
, sequenceName = "SQ_EMP"
, initialValue = 1
, allocationSize = 1
)
@Setter
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
// soft delete
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE TB_EMP SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') WHERE ENO = ?")
public class Emp extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE
, generator = "SQ_EMP_GENERATOR"
)
@Column
private Integer eno;
@Column
private String ename;
@Column
private String job;
@Column
private Integer manager;
@Column
private String hiredate;
@Column
private Integer salary;
@Column
private Integer commission;
@Column
private Integer dno;
}
3-3) DeptRepository.java 생성
📂 repository > DeptRepository.java
package com.example.simpledms.repository;
import com.example.simpledms.model.Dept;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DeptRepository extends JpaRepository<Dept, Integer> {
// dname like 검색 - 쿼리메소드
List<Dept> findAllByDnameContaining(String dname);
}
3-4) DeptService.java 생성
📂 service > DeptService.java
package com.example.simpledms.service;
import com.example.simpledms.model.Dept;
import com.example.simpledms.repository.DeptRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptService {
@Autowired
DeptRepository deptRepository; // DI
/** 전체조회 */
public List<Dept> findAll(){
List<Dept> list = deptRepository.findAll();
return list;
}
/** 검색어(dname like) 조회 함수 */
public List<Dept> findAllByDnameContaining(String dname){
List<Dept> list = deptRepository.findAllByDnameContaining(dname);
return list;
}
}
3-5) 프론트<->벡엔드 통신을 위해 CORS 보안 허용 설정 파일 생성
📂 config > WebConfig.java
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 :
* @Configuration : 자바클래스에 설정 기능을 부여하는 어노테이션 (application.properties 비슷)
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2022/06/14 kangtaegyung 최초 생성
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 아래 url 허용
// 사용법 : .allowedOrigins("http://허용할 ip:허용할Port")
.allowedOrigins("http://localhost:3000")
// Todo: 아래 추가해야 update, delete, insert, select 가 cors 문제가 안생김
.allowedMethods(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()
);
}
}
3-6) DeptController.java 생성
📂 controller > DeptController.java
package com.example.simpledms.controller;
import com.example.simpledms.model.Dept;
import com.example.simpledms.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.List;
/**
* packageName : com.example.simpledms.controller
* fileName : DeptController
* author : GGG
* date : 2023-10-19
* description : 부서 컨트롤러 (@RestController - react용)
* 요약 :
* react(3000) <-> springboot(8000) 연동 : axios
* 인터넷 기본 보안 : ip, port 최초에 지정된 것과 달라지면
* => 해킹으로 기본 인정 (블러킹 : 단절) : CORS 보안
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-19 GGG 최초 생성
*/
@Slf4j
@RestController
@RequestMapping("/api")
public class DeptController {
@Autowired
DeptService deptService; // DI
/**
* 전체 조회 + like 검색
*/
@GetMapping("/dept")
public ResponseEntity<Object> getDeptAll(
@RequestParam(defaultValue = "") String dname) {
try {
// 전체 조회 + like 검색
List<Dept> list = deptService.findAllByDnameContaining(dname);
if (list.isEmpty() == false) {
// 성공
return new ResponseEntity<>(list, HttpStatus.OK);
} else {
// 데이터 없음
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
3-7) REST API 테스트
3-8) 프론트 연동 테스트
'Spring Boot' 카테고리의 다른 글
JWT (0) | 2024.01.26 |
---|---|
스프링 시큐리티 (0) | 2024.01.26 |