+ 00 00 0000

Have any Questions?

15_Simple Coding – JPA – 실전예제 #2 : 파일업로드

15_Simple Coding – JPA – 실전예제 #2 : 파일업로드

📃 요약

네이버 카페 등의 게시판을 기본 기능을 제작하는 예제입니다.
기본적인 DB CRUD 를 사용해 제작합니다.
DB 프레임워크는 JPA 를 사용해 자동화기능을 강화합니다.

요소 기술 및 테이블 설계는 아래와 같습니다.

요소 기술 :

– 프론트엔드 : JSP

– 벡엔드 : 스프링부트 & JPA & Oracle 18xe(Oracle Cloud 19c)

결과 화면 :

프로젝트 탐색기 :

함수 URL :

메소드URL설명
GET/advanced/fileDb전체 조회
GET/advanced/fileDb/get/{uuid}상세조회
GET/advanced/fileDb/addition저장(추가) 페이지 열기
POST/advanced/fileDb/add버튼 클릭시 저장 함수
GET/advanced/fileDb/{uuid}이미지 다운로드 함수, return(JSON 데이터)
GET/advanced/fileDb/edition/{uuid}수정 페이지 열기
PUT/advanced/fileDb/edit/{uuid}수정
DELETE/advanced/fileDb/delete/{uuid}삭제

📃 기술 구현

스펙 :

- jdk 17
- spring boot 3.x
- gradle

테이블 설계

CREATE TABLE TB_FILE_DB
(
    UUID         VARCHAR2(1000) NOT NULL PRIMARY KEY, -- 파일 UUID
    FILE_TITLE   VARCHAR2(1000),           -- 제목
    FILE_CONTENT VARCHAR2(1000),           -- 내용
    FILE_NAME    VARCHAR2(1000),           -- 파일명
    FILE_DATA    BLOB,                     -- 바이너리 파일(이미지파일)
    FILE_URL     VARCHAR2(1000),           -- 파일 다운로드 URL
    DELETE_YN    VARCHAR2(1) DEFAULT 'N',
    INSERT_TIME  VARCHAR2(255),
    UPDATE_TIME  VARCHAR2(255),
    DELETE_TIME  VARCHAR2(255)
);

업로드 게시판 구현을 위한 테이블 설계입니다.

데이터베이스를 오라클을 사용하여 구현해 보겠습니다.

파일 업로드 로직

파일 업로드
파일을 업로드하는 방법은 대체적으로 2가지임
1) DB 에 파일을 업로드하는 방법과
2) 서버컴퓨터의 일정 폴더에 업로드하는 방법이 있음
DB 업로드를 진행하면 오라클 SQL 을 이용해서 간편하게 업로드가 가능하고, 수정/삭제 등 관리가 용이함
요즘은 DB 보다 가격이 싼 아마존 클라우드(AWS) 의 S3 공간을 임대해서 파일을 업로드하고 사용하기도 함 , 이때는 아마존에서 제공하는 함수를 이용해서 업로드 / 삭제를 진행함
이 예제에서는 과거부터 현재까지 이용되고 있는 DB 에 파일을 저장하는 방법으로 진행하기로 함

  • 특이사항 :
  • UUID 를 사용하여 유일한 값을 저장(시퀀스 사용 않함)
  • 프론트 서비스 함수
    1) 업로드(저장/수정) : FormData 객체를 사용하여 (키, 값) 형태로 저장 후 벡엔드 전송
    2) 헤더 : multipart/form-data 타입으로 전송
  • 벡엔드 엔티티 객체 정의
    1) DB 에 저장하는 이미지 는 BLOB 타입으로 테이블 정의
    2) 엔티티 정의 시 첨부파일 속성은 @Lob 를 붙이고, 타입은 byte[] 로 정의해야함
    3) 서비스 함수에서 저장시 이미지를 다운로드 받을 url 을 생성하여 테이블에 저장하고 프론트의 img 태그에서 그 url 넣어 화면에 이미지를 표시함
    4) 컨트롤러 함수에서 이미지 다운로드 함수를 추가 정의함
    이때 ResponseEntitiy 객체에 헤더/바디를 구분하여 프론트쪽으로 전달해야 이미지 다운로드가 됨
  • 헤더 : CONTENT_DISPOSITION, attachment; filename=”첨부파일명” 이 포함
  • 바디 : 이미지 데이터 저장

모델 : 엔티티

공통 모델

package com.example.jpaexam.model.common;

