
📃 요약
네이버 카페 등의 게시판을 기본 기능을 제작하는 예제입니다.
기본적인 DB CRUD 를 사용해 제작합니다.
DB 프레임워크는 JPA 를 사용해 자동화기능을 강화합니다.
뷰(Vue) & 스프링부트 연동 기초 예제
- axios CRUD 함수들과 스프링부트의 컨트롤러 함수들과 네트웍으로 연결됨
이때 컨트롤러는 @RestController 어노테이션을 사용해야함 - Vue : axios 라이브러리의 get(), post(), put(), delete() 함수 사용
- 스프링부트 – @RestController 어노테이션 사용해서 컨트롤러 클래스 생성
CRUD : @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
나머지 모델, 레포지토리, 서비스는 JSP 와 동일함
요소 기술 및 테이블 설계는 아래와 같습니다.
요소 기술 :
– 프론트엔드 : Vue
– 벡엔드 : 스프링부트 & JPA & Oracle 18xe(Oracle Cloud 19c)
결과 화면 :

프로젝트 탐색기 : Vue

프로젝트 탐색기 : String Boot

Rest API :
메소드 | URL | 설명 |
---|---|---|
GET | dept | 전체 조회 |
GET | dept/{dno} | 상세조회 |
POST | /dept | 저장 |
PUT | /dept/{dno} | 수정 |
DELETE | /dept/deletion/{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 { // 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.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" ) // todo: @Column(columnDefinition = "DB컬럼자료형") @Column(columnDefinition = "NUMBER") private Integer dno; // 부서번호(기본키) - 시퀀스 기능 부여 @Column(columnDefinition = "VARCHAR2(255)") private String dname; // 부서명 @Column(columnDefinition = "VARCHAR2(255)") private String loc; // 부서위치 }
부서 레포지토리
package com.example.jpaexam.repository.basic; import com.example.jpaexam.model.entity.basic.Dept; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * 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> { }
select : @Query 이용한 오라클 기반 쿼리(nativeQuery = true) 임
부서 게시판 서비스
package com.example.jpaexam.service.basic; 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; } /** 상세조회(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; } }
부서 게시판 컨트롤러 : Rest Controller 사용 ( Vue, React, Angular.js 등 )
package com.example.simpledms.controller.basic; import com.example.simpledms.model.entity.basic.Dept; import com.example.simpledms.service.basic.DeptService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.*; /** * packageName : com.example.modelexam.controller * fileName : DeptController * author : kangtaegyung * date : 2022/10/12 * description : 부서 컨트롤러 * 요약 : * <p> * =========================================================== * DATE AUTHOR NOTE * ----------------------------------------------------------- * 2022/10/12 kangtaegyung 최초 생성 */ @Slf4j @RestController @RequestMapping("/api/basic") public class DeptController { @Autowired DeptService deptService; @GetMapping("/dept") public ResponseEntity<Object> findAllByDnameContaining(@RequestParam(defaultValue = "") String dname, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "3") int size ) { try { // 페이지 변수 저장 Pageable pageable = PageRequest.of(page, size); // List<Dept> list = Collections.emptyList(); Page<Dept> deptPage; deptPage = deptService.findAllByDnameContaining(dname, pageable); Map<String, Object> response = new HashMap<>(); response.put("dept", deptPage.getContent()); response.put("currentPage", deptPage.getNumber()); response.put("totalItems", deptPage.getTotalElements()); response.put("totalPages", deptPage.getTotalPages()); if (deptPage.isEmpty() == false) { // 성공 return new ResponseEntity<>(response, HttpStatus.OK); } else { // 데이터 없음 return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } catch (Exception e) { // 서버 에러 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @GetMapping("/dept/{dno}") public ResponseEntity<Object> findById(@PathVariable int dno) { try { Optional<Dept> optionalDept = deptService.findById(dno); if (optionalDept.isPresent()) { // 성공 return new ResponseEntity<>(optionalDept.get(), HttpStatus.OK); } else { // 데이터 없음 return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } catch (Exception e) { // 서버 에러 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PostMapping("/dept") public ResponseEntity<Object> create(@RequestBody Dept dept) { try { Dept dept2 = deptService.save(dept); return new ResponseEntity<>(dept2, HttpStatus.OK); } catch (Exception e) { // DB 에러가 났을경우 : INTERNAL_SERVER_ERROR 프론트엔드로 전송 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PutMapping("/dept/{dno}") public ResponseEntity<Object> update(@PathVariable int dno, @RequestBody Dept dept) { try { Dept dept2 = deptService.save(dept); return new ResponseEntity<>(dept2, HttpStatus.OK); } catch (Exception e) { // DB 에러가 났을경우 : INTERNAL_SERVER_ERROR 프론트엔드로 전송 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @DeleteMapping("/dept/deletion/{dno}") public ResponseEntity<Object> delete(@PathVariable int dno) { // 프론트엔드 쪽으로 상태정보를 보내줌 try { boolean bSuccess = deptService.removeById(dno); if (bSuccess == true) { // delete 문이 성공했을 경우 return new ResponseEntity<>(HttpStatus.OK); } // delete 실패했을 경우( 0건 삭제가 될경우 ) return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch (Exception e) { // DB 에러가 날경우 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @DeleteMapping("/dept/all") public ResponseEntity<Object> deleteAll() { // 프론트엔드 쪽으로 상태정보를 보내줌 try { deptService.removeAll(); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { // DB 에러가 날경우 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } }
Vue 페이지
0) 라이브러리 설치
axios 라이브러리 설치
npm install axios
bootstrap-vue-3 설치
npm install bootstrap-vue-3
main.js import : 주석부분 추가
import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' // TODO: bootstartp vue3 import import BootstrapVue3 from 'bootstrap-vue-3' // TODO: bootstartp vue3 css import import 'bootstrap-vue-3/dist/bootstrap-vue-3.css' createApp(App) // TODO: bootstartp vue3 lib 뷰에 넣기 .use(BootstrapVue3) .use(store) .use(router) .mount('#app')
1) 공통 js
– axios import 및 기본설정파일
- utils/http-common.js
// 1)
import axios from "axios";
export default axios.create({
baseURL: “http://localhost:8000/api",
headers: {
“Content-Type”: “application/json”
}
});
#### - axios 공통 함수 : get/post/put/delete 방식 함수들 - basic/DeptService.js ```js // 2) import http from "../../utils/http-common"; // springboot 로 연결할 axios 함수들 // 전송 경로 : springboot url을 코딩 class DeptService { getAll(dname, page, size) { return http.get(`/basic/dept?dname=${dname}&page=${page}&size=${size}`); } get(dno) { return http.get(`/basic/dept/${dno}`); } create(data) { return http.post("/basic/dept", data); } update(dno, data) { return http.put(`/basic/dept/${dno}`, data); } delete(dno) { return http.delete(`/basic/dept/deletion/${dno}`); } } export default new DeptService(); </code></pre> <h4>- 메뉴 & url 정의</h4> <ul> <li>router/index.js <pre><code class="language-js"> import Vue from "vue"; import VueRouter from "vue-router";</code></pre></li> </ul> <p>Vue.use(VueRouter);</p> <p>const routes = [ { path: "/", alias: "/dept", name: "dept", component: () => import("@/views/basic/dept/DeptList"), }, // 부서 라우팅 { path: "/dept/:dno", name: "dept-detail", component: () => import("@/views/basic/dept/DeptDetail"), }, { path: "/add-dept", name: "add-dept", component: () => import("@/views/basic/dept/AddDept"), }, // 사원 라우팅 { path: "/emp", name: "emp", component: () => import("@/views/basic/emp/EmpList"), }, { path: "/emp/:eno", name: "emp-detail", component: () => import("@/views/basic/emp/EmpDetail"), }, { path: "/add-emp", name: "add-emp", component: () => import("@/views/basic/emp/AddEmp"), }, // faq 라우팅 { path: "/faq", name: "faq", component: () => import("@/views/basic/faq/FaqList"), }, { path: "/faq/:fno", name: "faq-detail", component: () => import("@/views/basic/faq/FaqDetail"), }, { path: "/add-faq", name: "add-faq", component: () => import("@/views/basic/faq/AddFaq"), }, // fileDb Upload 라우팅 { path: "/fileDb", name: "fileDb", component: () => import("@/views/advanced/fileDb/FileDbList"), }, { path: "/fileDb/:uuid", name: "fileDb-detail", component: () => import("@/views/advanced/fileDb/FileDbDetail"), }, { path: "/add-fileDb", name: "add-fileDb", component: () => import("@/views/advanced/fileDb/AddFileDb"), }, // gallery Upload 라우팅 { path: "/gallery", name: "gallery", component: () => import("@/views/advanced/gallery/GalleryList"), }, { path: "/gallery/:uuid", name: "gallery-detail", component: () => import("@/views/advanced/gallery/GalleryDetail"), }, { path: "/add-gallery", name: "add-gallery", component: () => import("@/views/advanced/gallery/AddGallery"), }, // code-category 라우팅 { path: "/code-category", name: "code-category", component: () => import("@/views/admin/code/CodeCategoryList"), }, { path: "/add-code-category", name: "add-code-category", component: () => import("@/views/admin/code/AddCodeCategory"), }, // code 라우팅 { path: "/code", name: "code", component: () => import("@/views/admin/code/CodeList"), }, { path: "/code/:codeId", name: "code-detail", component: () => import("@/views/admin/code/CodeDetail"), }, { path: "/add-code", name: "add-code", component: () => import("@/views/admin/code/AddCode"), }, // admin/login 라우팅 { path: "/login", name: "login", component: () => import("@/views/auth/LoginView"), },<br /> { path: "/register", name: "register", component: () => import("@/views/auth/RegisterView"), },<br /> { path: "/forgot-password", name: "forgot-password", component: () => import("@/views/auth/ForgotPassword"), }, ];</p> <p>const router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes, });</p> <p>export default router;</p> <pre><code> ### 2) Vue 페이지 ### Vue 전체조회 페이지 #### 결과 화면 : <img src = "https://velog.velcdn.com/images/forbob/post/c20b7ff5-3564-4133-bc5d-eaf7d7f3d683/image.png" style="max-width:100%; height:auto;"> DeptList.vue ```html <template> <!-- 3) --> <div> <!-- {/* dname start */} --> <div class="row mb-5 justify-content-center"> <!-- {/* w-50 : 크기 조정, mx-auto : 중앙정렬(margin: 0 auto), justify-content-center */} --> <div class="col-12 w-50 input-group mb-3"> <input type="text" class="form-control" placeholder="Search by dname" v-model="searchDname" /> <div class="input-group-append"> <button class="btn btn-outline-secondary" type="button" @click="retrieveDept" > Search </button> </div> </div> </div> <!-- {/* dname end */} --> <!-- 페이징 + 전체 목록 시작 --> <!-- 페이징 양식 시작 --> <div class="col-md-12"> <!-- 셀렉트 박스 : 기본 페이지 변경 --> <!-- change : handlePageSizeChange($event), 1페이지당 개수 변경 시 실행되는 이벤트--> <!-- $event : html 태그의 기본 이벤트, 이 객체로 현재 선택 또는 클릭한 html태그(양식)를 알 수 있음. event.target (현재 사용자가 선택한 양식(여기서는 셀렉트 박스내 목록을 의미 ) --> <div class="mb-3"> Items per Page: <select v-model="pageSize" @change="handlePageSizeChange($event)"> <option v-for="size in pageSizes" :key="size" :value="size"> {{ size }} </option> </select> </div> <!-- b-pagination : 부트스트랩 - 페이지 번호 컨트롤 --> <!-- total-rows : 전체 데이터 개수 --> <!-- per-page : 1페이지 당 개수 --> <!-- change : handlePageChange(), 페이지 번호 변경 시 실행되는 이벤트 --> <b-pagination v-model="page" :total-rows="count" :per-page="pageSize" prev-text="Prev" next-text="Next" @change="handlePageChange" ></b-pagination> </div> <!-- 페이징 양식 끝 --> <!-- {/* table start */} --> <div class="col-md-12"> <!-- {/* table start */} --> <table class="table"> <thead class="table-light"> <tr> <th scope="col">Dname</th> <th scope="col">Loc</th> <th scope="col">Actions</th> </tr> </thead> <tbody v-for="(data, index) in dept" :key="index"> <tr> <td>{{data.dname}}</td> <td>{{data.loc}}</td> <td> <router-link :to="'/dept/' + data.dno" ><span class="badge bg-success">Edit</span></router-link > </td> </tr> </tbody> </table> <!-- {/* table end */} --> </div> <!-- {/* table end */} --> </div> </template> <script> import DeptService from "@/services/basic/DeptService"; export default { data() { return { dept: [], searchDname: "", page: 1, count: 0, pageSize: 3, pageSizes: [3, 6, 9], }; }, methods: { retrieveDept() { DeptService.getAll(this.searchDname, this.page - 1, this.pageSize) .then((response) => { const { dept, totalItems } = response.data; this.dept = dept; this.count = totalItems; console.log(response.data); }) .catch((e) => { console.log(e); }); }, // 페이지 번호 변경시 실행되는 함수 // 부트스트랩-페이지 양식에 페이지번호만 매개변수로 전달하면 됨 // 페이지번호를 변경한 숫자가 매개변수(value)로 전달됨 handlePageChange(value) { this.page = value; this.retrieveDept(); }, // 셀렉트 박스 값 변경시 (페이지 크기 변경) 실행되는 함수 // event.target.value : 셀렉트 박스에서 선택된 값 handlePageSizeChange(event) { this.pageSize = event.target.value; this.page = 1; this.retrieveDept(); }, }, mounted() { this.retrieveDept(); }, }; </script> <style> </style>
Vue 추가 페이지
결과 화면 :

AddDept.vue
<template> <!-- 4) --> <div class="row"> <div v-if="!submitted"> <div class="col-6 mx-auto"> <div class="row g-3 align-items-center mb-3"> <div class="col-3"> <label htmlFor="dname" class="col-form-label"> Dname </label> </div> <div class="col-9"> <input type="text" id="dname" required class="form-control" v-model="dept.dname" placeholder="dname" name="dname" /> </div> </div> <div class="row g-3 align-items-center mb-3"> <div class="col-3"> <label htmlFor="loc" class="col-form-label"> Loc </label> </div> <div class="col-9"> <input type="text" id="loc" required class="form-control" v-model="dept.loc" placeholder="loc" name="loc" /> </div> </div> <div class="row g-3 mt-3 mb-3"> <button @click="saveDept" class="btn btn-outline-primary ms-2 col" > Submit </button> </div> </div> </div> <div v-else> <h4>You submitted successfully!</h4> <button class="btn btn-success" @click="newDept">Add</button> </div> </div> </template> <script> import DeptService from "@/services/basic/DeptService"; export default { data() { return { dept: { dno: null, dname: "", loc: "", }, submitted: false, }; }, methods: { saveDept() { var data = { dname: this.dept.dname, loc: this.dept.loc, }; DeptService.create(data) .then((response) => { this.dept.dno = response.data.dno; console.log(response.data); this.submitted = true; }) .catch((e) => { console.log(e); }); }, newDept() { this.submitted = false; this.dept = {}; }, }, }; </script> <style> </style>
Vue 수정 페이지
결과 화면 :

