+ 00 00 0000

Have any Questions?

15_Simple Coding – JPA – 실전예제 #1

15_Simple Coding – JPA – 실전예제 #1

📃 요약

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

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

요소 기술 :

– 프론트엔드 : JSP

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

결과 화면 :

프로젝트 탐색기 :

함수 URL :

메소드URL설명
GETdept전체 조회
GETdept/{dno}상세조회
GET/dept/addition추가페이지 열기
POST/dept/add저장
GET/dept/edition/{dno}수정페이지 열기(상세조회)
PUT/dept/edit/{dno}수정
DELETE/dept/delete/{dno}삭제

📃 기술 구현

스펙 :

- jdk 17
- spring boot 3.x
- gradle

테이블 설계

-- 부서 게시판
DROP SEQUENCE SQ_DEPT;
CREATE SEQUENCE SQ_DEPT START WITH 50 INCREMENT BY  10;

CREATE TABLE TB_DEPT (
                         DNO NUMBER NOT NULL PRIMARY KEY,
                         DNAME VARCHAR2(255),
                         LOC VARCHAR2(255),
                         INSERT_TIME VARCHAR2(255),
                         UPDATE_TIME VARCHAR2(255)
);

부서 게시판 구현을 위한 테이블 설계입니다.

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

모델 : 엔티티

공통 모델

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 {
    private String insertTime;

    private String updateTime;
}

부서 게시판 : 엔티티

package com.example.jpaexam.model.entity.basic;

import com.example.jpaexam.model.common.BaseTimeEntity;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

