반응형

안녕하세요 ! :)

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 으로 하고 종속성 추가는

  1. Spring Boot DevTools
  2. Lombok
  3. Spring Web
  4. Spring Data JPA
  5. 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

+ Recent posts