import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * packageName : com.example.jpaexam.model
 * fileName : BaseTimeEntity
 * author : GGG
 * date : 2023-10-16
 * description : JPA 에서 자동으로 생성일자/수정일자를 만들어 주는 클래스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Getter
// todo: 자동으로 생성일자/수정일자 컬럼을 sql 문에 추가시키는 어노테이션 2개
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
//    todo: 공통속성 : yyyy-MM-dd HH:mm:ss 아니고 기본 패턴으로 보임
    private String insertTime;

    private String updateTime;

//    todo: 해당 테이블에 데이터가 만들어 질때(insert 문) 실행되는 이벤트 함수
    @PrePersist
    void OnPrePersist() {
        this.insertTime
                = LocalDateTime.now()
                    .format(DateTimeFormatter
                            .ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

//    todo: 해당 테이블에 데이터가 수정 질때(update 문) 실행되는 이벤트 함수
    @PreUpdate
    void OnPreUpdate() {
        this.updateTime
                = LocalDateTime.now()
                .format(DateTimeFormatter
                        .ofPattern("yyyy-MM-dd HH:mm:ss"));
        this.insertTime = this.updateTime; // 생성일시 == 수정일시 동일하게 처리
    }
}

파일 업로드 레포지토리

package com.example.jpaexam.repository.advanced;

import com.example.jpaexam.model.entity.advanced.FileDb;
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.jpaexam.repository
 * fileName : DeptRepostory
 * author : kangtaegyung
 * date : 2022/10/16
 * description : JPA CRUD 인터페이스 ( DAO 역할 )
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/10/16         kangtaegyung          최초 생성
 */
@Repository
public interface FileDbRepository extends JpaRepository<FileDb, String> {

//    title like 검색
    Page<FileDb> findAllByFileTitleContaining(String fileTitle, Pageable pageable);
}

select : @Query 이용한 오라클 기반 쿼리(nativeQuery = true) 임

파일 업로드 게시판 서비스

package com.example.jpaexam.service.advanced;

import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.repository.advanced.FileDbRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.io.IOException;
import java.util.Optional;
import java.util.UUID;

/**
 * packageName : com.example.modelexam.service
 * fileName : DeptService
 * author : kangtaegyung
 * date : 2022/10/12
 * description : Qna 서비스 클래스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/10/12         kangtaegyung          최초 생성
 */
@Slf4j
@Service
public class FileDbService {

    @Autowired
    FileDbRepository fileDbRepository; // 샘플데이터 DB에 접근하는 객체

    public Page<FileDb> findAllByFileTitleContaining(String fileTitle, Pageable pageable) {
        Page<FileDb> page = fileDbRepository.findAllByFileTitleContaining(fileTitle, pageable);
        return page;
    }

    public Page<FileDb> findAll(Pageable pageable) {
        Page<FileDb> page = fileDbRepository.findAll(pageable);
        return page;
    }

    public Optional<FileDb> findById(String uuid) {
        return fileDbRepository.findById(uuid);
    }

    // TODO: uuid 를 생성해서 insert 또는 기존 uuid 를 전달받아 update
    public FileDb save(String uuid, String fileTitle, String fileContent, MultipartFile file) throws IOException {

        FileDb fileDb2 = null;

        try {
            //        기본키가 없으면 insert
            if (uuid == null) {
                //        램덤 고유값 구하기 : UUID, 중간에 - 있음, uuid 생성
                String tmpUuid = UUID.randomUUID().toString().replace("-", "");

                //        다운로드 url 만들기
                String fileDownloadUri = ServletUriComponentsBuilder
                        .fromCurrentContextPath()
                        .path("/advanced/fileDb/")
                        .path(tmpUuid)
                        .toUriString();

                FileDb fileDb = new FileDb(tmpUuid, fileTitle, fileContent, file.getOriginalFilename(), file.getBytes(), fileDownloadUri);
                fileDb2 = fileDbRepository.save(fileDb);
            } else {
//        기본키가 있으면 update
                //        다운로드 url 만들기, 기존 uuid 사용
                String fileDownloadUri = ServletUriComponentsBuilder
                        .fromCurrentContextPath()
                        .path("/advanced/fileDb/")
                        .path(uuid)
                        .toUriString();

                FileDb fileDb = new FileDb(uuid, fileTitle, fileContent, file.getOriginalFilename(), file.getBytes(), fileDownloadUri);
                fileDb2 = fileDbRepository.save(fileDb);
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
        }

        return fileDb2;
    }

    public boolean removeById(String uuid) {

        if (fileDbRepository.existsById(uuid)) {
            fileDbRepository.deleteById(uuid);
            return true;
        }
        return false;
    }
}

파일 업로드 게시판 컨트롤러

package com.example.jpaexam.controller.advanced;

import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.service.advanced.FileDbService;
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.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.view.RedirectView;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * packageName : com.example.modelexam.controller
 * fileName : FileDbController
 * author : kangtaegyung
 * date : 2022/10/12
 * description : 부서 컨트롤러
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022/10/12         kangtaegyung          최초 생성
 */
@Slf4j
@Controller
@RequestMapping("/advanced")
public class FileDbController {

    @Autowired
    FileDbService fileDbService;

    @GetMapping("/fileDb")
    public String findAllByFileTitleContaining(
            @RequestParam(defaultValue = "") String fileTitle
            , @RequestParam(defaultValue = "0") int page
            , @RequestParam(defaultValue = "3") int size
            , Model model
    ) {
//      todo: 페이징 요청 객체에 정보 저장
//        page : 현재페이지 번호, size : 1 페이지당 개수
        Pageable pageable = PageRequest.of(page, size);

//      todo: 전체 조회 함수 호출
//        다운로드 url 을 만들어 DTO 에 저장함
        Page<FileDb> pageRes = fileDbService.findAllByFileTitleContaining(fileTitle, pageable);

//      todo: jsp 정보전달( 부서배열, 페이징정보 )
        model.addAttribute("fileDb", pageRes.getContent()); // 이미지배열
        model.addAttribute("currentPage", pageRes.getNumber()); // 현재 페이지 번호(0 ~)
        model.addAttribute("totalItems", pageRes.getTotalElements()); // 전체 테이블 건수
        model.addAttribute("totalPages", pageRes.getTotalPages()); // 전체 페이지 개수

        long blockStartPage = 0; // 블럭 시작 페이지 번호(0 ~)
        long blockEndPage = 0;   // 현재 끝 페이지 번호

//        테이블 데이터가 있다면 페이지 계산 후 jsp 로 전송
        if (pageRes.getTotalElements() > 0) {
//        공식 : 블럭 시작페이지 번호 = 현재페이지번호 * 1페이지당개수
            blockStartPage = ((int) Math.floor((double) (pageRes.getNumber()) / size) * size);
//        공식 : 블럭 끝페이지 번호 = 블럭 시작페이지번호 + 1페이자당개수 - 1
//           만약 블럭 끝페이지번호 >= 전체페이지수 =>(오류이므로 오류보정) 블럭끝페이지번호 = 전체페이지수 -1 넣기
            blockEndPage = blockStartPage + size - 1;
            blockEndPage = (blockEndPage >= pageRes.getTotalPages()) ? pageRes.getTotalPages() - 1 : blockEndPage;
        }

        model.addAttribute("startPage", blockStartPage); // 블럭 시작 페이지 번호(0 ~)
        model.addAttribute("endPage", blockEndPage); // 현재 끝 페이지 번호

//        log.debug(model.toString()); // 로그 출력

        return "advanced/fileDb/fileDb_all.jsp";
    }

    //    화면 출력 시 자동적으로 ID로 조회됨(이미지 id 는 uuid 임)
//    jsp 에서  실행될때 자동 실행되는 함수
    @GetMapping("/fileDb/{uuid}")
    @ResponseBody
    public ResponseEntity<byte[]> findByIdDownloading(@PathVariable String uuid) {
        FileDb fileDb = fileDbService.findById(uuid).get();

        return ResponseEntity.ok()
//           Todo : attachment: => attachment;
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileDb.getFileName() + "\"")
                .body(fileDb.getFileData());
    }

    /**
     * 저장함수 : 저장 페이지로 이동
     */
    @GetMapping("/fileDb/addition")
    public String addFileDb() {
        return "advanced/fileDb/add_fileDb.jsp";
    }

    /**
     * 저장함수 : db 저장
     */
    @PostMapping("/fileDb/add")
    public RedirectView createFileDb(
            @RequestParam String fileTitle,
            @RequestParam String fileContent,
            @RequestParam MultipartFile image
    ) {
        try {
            fileDbService.save(null, fileTitle, fileContent, image);

        } catch (Exception e) {
            log.debug(e.getMessage());
        }

//      전체 조회 페이지로 강제 이동
        return new RedirectView("/advanced/fileDb");
    }

    @DeleteMapping("/fileDb/delete/{uuid}")
    public RedirectView delete(@PathVariable String uuid) {

        fileDbService.removeById(uuid); // db 삭제

        return new RedirectView("/advanced/fileDb");
    }

    /** 수정함수 : 수정 페이지로 이동 + 상세조회(1건조회) */
    @GetMapping("/fileDb/edition/{uuid}")
    public String editDept(@PathVariable String uuid,
                           Model model
    ) {
//      서비스 상세조회 함수 호출
        Optional<FileDb> optionalFileDb = fileDbService.findById(uuid);

//      jsp 전달
        model.addAttribute("fileDb", optionalFileDb.get());

        return "advanced/fileDb/update_fileDb.jsp";
    }

    /** 수정함수 : db 수정 저장 */
    @PutMapping("/fileDb/edit/{uuid}")
    public RedirectView updateDept(@RequestParam String uuid,
                                   @RequestParam String fileTitle,
                                   @RequestParam String fileContent,
                                   @RequestParam MultipartFile image
    ) {
        try {
            fileDbService.save(uuid, fileTitle, fileContent, image);

        } catch (Exception e) {
            log.debug(e.getMessage());
        }

//      전체 조회 페이지로 강제 이동
        return new RedirectView("/advanced/fileDb");
    }
}

jsp 페이지

jsp 전체조회 페이지

결과 화면 :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<%--header--%>
<jsp:include page="../../common/header.jsp"/>

<div class="container">
    <%--    todo: 검색어 시작--%>
    <%--     form 안에 input 태그의 value 값들이 -> springboot 함수로 전달됨 --%>
    <form class="row g-3 justify-content-center" action="/advanced/fileDb" method="get">
        <div class="col-auto">
            <%--            todo: 제목 라벨 --%>
            <label for="fileTitle" class="visually-hidden">searchTitle</label>
            <%--            todo: 검색창--%>
            <input type="text" class="form-control" id="fileTitle" placeholder="fileTitle" name="fileTitle">
            <%--            todo: hidden(숨김) page = 0, size = 3 --%>
            <input type="hidden" class="form-control" id="page" name="page" value="0">
            <input type="hidden" class="form-control" id="size" name="size" value="3">
        </div>
        <div class="col-auto">
            <button type="submit" class="btn btn-primary mb-3">Search</button>
        </div>
    </form>
    <%--    todo: 검색어 끝--%>

    <%--    todo: 테이블 반복문 시작--%>
    <div class="row">
        <c:forEach var="data" items="${fileDb}">
            <div class="col-3">
                <div class="card" style="width: 18rem;">
                    <img src="${data.fileUrl}" class="card-img-top" alt="강의"/>
                    <div class="card-body">
                        <h5 class="card-title"><a href="/advanced/fileDb/edition/${data.uuid}">${data.fileTitle}</a>
                        </h5>
                        <p class="card-text">${data.fileContent}</p>
                        <form id="delete-form" action="/advanced/fileDb/delete/${data.uuid}" method="post">
                                <%--    TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 :  delete 방식으로 인식해서 연결해줌    --%>
                            <input type="hidden" name="_method" value="delete"/>
                            <button type="submit" class="btn btn-danger btn-sm">Delete</button>
                        </form>
                    </div>
                </div>
            </div>
        </c:forEach>
    </div>

    <%--    todo: 테이블 반복문 끝--%>

    <%--    todo: 페이지 번호 시작--%>
    <div class="d-flex justify-content-center">
        <ul class="pagination">
            <%--                todo: 첫페이지 번호 --%>
            <%--                 startPage : 0부터 시작 --%>
            <%--                 currentPage : 0부터 시작--%>
            <li class="page-item ${(startPage+1==1)? 'disabled': ''}">
                <a class="page-link" href="/basic/dept?page=${startPage-1}&size=">Previous</a>
            </li>
            <%--                todo: 실제 페이지 번호들 --%>
            <%--                  사용법 : <c:forEach var="data" begin="시작값" end="끝값">반복문</c:forEach>--%>
            <c:forEach var="data" begin="${startPage}" end="${endPage}">
                <li class="page-item ${(currentPage==data)? 'active': ''}">
                    <a class="page-link" href="/basic/dept?page=${data}&size=">
                            ${data+1}
                    </a>
                </li>
            </c:forEach>
            <%--                todo: 끝페이지 번호--%>
            <li class="page-item ${(endPage+1==totalPages)? 'disabled': ''}">
                <a class="page-link" href="/basic/dept?page=${endPage+1}&size=">Next</a>
            </li>
        </ul>
    </div>
    <%--    todo: 페이지 번호 끝--%>

    <%--    todo: Add 버튼 추가 --%>
    <div class="text-center">
        <a href="/advanced/fileDb/addition" class="btn btn-primary center">Add</a>
    </div>
</div>

<script>
    let obj2 = "${currentPage}";
    let obj3 = "${totalItems}";
    let obj4 = "${totalPages}";
    let obj5 = "${startPage}";
    let obj6 = "${endPage}";
    console.log("currentPage", obj2);
    console.log("totalItems", obj3);
    console.log("totalPages", obj4);
    console.log("startPage", obj5);
    console.log("endPage", obj6);
</script>

<%--footer--%>
<jsp:include page="../../common/footer.jsp"/>

</body>
</html>

jsp 추가 페이지

결과 화면 :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<%--  header 시작 --%>
<jsp:include page="../../common/header.jsp"/>
<%--  header 끝 --%>

<div class="container mt-5">

    <div>
        <%--     todo: /advanced/fileDb/add 해당되는 컨트롤러 함수 실행   --%>
        <form action="/advanced/fileDb/add" method="post" enctype="multipart/form-data">
            <%--         todo: 이름   --%>
            <div class="mb-3">
                <label for="fileTitle" class="form-label">이름</label>
                <input type="text" class="form-control" id="fileTitle" required name="fileTitle">
            </div>
            <%--         todo: 내용 --%>
            <div class="mb-3">
                <label for="fileContent" class="form-label">내용</label>
                <input type="text" class="form-control" id="fileContent" required name="fileContent">
            </div>
            <%--         todo: 업로드 --%>
            <div class="input-group mb-3">
                <input
                        type="file"
                        class="form-control"
                        name="image"
                />
                <button
                        class="btn btn-outline-secondary"
                        type="submit"
                >
                    Upload
                </button>
            </div>
        </form>
    </div>

</div>

<%--  header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%--  header 끝 --%>
</body>
</html>

jsp 수정 페이지

결과 화면 :

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<!-- thymeleaf 설정 -->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<%--  header 시작 --%>
<jsp:include page="../../common/header.jsp"/>
<%--  header 끝 --%>

<div class="container mt-5">

    <div>
        <form action="/advanced/fileDb/edit/${fileDb.uuid}" method="post" enctype="multipart/form-data">
            <%--    TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 :  put 방식으로 인식해서 연결해줌    --%>
            <input type="hidden" name="_method" value="put"/>

            <input type="hidden" name="uuid" value="${fileDb.uuid}"/>

            <div class="mb-3">
                <label for="fileTitle" class="form-label">이름</label>
                <input type="text"
                       class="form-control"
                       id="fileTitle"
                       required
                       name="fileTitle"
                       value="${fileDb.fileTitle}"
                >
            </div>
            <div class="mb-3">
                <label for="fileContent" class="form-label">내용</label>
                <input type="text"
                       class="form-control"
                       id="fileContent"
                       required
                       name="fileContent"
                       value="${fileDb.fileContent}"
                >
            </div>
            <div class="mb-3 col-md-12" style="width: 18rem;">
                <img src="${fileDb.fileUrl}" class="card-img-top" alt="강의"/>
            </div>
            <%--         todo: 업로드 --%>
            <div class="input-group mb-3">

                <input
                        type="file"
                        class="form-control"
                        name="image"
                />
                <button
                        class="btn btn-outline-secondary"
                        type="submit"
                >
                    Update
                </button>
            </div>
        </form>
    </div>

</div>

<%--  header 시작 --%>
<jsp:include page="../../common/footer.jsp"/>
<%--  header 끝 --%>
</script>
</body>
</html>

📃 결론

부서 게시판 샘플 예제를 살펴보았습니다.
기본적은 CRUD 기능을 구현했으며 게시판의 페이징 처리도 해봤습니다.

DB 프레임워크는 JPA 를 이용해서 sql 문을 직접 제작하지 않고 자동화기능을 이용해 구현했습니다.
Mybatis 의 직접 sql 문 제작 기능에 대응해 자주 반복되고 쉬운 기능은 JPA 의 sql 자동생성 기능을 이용하고,
복잡한 sql 문은 @Query 를 이용해 직접 작성할 수 있어 요즘 많이 서비스 업체 기준으로 사용되고 있습니다.

감사합니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다