/**
 * packageName : com.example.jpaexam.model
 * fileName : Dept
 * author : GGG
 * date : 2023-10-16
 * description : 부서 모델 클래스 ( 엔티티(entity) )
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
// todo: @Entity - JPA 기능을 클래스에 부여하는 어노테이션
@Entity
// todo: @Table(name = "생성될테이블명")
@Table(name = "TB_DEPT")
// todo 사용법 : @SequenceGenerator(
//        name = "시퀀스함수이름"
//        , sequenceName = "DB에생성된시퀀스이름"
//        , initialValue = 시작값
//        , allocationSize = jpa에서관리용숫자(성능지표)
//)
@SequenceGenerator(
        name = "SQ_DEPT_GENERATOR"
        , sequenceName = "SQ_DEPT"
        , initialValue = 1
        , allocationSize = 1
)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
// todo: jpa 어노테이션 sql 자동 생성시 null 값 컬럼은 제외하고 생성
//   예) insert into 테이블명(컬럼1, 컬럼2, 컬럼3) values(1, 2, null);
//    => insert into 테이블명(컬럼1, 컬럼2) values(1, 2);
@DynamicInsert
@DynamicUpdate
public class Dept extends BaseTimeEntity {
    @Id
//  todo: @GeneratedValue(strategy = GenerationType.SEQUENCE
//            , generator = "시퀀스함수이름"
//    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            , generator = "SQ_DEPT_GENERATOR"
    )
    private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여

    private String dname; // 부서명

    private String loc;   // 부서위치
}

JPA 어노테이션 :

(1) 클래스에 붙임

  1) @Entity 
      : JPA 프레임워크 사용시 기능을 대상 클래스 부여하는 어노테이션
  2) @Table(name = "테이블명") 
      : JPA ddl(테이블,인덱스등생성) 생성기능 사용시
          그 이름으로 DB 생성해주는 어노테이션
  3) @SequenceGenerator(
                     name = "시퀀스_제너레이터이름"
                     , sequenceName = "DB시퀀스명"
                     , initialValue = 초기값   ( 시퀀스 처음값 )
                     , allocationSize = 할당값 (JPA 공간에서 생성될 값: 보통 1 사용)
      : 오라클 DB 제품의 시퀀스를 JPA 에서 사용하기 위한 어노테이션,
         클래스 위에 붙임
  4) @DynamicInsert(옵션) 
       : 옵션 기능임, JPA 에서 insert SQL 자동 생성시
        null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
        예) insert into dept(dno, dname, loc)
           values(1,2, null);
          => insert into dept(dno, dname)
             values(1,2); // 이렇게 바뀜(null 에러 방지)
  5) @DynamicUpdate(옵션) 
       : 옵션 기능임, JPA 에서 update SQL 자동 생성시
         null 값 들어오는 컬럼은 제외하고 sql 작성해주는 어노테이션
  6) 기타 
       : 롬북 어노테이션 상황에따라 추가 (setter/getter 등)
 ```

 (2) 속성(필드)에 붙임
 ```
   1) @Id 
        : 속성(필드) 기본키를 정의하는 어노테이션 (필수)
   2) @GeneratedValue(strategy = GenerationType.SEQUENCE
                    , generator = "시퀀스_제너레이터이름"
        : 시퀀스를 어느 속성(필드)에 연결할 것인가를 지시하는 어노테이션
          보통 기본키 컬럼(속성)에 사용함
  3) @Column(columnDefinition = "DB컬럼자료형")
        : 생략가능, JPA 의 ddl(테이블,인덱스등) 생성기능을 사용한다면 DB 테이블을 만들때
          지시된 자료형으로 생성함

부서 레포지토리

package com.example.jpaexam.repository.basic;

import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.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.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * packageName : com.example.jpaexam.repository
 * fileName : DeptRepository
 * author : GGG
 * date : 2023-10-16
 * description : JPA 레포지토리 인터페이스 ( DB 접속 함수들(CRUD) 있음)
 * == DAO 비슷함
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
// todo: @Repository - 클래스 위에 붙이고, 스프링서버가 실행될때 자동으로
//      객체 1개를 만들어줌 ( IOC )
//   사용법 : 인터페이스명 extends JpaRepository<모델클래스명, 기본키의자료형>
@Repository
public interface DeptRepository extends JpaRepository<Dept, Integer> {
    //    dname like 검색
//    Page<Dept> findAllByDnameContaining(String dname, Pageable pageable);
    @Query(value = "SELECT TD.* FROM TB_DEPT TD " +
            "WHERE TD.DNAME LIKE '%' || :dname || '%' "
            , countQuery = "SELECT COUNT(*) FROM TB_DEPT D " +
            "WHERE D.DNAME LIKE '%' || :dname || '%'"
            , nativeQuery = true)
    Page<Dept> findAllByDnameContaining(@Param("dname") String dname, Pageable pageable);
}

Repository 인터페이스 : DB CRUD 함수가 있는 인터페이스

       사용법 : interface 이름 extends JpaRepository<엔티티명,기본키속성자료형>
           => 엔티티명        : DB 와 연결될 엔티티 클래스명
           => 기본키속성자료형 : 엔티티 클래스의 기본키 속성 자료형 명시
           => JPA 기본 함수 사용 가능
              - findAll()       : 전체 조회 , 자동 sql 문 생성
              - findById(기본키) : 상세 조회(1건), 자동 sql 문 생성
              - save(객체)       : 저장/수정을 알아서 실행함
                   저장 : 기본키가 없으면 insert
                   수정 : 기본키가 있으면 update
              - deleteById(기본키): 삭제 , 자동 sql 문 생성

@Query : 직접 sql 문 작성 기능, 오라클 기반 쿼리(nativeQuery = true)

검색어를 넣어 like 검색을 할수 있게 하는 함수로 sql 문을 직접 작성가능

페이징 처리를 위해 countQuery 가 필요함 : 조건에 따른 결과 개수세기

  @Query 사용법 : 
          @Query(value = "SELECT 컬럼명 FROM 테이블명 별명 " +
              "WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%' "
              , countQuery = "SELECT COUNT(*) FROM 테이블명 별명 " +
              "WHERE 별명.컬럼명 LIKE '%' || :변수명 || '%'"
              , nativeQuery = true)
      Page<자료형> 함수명(@Param("변수명") 자료형 변수명, Pageable pageable);

부서 게시판 서비스

package com.example.jpaexam.service.basic;

import com.example.jpaexam.model.entity.advanced.FileDb;
import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.repository.basic.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;

import java.util.List;
import java.util.Optional;

/**
 * packageName : com.example.jpaexam.service
 * fileName : DeptService
 * author : GGG
 * date : 2023-10-16
 * description : 부서 업무 서비스
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-16         GGG          최초 생성
 */
@Service
public class DeptService {

    @Autowired
    DeptRepository deptRepository; // DI 객체 가져오기

    /** 전체조회 */
    public List<Dept> findAll() {
        List<Dept> list = deptRepository.findAll(); // db 전체조회 함수 호출
        return list;
    }

    /** 전체조회 : 페이징 */
    public Page<Dept> findAll(Pageable pageable) {
        Page<Dept> list = deptRepository.findAll(pageable); // db 전체조회 함수 호출
        return list;
    }

    public Page<Dept> findAllByDnameContaining(String dname, Pageable pageable) {
        Page<Dept> page = deptRepository.findAllByDnameContaining(dname, pageable);
        return page;
    }

    /** 상세조회(1건조회) */
    public Optional<Dept> findById(int dno) {
        Optional<Dept> optionalDept = deptRepository.findById(dno);

        return optionalDept;
    }