DeptDetail.vue
<template> <!-- 5) --> <div v-if="dept"> <div class="col-6 mx-auto"> <form> <div class="row g-3 align-items-center mb-3"> <div class="col-3"> <label htmlFor="dname" class="col-form-label"> Dname </label> </div> <div class="col-9"> <input type="text" id="dname" required class="form-control" v-model="dept.dname" placeholder="dname" name="dname" /> </div> </div> <div class="row g-3 align-items-center mb-3"> <div class="col-3"> <label htmlFor="loc" class="col-form-label"> Loc </label> </div> <div class="col-9"> <input type="text" id="loc" required class="form-control" v-model="dept.loc" placeholder="loc" name="loc" /> </div> </div> </form> <div class="row g-3 mt-3 mb-3"> <button @click="deleteDept" class="btn btn-outline-danger ms-3 col" > Delete </button> <button type="submit" @click="updateDept" class="btn btn-outline-success ms-2 col" > Update </button> </div> <p v-if="message" class="alert alert-success mt-3 text-center">{{message}}</p> </div> </div> <div v-else> <br /> <p>Please click on a Dept...</p> </div> </template> <script> import DeptService from "@/services/basic/DeptService"; /* eslint-disable */ export default { data() { return { dept: null, message: "", }; }, methods: { getDept(dno) { DeptService.get(dno) // spring 요청 // 성공/실패 모르겠지만 // 성공하면 then안에 있는것이 실행 .then((response) => { this.dept = response.data; console.log(response.data); }) // 실패하면 catch안에 있는것이 실행 .catch((e) => { console.log(e); }); }, updateDept() { DeptService.update(this.dept.dno, this.dept) .then((response) => { console.log(response.data); this.message = "The Dept was updated successfully!"; }) .catch((e) => { console.log(e); }); }, deleteDept() { DeptService.delete(this.dept.dno) .then((response) => { console.log(response.data); this.$router.push("/dept"); }) .catch((e) => { console.log(e); }); }, }, mounted() { this.message = ""; this.getDept(this.$route.params.dno); }, }; </script> <style> .edit-form { max-width: 300px; margin: auto; } </style>
📃 결론
부서 게시판 샘플 예제를 Vue & Spring boot 연동 예제를 살펴보았습니다.
기본적인 CRUD 기능을 구현했으며 게시판의 페이징 처리도 해봤습니다.
Spring Boot 는 @RestController 어노테이션을 이용해 구현했으며, 결과는 JSON 데이터로 리턴했으며,
Vue 에 axios 라이브러리를 이용해 전달했습니댜.
DB 프레임워크는 JPA 를 이용해서 sql 문을 직접 제작하지 않고 자동화기능을 이용해 구현했습니다.
Mybatis 의 직접 sql 문 제작 기능에 대응해 자주 반복되고 쉬운 기능은 JPA 의 sql 자동생성 기능을 이용하고,
복잡한 sql 문은 @Query 를 이용해 직접 작성할 수 있어 요즘 많이 서비스 업체 기준으로 사용되고 있습니다.
감사합니다.