+ 00 00 0000

Have any Questions?

01_Simple Coding – SI-MSA – 기초 실전예제 1

01_Simple Coding – SI-MSA – 기초 실전예제 1

📃 요약

네이버 카페 등의 게시판을 기본 기능을 제작하는 예제입니다.
기본적인 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설명
GETdept전체 조회
GETdept/{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&quot;,
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 를 이용해 직접 작성할 수 있어 요즘 많이 서비스 업체 기준으로 사용되고 있습니다.

감사합니다.

답글 남기기

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