    /** 저장(수정)함수 */
    public Dept save(Dept dept) {
//      todo: jpa 저장함수 호출 ( 기본키 없으면 insert, 있으면 update )
        Dept dept2 = deptRepository.save(dept);

        return dept2; // 저장된 부서객체
    }

    /** 삭제함수 */
    public boolean removeById(int dno) {

//      existsById : jpa 함수 - 리턴값: 있으면 true, 없으면 false
        if(deptRepository.existsById(dno)) {
            deptRepository.deleteById(dno); // DB 삭제(dno)
            return true;
        }
        return false;
    }

}

부서 게시판 컨트롤러

package com.example.jpaexam.controller.basic;

import com.example.jpaexam.model.entity.basic.Dept;
import com.example.jpaexam.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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;

import java.util.Optional;

/**
 * packageName : com.example.mybatisexam.controller.exam01
 * fileName : DeptController
 * author : GGG
 * date : 2023-10-12
 * description : 부서 컨트롤러 : jsp 연동 : @Controller
 * 요약 :
 * <p>
 * ===========================================================
 * DATE            AUTHOR             NOTE
 * —————————————————————————————
 * 2023-10-12         GGG          최초 생성
 */
@Slf4j
@Controller
@RequestMapping("/basic")
public class DeptJspController {

    @Autowired
    DeptService deptService; // 서비스 객체 가져오기

    /** 전체 조회 : dname like 기능 (+) */
//  todo: @RequestParam - url?변수=값&변수2=값2 (쿼리스트링 방식)
    @GetMapping("/dept")
    public String getDeptAll(
            @RequestParam(defaultValue = "") String dname
        , @RequestParam(defaultValue = "0") int page
        , @RequestParam(defaultValue = "3") int size
        , Model model
            ){
//      todo: 페이징 요청 객체에 정보 저장
//        page : 현재페이지 번호, size : 1 페이지당 개수
        Pageable pageable = PageRequest.of(page, size);

//      todo: 전체 조회 함수 호출
        Page<Dept> pageRes
                = deptService.findAllByDnameContaining(dname, pageable);
//      todo: jsp 정보전달( 부서배열, 페이징정보 )
        model.addAttribute("dept", pageRes.getContent()); // 부서배열
        model.addAttribute("currentPage", pageRes.getNumber()); // 현재 페이지 번호(0 ~)
        model.addAttribute("totalItems", pageRes.getTotalElements()); // 전체 테이블 건수
        model.addAttribute("totalPages", pageRes.getTotalPages()); // 전체 페이지 개수
//        공식 : 블럭 시작페이지 번호 = (Math.floor(현재페이지번호/1페이지당개수)) * 1페이지당개수
//          예) 1블럭 : 0(블럭시작페이지번호) ~ 2(블럭끝페이지번호)  , 2블럭 : 3(블럭시작페이지번호) ~ 5(블럭끝페이지번호), 3블럭 : 6(블럭시작페이지번호) ~ 8(블럭끝페이지번호)
//              현재페이지 2, 1페이지당 개수 3 => Math.floor(2/3) * 3 = 0
//              현재페이지 5, 1페이지당 개수 3 => Math.floor(5/3) * 3 = 3
        long blockStartPage = (long) Math.floor((double)(pageRes.getNumber()) / size) * size;
        model.addAttribute("startPage", blockStartPage); // 블럭 시작 페이지 번호(0 ~)
//        공식 : 블럭 끝페이지 번호 = 블럭 시작페이지번호 + 1페이자당개수 - 1
//           만약 블럭 끝페이지번호 >= 전체페이지수 =>(블럭 끝페이지번호는 전체페이지수보다 클수 없음) 블럭끝페이지번호 = 전체페이지수 -1 넣기(0부터 시작)
        long blockEndPage = blockStartPage + size - 1;
        blockEndPage = (blockEndPage >= pageRes.getTotalPages())? pageRes.getTotalPages() - 1 : blockEndPage;
        model.addAttribute("endPage", blockEndPage); // 현재 끝 페이지 번호

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

        return "basic/dept/dept_all.jsp";
    }

    /** 상세조회 */
    @GetMapping("/dept/{dno}")
    public String getDeptId(@PathVariable int dno,
                            Model model
                            ) {
//      서비스 상세조회 함수 호출
        Optional<Dept> optionalDept = deptService.findById(dno);
        model.addAttribute("dept", optionalDept.get());

        return "basic/dept/dept_id.jsp";
    }

    /** 저장함수 : 저장 페이지로 이동 */
    @GetMapping("/dept/addition")
    public String addDept(){
        return "basic/dept/add_dept.jsp";
    }

    /** 저장함수 : db 저장 */
    @PostMapping("/dept/add")
    public RedirectView createDept(
            @ModelAttribute Dept dept
    ){
        deptService.save(dept); // db 저장

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

    /** 수정함수 : 수정 페이지로 이동 + 상세조회(1건조회) */
    @GetMapping("/dept/edition/{dno}")
    public String editDept(@PathVariable int dno,
                           Model model
                           ) {
//      서비스 상세조회 함수 호출
        Optional<Dept> optionalDept = deptService.findById(dno);
//      jsp 전달
        model.addAttribute("dept", optionalDept.get());
        return "basic/dept/update_dept.jsp";
    }

    /** 수정함수 : db 수정 저장 */
    @PutMapping("/dept/edit/{dno}")
    public RedirectView updateDept(@PathVariable int dno,
                                   @ModelAttribute Dept dept
                                   ) {
        log.debug("---------------start-------------------------------");
        deptService.save(dept); // db 수정 저장
        log.debug("---------------end-------------------------------");

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

    /** 삭제함수 */
    @DeleteMapping("/dept/delete/{dno}")
    public RedirectView deleteDept(@PathVariable int dno) {
        deptService.removeById(dno); // db 삭제

        return new RedirectView("basic/dept");
    }

//  todo: 연습 5) 부서 클래스를 참고하여 사원 삭제기능을 추가하세요
//    empDao, emp.xml, EmpService, EmpController, update_emp.jsp 수정
//             url : /emp/delete/{eno}
//    redirect jsp : /emp

}
  • 전체 조회 : getDeptAll() 은 검색어가 “” 전달되면 전체조회가 되고
    “검색어” 넣으면 like 검색이 실행됨
  • 페이징 처리 : 기본 요약 페이징 처리 부분 참고

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="/basic/dept" method="get">
        <div class="col-auto">
            <%--            todo: 부서명 라벨 --%>
            <label for="dname" class="visually-hidden">Dname</label>
            <%--            todo: 검색창--%>
            <input type="text" class="form-control" id="dname" placeholder="dname" name="dname">
            <%--            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: 테이블 반복문 시작--%>
    <table class="table">
        <thead class="thead-dark">
        <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
            <th scope="col">위치</th>
            <th scope="col">등록일자</th>
            <th scope="col">수정일자</th>
        </tr>
        </thead>
        <tbody>
        <c:forEach var="data" items="${dept}">
            <tr>
                <td><a href="/basic/dept/edition/${data.dno}">${data.dno}</a></td>
                <td>${data.dname}</td>
                <td>${data.loc}</td>
                <td>${data.insertTime}</td>
                <td>${data.updateTime}</td>
            </tr>
        </c:forEach>

        </tbody>
    </table>
    <%--    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="/basic/dept/addition" class="btn btn-primary center">Add</a>
        </div>
</div>

<%--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: /basic/dept/add 해당되는 컨트롤러 함수 실행   --%>
        <form action="/basic/dept/add" method="post">
<%--         todo: 부서명   --%>
            <div class="mb-3">
                <label for="dname" class="form-label">부서 이름</label>
                <input type="text" class="form-control" id="dname" required name="dname">
            </div>
<%--         todo: 부서위치 --%>
            <div class="mb-3">
                <label for="loc" class="form-label">부서 위치</label>
                <input type="text" class="form-control" id="loc" required name="loc">
            </div>
            <div class="mb-3">
                <button type="submit" class="btn btn-primary">Submit</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="/basic/dept/edit/${dept.dno}" method="post">
<%--    TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 :  put 방식으로 인식해서 연결해줌    --%>
            <input type="hidden" name="_method" value="put"/>

            <input type="hidden" name="dno" value="${dept.dno}"/>

            <div class="mb-3">
                <label for="dname" class="form-label">부서 이름</label>
                <input type="text"
                       class="form-control"
                       id="dname"
                       required
                       name="dname"
                       value="${dept.dname}"
                >
            </div>
            <div class="mb-3">
                <label for="loc" class="form-label">부서 위치</label>
                <input type="text"
                       class="form-control"
                       id="loc"
                       required
                       name="loc"
                       value="${dept.loc}"
                >
            </div>
            <div class="mb-3">
                <button type="submit" class="btn btn-primary">Update</button>
            </div>
        </form>

<%--        todo: 삭제 버튼 form --%>
        <form id="delete-form" action="/basic/dept/delete/${dept.dno}" method="post">
            <%--    TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 :  delete 방식으로 인식해서 연결해줌    --%>
            <input type="hidden" name="_method" value="delete"/>
            <button type="submit" class="btn btn-danger">Delete</button>
        </form>
    </div>

</div>

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

📃 결론

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

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

감사합니다.

답글 남기기

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