MVC의 한계
양방향 데이터 바인딩 → 복잡도증가 → 리덕스와 같이 단방향(One Way) 방식 사용
상태관리 → 쿠키와 세션 → 상태값 변했을때 화면반영 → 브라우저가 Render Tree 그려야 함(기존의 DOM Tree+css) → Render Engine에 따라 속도, 성능차이
새로운 데이터 관련 → useState, useEffect → 리렌더링(return 안의 것들이 다시그려짐)
createStore → 예제로 만든 Store
컴포넌트 바깥쪽에 존재하는 저장소
상태를 관리해 줌 → 모든 상태 변경이 결정됨
let state를 가지고 있음
파라미터로 worker를 받음
increase를 받으면 카운트값 올림
worker(state, action) → 예제로 만든 Dispatcher
Action
변화되는 정보(타입) 들어있음 → increase 키워드를 Store로 전달
Dispatcher
Action을 Store에 전달, Hub역할
State와 Action이 파라미터로 전달된다
switch문을 써서 걸러준다
View
컴포넌트에 increase한 결과가 출력됨
또 다른 입력(요청) 발생 → Action 발생시키고 다시 Dispatcher로 간다
발행-구독 모델
비동기 메시징 패러다임
발행-구독 모델에서 발신자의 메시지는 특별한 수신자가 정해져 있지 않음
대신 발행된 메시지는 정해진 범주에 따라 각 범주에 대한 구독을 신청한 수신자에게 전달됨
수신자는 발행자에 대한 지식이 없어도 원하는 메시지만을 수신할 수 있음
리덕스 흐름
컴포넌트의 스토어 구독 subscribe(listener)
컴포넌트가 스토어 구독(구독 과정에서 특정 함수가 스토어에 전달됨)
→ 스토어 상태값에 변동이 생기면 전달받았던 함수를 호출해 줌
스토어에 상태 변경 알림 dispatch(action)
어떤 컴포넌트에 이벤트가 생겨 상태를 업데이트해야 한다면 dispatch 함수를 통해 action을 스토어에 건네줌(action은 상태에 변화를 일으킬 때 참조할 수 있는 객체)
→ 건네준 데이터(Action)에는 타입(type)이 들어있어야 함(ex. {type: 'increase'})
리듀서를 통해 상태 변화
action객체를 받으면 전달받은 액션의 타입에 따라 어떻게 상태를 업데이트할지 정의
→ 리듀서(refuce)는 업데이트 로직을 정의하는 함수(store에 들어갈 state와 state를 바꿀 함수를 정의하는 곳)
→ 해당 로직 구현해야 함(불변성 유지 중요!)
상태 변화가 생기면 구독하고 있던 컴포넌트에 알림 listener()
상태에 변화가 생기면 이전에 컴포넌트가 스토어한테 구독할 때 전달해 줬던 함수(listener)가 호출됨
→ 컴포넌트가 새로운 상태를 발도 이에 따른 리렌더링 실행
react-redux 사용법
프로젝트 루트레벨 - index.js (index.html은 단지 root 위치만 잡아주는 것)
worker
dispatcher함수 사용-switch문
index.js(app이 들어있으니까)
불러올 때는 useSelector훅사용
dispatch에 상태를 바꾸는 함수(action)
<Flux 복습 - app.js>
// 상태는 createStore() 안에 있다
// 상태를 담을 변수 선언
// 콜백함수를 담을 배열 선언
// send함수 구현 - 파라미터로 action을 받음
// 구독발행모델 - subscribe(handler-콜백함수)
// subscribe를 통해서 들어온 콜백함수는 handlers배열에 담는다
// getState함수를 통해서 state값을 반환받음
// return { send, subscribe, getState }
const createStore = () => { // 배치위치는 index.js -> 이곳에서 store생성
let state; // 상태를 담아두는 저장소
let handlers = [] // 함수를 담아두는 배열 선언
// 상태를 바꾸는 일을 send()에서 한다 - useSelector훅
const send = (action) => {
console.log('send 호출')
// 새로운 객체가 만들어진다 -> 깊은복사
// 아래 코드와 같은 원리
// Map m = new HashMap()
// m = new HashMap()
state = worker(state, action)
// 나에게 구독신청한 사람들에게 모두 알림
handlers.forEach(handler => handler()) // 전달받은 함수를 호출해줘
}
const subscribe = ((handler) => { // useDispatch훅
// 콜백함수
handlers.push(handler)
})
const getState = () => {
return state;
}
// 함수 안에서 함수를 리턴하도록 처리해야 바깥쪽에서 해당 함수를 요청할 수 있다
return {
// state -> 이런식으로 직접적으로 상태값을 주지 않는다
// 밖에서 상태값을 변경해야하기에 send를 넘겨준다
send, // 함수(객체), 파라미터로 들어온 상태를 받아서 가공 후 새로운 객체로 내보냄
// 직접 상태값을 주지 못하지만 getState호출해 상태값 알 수 있게 함
getState, // 함수, 상태정보를 담은 state를 반환해줌
subscribe
}
} // end of createStore
// react-redux에서는 worker가 dispatcher가 됨
// reducer, dispatch함수
const worker = (state = {count: 0}, action) => { // state가 undefined되는 것 방지하기위해 객체 선언
// worker안에서는 무엇을 하는가?
// 상태를 바꾸면 createStore 안 state의 참조 무결성이 깨짐
// 리덕스에서는 상태를 바꾸는 함수는 반드시 새로운 상태를 반환해야한다
// 새로운 상태 -> 화면의 입력(Action)으로 상태의 객체를 줄테니 이 객체를 Deeo copy해서
// 기존의 참조링크를 끊어내라 - 그래야 side effect 방지 가능함
switch(action.type){
case 'increase':
return {...state, count: state.count + 1}
case 'decrease':
return {...state, count: state.count - 1}
default:
return {...state}
}
}
// 자바스크립트에서는 함수도 파라미터로 넘길 수 있다
const store = createStore(worker) // index.js에서 생성할 것임 - props대신 중앙에서 한번에 가져다 사용
store.subscribe(function () {
console.log(store.getState())
})
// action의 내용은 send에서 만듦
// 사용자가 버튼을 클릭했을 때 시그널 발생 - type을 정해서 value를 store에 전달한다
// store가 받아서 전역변수로 관리됨 - 다른 컴포넌트에서 즉시 사용 가능함
store.send({type: 'increase'}) // 시그널을 주기(어떤 타입을 바꿀건지) - action
store.send({type: 'increase'})
store.send({type: 'decrease'})
/*
send 호출
{ count: 1 }
send 호출
{ count: 2 }
send 호출
{ count: 1 }
*/
/*
JS에서 함수는 객체이다
소문자로 선언하면 함수이고
대문자로 선언하면 화면을 렌더링하는 컴포넌트이다
1. UI한테는 직접적인 상태를 주지 않는다
return에서는 상태값을 직접 넘겨주지 않는다
상태는 createStore함수에 있지만,
변경하거나 읽거나 하는 코드들은 UI의 Component들이다(컴포넌트가 데이터를 사용한다)
이 컴포넌트들은 createStore함수의 바깥쪽에 위치한다
-> 밖에서 createStore함수 안의 state를 변경하는 로직을 작성해야한다
-> worker를 createStore에 넘겨준다(createStore에서 변경할 값과 변경 내용을 알아야하기에)
-> 언제 무엇을 변경할 것인가 시그널은 store 밖에서 준다(ex. 사용자가 버튼을 누름)
문제제기
컴포넌트(HomePage.jsx, LoginPage.jsx 등)가 여러개 있는 상황에서 어떤 컴포넌트가
데이터가 변경되었는지 어떻게 알고서 getState함수를 호출할 것인가?
-> 구독발행 모델 사용 - Pub and Subscribe
일종의 패턴, 어떤 함수를 주고 데이터가 변경되면 그 함수를 호출해줌(이벤트처리)
*/
메모이제이션(Memoization)
기존에 수행한 연산의 결괏값을 저장해 두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
중복 연산 피할 수 있어 애플리케이션의 성능 최적화함
useMemo와 useCallback 차이
useMemo
메모이제이션된 값을 반환
아래의 코드에서 ex로 지정한 값이 변하면 () => fn 함수를 실행하고, 그 함수의 반환값을 반환해 줌
ex값이 변할 경우에만 연산을 수행할 수 있도록 useMemo를 사용해 ex라는 변수에 의존하도록 등록하는 것
→ 리렌더링이 발생할 경우, 특정 변수가 변할 때만 useMemo에 등록한 함수가 실행되도록 처리하면 불필요한 연산 피할 수 있음
useMemo(() => fn, [ex])
useCallback
메모이제이션된 함수를 반환
함수를 반환하기에 그 함수를 가지는 변수에 초기화하는 것이 일반적
자식 컴포넌트에 props로 함수를 전달(자식 컴포넌트의 리렌더링 방지)하거나 외부에서 값을 가져오는 api를 호출(함수의 참조값 동일하게 유지)하는 경우 사용
useCallback(fn, [ex])
스프링 어노테이션
@Configuration
설정파일을 만들기 위한(Bean을 등록하기 위한) 어노테이션
자바클래스(spring applcation)에 cors 이슈 해결을 위해 사용
클래스 선언 앞에 위치
@Bean
개발자가 직접 제어 불가능한 외부 라이버러리 등을 Bean으로 만들 때 사용
spring-service, data, servlet-context에서 사용
메소드 선언 앞에 위치
@ComponentScan
@Component 및 @Service, @Repository, @Controller 어노테이션이 부여된 클래스들을 자동으로 스캔하여 Bean으로 등록해 주는 역할
클래스 선언 앞에 위치
@Controller
Model객체를 만들어 데이터를 담고 View를 반환
클래스 선언 앞에 위치
@RestController
@Controller + @ResponseBody → 마임타입 text/plain
화면이 아니라 문자열 출력 → 단순히 객체만 반환, 데이터는 JSON 또는 XML 형식으로 HTTP 응답에 담아 전송
클래스 선언 앞에 위치
@Autowired
의존성 주입에 사용하는 어노테이션으로, 의존 객체의 타입에 해당하는 빈을 찾아 주입하는 역할
setter객체주입법을 대체함
클래스 선언 앞에 위치
@RequestParam
사용자가 전달하는 값을 1:1 매핑해 주는 어노테이션
파라미터를 통해 값을 전달할 때 자주 사용
1개의 HTTP 요청 파라미터를 받기 위해 사용 →
Map<String, Object> pMap
메소드 파라미터에 위치
@RequestBody
JSON형태의 HTTP Body를 자바 객체로 변환시켜 주는 역할
메소드 파라미터에 위치
@Service
모델계층에서 사용
내부에서 자바 로직을 처리함
클래스 선언 앞에 위치
@Repository
MyBatis레이어를 객체주입받을 때 사용
DB나 파일 같은 외부 I/O 작업 처리
클래스 선언 앞에 위치
@PathVariable
해시값 받아옴
요청 URI의 매핑 템플릿 변수를 처리하는 데 사용
메소드 파라미터에 위치
리액트 훅
useState
state 관리(동적 상태 관리)
가변적인 상태를 지닐 수 있게 함
함수에 파라미터를 넣어 호출하면 전달받은 파라미터로 값이 변경되고 컴포넌트가 리렌더링 됨(Dom Tree와 Render Tree)
useEffect
렌더링 될 때마다 특정 작업을 수행하도록 설정
의존성배열에 아무것도 넣지 않으면 렌더링 할 때마다, 빈 배열은 처음 한 번만, 배열에 값을 넣어주면 해당 값이 변경될 때마다 실행됨
useRef
사용자 입력 컴포넌트를 기억, 요즘은 useCallback 많이 사용
저장공간, DOM 요소에 접근하기 위해 사용
useNavigate
화면 전환용
양식이 제출되거나 특정 이벤트가 발생했을 때 url을 조작할 수 있는 인터페이스 제공
useCallback
메모이제이션이며 함수 전체를 기억(특정 함수 재사용)
함수 재사용 시 렌더링 성능 최적화 가능
useMemo
메모이제이션이며 값을 기억(특정 값 재사용)
연상 값 재사용 시 컴포넌트 내부의 연산 최적화 가능
렌더링 과정에서 특정 값이 바뀌었을 때만 연산 실행
<dept 리액트 스프링 연동 - App.jsx>
import { Route, Routes } from 'react-router-dom';
import LoginPage from './components/auth/LoginPage';
import KakaoRedirectHandler from './components/kakao/KakaoRedirectHandler';
import Profile from './components/kakao/Profile';
import MemberPage from './components/page/MemberPage';
import HomePage from './components/page/HomePage';
import DeptPage from './components/page/DeptPage';
function App({imageUploader}) {
return (
<>
<Routes>
<Route path='/' exact={true} element={<LoginPage />} />
<Route path='/home' exact={true} element={<HomePage />} />
<Route path='/dept/:gubun' element={<DeptPage imageUploader={imageUploader} />} />
<Route path='/auth/kakao/callback' exact={true} element={<KakaoRedirectHandler />} />
<Route path="/member" exact={true} element={<MemberPage imageUploader={imageUploader} />} />
<Route path="/profile" exact={true} element={<Profile />} />
</Routes>
</>
);
}
export default App;
<dept 리액트 스프링 연동 - BlogHeader.jsx>
import React from 'react'
import { Container, Nav, Navbar } from 'react-bootstrap'
import { Link } from 'react-router-dom'
const BlogHeader = () => {
return (
<>
<Navbar bg="light" variant="light">
<Container>
<Link to="/" className='nav-link'>TerrGYM</Link>
<Nav className="me-auto">
<Link to="/home" className='nav-link'>Home</Link>
<Link to="/dept/0" className='nav-link'>부서관리</Link>
<Link to="/board" className='nav-link'>게시판</Link>
</Nav>
</Container>
</Navbar>
</>
)
}
export default BlogHeader
<dept 리액트 스프링 연동 - DeptPage.jsx>
import React, { useCallback, useEffect, useState } from 'react'
import { Button, Form, Modal, Table } from 'react-bootstrap'
import '../css/style.css'
import BlogHeader from '../include/BlogHeader'
import DeptRow from '../dept/DeptRow'
import { deptInsertDB, deptListDB } from '../../service/dbLogic'
import { useNavigate, useParams } from 'react-router-dom'
import { MyInput, MyLabel, MyLabelAb } from '../styles/FormStyle'
import { validateDname } from '../../service/validateLogic'
const DeptPage = ({imageUploader}) => {
// 화면 전환시나 가급적 전체 페이지 리로딩을 하지 않음 -> Navigate훅을 사용하면 됨
const navigate = useNavigate()
// path = "/dept/:gubun" -> 이 부분을 useParams가 가져옴
// 디폴트 없고 부서등록이 성공하면 1을 돌려줌
const gubun = useParams()
const [deptList, setDeptList] = useState([])
const [show, setShow] = useState(false)
const handleClose = () => setShow(false)
const handleShow = () => setShow(true)
const [deptno, setDeptno] = useState(0)
const [dname, setDname] = useState('')
const [loc, setLoc] = useState('')
// filename과 fileurl, 두 개를 담아야하니 객체로 선언할 것
const [files, setFiles] = useState({filename: null, fileurl: null})
const [comment, setComment] = useState({
deptno: "",
dname: "",
loc: ""
})
const [star, setStar] = useState({
deptno: "*",
dname: "*",
loc: "*"
})
const validate = (key, e) => {
console.log('validate: ' + key)
let result;
if(key === 'dname'){
result = validateDname(e);
}
setComment({...comment, [key]: result})
if(result) {
if(result === ''){
setStar({...star, [key]: ''})
} else {
setStar({...star, [key]: '*'})
}
} else {
setStar({...star, [key]: ''})
}
}
// deptno 값 가져와서 저장
const handleDeptno = useCallback((value) => {
console.log(value)
setDeptno(value)
}, [])
// dname 값 가져와서 저장
const handleDname = useCallback((value) => {
console.log(value)
setDname(value)
}, [])
// loc 값 가져와서 저장
const handleLoc = useCallback((value) => {
console.log(value)
setLoc(value)
}, [])
// 조건 검색 구현
const reactSearch = () => {
// select 콤보에서 선택한 값 담기
const gubun = document.querySelector('#gubun').value
// 조건검색에 필요한 문자열 담기
const keyword = document.querySelector('#keyword').value
console.log(gubun + ", " + keyword)
const asyncDB = async () => {
// 키와 값이 같을경우 생략가능 gubun:gubun -> gubun
const res = await deptListDB({gubun, keyword, deptno: 0})
console.log(res.data)
if(res.data) {
setDeptList(res.data)
}
}
asyncDB()
}
// 부서목록 JSON포맷 가져오기
const jsonDeptList = async() => {
const res = await deptListDB({deptno: 0})
console.log(res.data)
if(res.data) {
setDeptList(res.data)
} else {
console.log('부서목록 조회 실패')
}
}
// 이미지 파일 첨부 구현
const imgChange = async(event) => {
const uploaded = await imageUploader.upload(event.target.files[0])
console.log(uploaded)
setFiles({
filename: uploaded.public_id + "." + uploaded.format,
fileurl: uploaded.url
})
// input의 이미지 객체 얻어오기
const upload = document.querySelector("#dimg")
// 이미지를 집어넣을 곳의 부모태그
const holder = document.querySelector("#uploadImg")
const file = upload.files[0]
const reader = new FileReader()
reader.onload = (event) => {
const img = new Image()
img.src = event.target.result
if(img.width > 150) {
img.width = 150
}
holder.innerHTML = "";
holder.appendChild(img)
}
reader.readAsDataURL(file)
return false
}
// 부서 등록 구현
// 스프링 부트와 리액트 연동하기 - @RequestBody사용해서 JSON포맷으로 넘김
const deptInsert = async() => {
const dept = {
deptno,
dname,
loc,
filename: files.filename,
fileurlL: files.fileurl
}
const res = await deptInsertDB(dept)
console.log(res + ", " + res.data)
if(!res.data) {
console.log('부서등록에 실패하였습니다.')
} else {
console.log('부서등록 성공!')
// 성공시 부서목록 새로고침 처리 - window.location.reload()쓰지 말것! - SPA컨벤션
// useEffect - 의존성 배열 연습할 수 있음
handleClose() // 모달창을 닫기
// 부서목록 새로고침 처리
navigate("/dept/1")
}
}
useEffect (() => {
jsonDeptList()
}, [gubun]) // 의존성배열이 빈 배열이면 최초 한번만
// 의존성 배열에 올 수 있는 변수는 전역변수!
return (
<React.Fragment>
<BlogHeader />
<div className='container'>
<div className="page-header">
<h2>부서관리 <i className="fa-solid fa-angles-right"></i> <small>부서목록</small></h2>
<hr />
</div>
<div className="row">
<div className="col-3">
<select id="gubun" className="form-select" aria-label="분류선택">
<option defaultValue>분류선택</option>
<option value="deptno">부서번호</option>
<option value="dname">부서명</option>
<option value="loc">지역</option>
</select>
</div>
<div className="col-6">
<input type="text" id="keyword" className="form-control" placeholder="검색어를 입력하세요"
aria-label="검색어를 입력하세요" aria-describedby="btn_search" />
</div>
<div className="col-3">
<Button variant='danger' id="btn_search" onClick={reactSearch}>검색</Button>
</div>
</div>
<div className='book-list'>
<Table striped bordered hover>
<thead>
<tr>
<th>#</th>
<th>부서번호</th>
<th>부서명</th>
<th>지역</th>
</tr>
</thead>
<tbody>
{deptList.map(dept => (
<DeptRow key={dept.DEPTNO} dept={dept} />
))}
</tbody>
</Table>
<hr />
<div className='booklist-footer'>
<Button variant="warning" onClick={jsonDeptList}>
전체조회
</Button>
<Button variant="success" onClick={handleShow}>
부서등록
</Button>
</div>
</div>
</div>
{/* ========================== [[ 도서등록 Modal ]] ========================== */}
<Modal show={show} onHide={handleClose} animation={false}>
<Modal.Header closeButton>
<Modal.Title>부서등록</Modal.Title>
</Modal.Header>
<Modal.Body>
<div style={{display: 'flex', flexWrap: 'wrap', justifyContent: 'center'}}>
<div style={{display: 'flex'}}>
<MyLabel>부서번호<span style={{color: 'red'}}>{star.deptno}</span>
<MyInput type="text" id="deptno" placeholder="Enter 부서번호" onChange={(e) => {handleDeptno(e.target.value)}} />
<MyLabelAb>{comment.deptno}</MyLabelAb>
</MyLabel>
</div>
<div style={{display: 'flex'}}>
<MyLabel>부서명<span style={{color: 'red'}}>{star.dname}</span>
<MyInput type="text" id="dname" placeholder="Enter 부서명" onChange={(e) => {handleDname(e.target.value); validate('dname', e);}} />
<MyLabelAb>{comment.dname}</MyLabelAb>
</MyLabel>
</div>
<div style={{display: 'flex'}}>
<MyLabel>지역<span style={{color: 'red'}}>{star.loc}</span>
<MyInput type="text" id="loc" placeholder="Enter 지역" onChange={(e) => {handleLoc(e.target.value)}} />
<MyLabelAb>{comment.loc}</MyLabelAb>
</MyLabel>
</div>
<Form.Group className="mb-3" controlId="formBasicOffice">
<Form.Label>건물이미지</Form.Label>
<input className="form-control" type="file" accept='image/*' id="dimg" name="dimg" onChange={imgChange}/>
</Form.Group>
<div id="uploadImg">
<img className='thumbNail' src="http://via.placeholder.com/200X250" alt="미리보기" />
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
닫기
</Button>
<Button variant="primary" onClick={deptInsert}>
저장
</Button>
</Modal.Footer>
</Modal>
{/* ========================== [[ 부서등록 Modal ]] ========================== */}
</React.Fragment>
)
}
export default DeptPage
<dept 리액트 스프링 연동 - DeptRow.jsx>
import React from 'react'
import { Link } from 'react-router-dom'
const DeptRow = ({dept}) => {
return (
<>
<tr>
<td>{dept.DEPTNO}</td>
<td>
<Link to={"/deptdetail" + dept.DEPTNO} className="btn btn-primary">{dept.DEPTNO}</Link>
</td>
<td>{dept.DNAME}</td>
<td>{dept.LOC}</td>
</tr>
</>
)
}
export default DeptRow
<dept 리액트 스프링 연동 - dbLogic.js>
import axios from "axios";
export const deptInsertDB = (dept) => {
return new Promise((resolve, reject) => {
try {
const response = axios({
method: "post", // @RequestBody
url: process.env.REACT_APP_SPRING_IP + "dept/deptInsert",
data: dept, // post방식 data
});
resolve(response);
} catch (error) {
reject(error);
}
});
};
export const deptListDB = (dept) => {
return new Promise((resolve, reject) => {
try {
const response = axios({
method: "get",
url: process.env.REACT_APP_SPRING_IP + "dept/deptList",
params: dept, // 쿼리스트링은 header에 담김 - get방식 params
});
resolve(response);
} catch (error) {
reject(error);
}
});
};
export const jsonMemberListDB = (member) => {
return new Promise((resolve, reject) => {
try {
const response = axios({
method: "get",
url: process.env.REACT_APP_SPRING_IP + "member/jsonMemberList",
params: member,
});
resolve(response);
} catch (error) {
reject(error);
}
});
};
<dept 리액트 스프링 연동 - validateLogic.js>
export const validateDname = (e) => {
// 사용자가 입력한 부서명
const name = e.target.value // input onChange
const kor = /^[ㄱ-ㅎ가-힣]+$/;
const eng = /^[a-zA-Z]+$/;
if(name.length === 0) {
return " ";
} else if (kor.test(name) || eng.test(name)) { // test의 리턴타입은 boolean
return "";
} else {
return "부서명은 영어 또는 한글만 가능합니다.";
}
}
<dept 리액트 스프링 연동 - DeptVO.java>
package com.example.demo.vo;
import lombok.Builder;
import lombok.Data;
@Data // getter와 setter 역할 대신하는 어노테이션
@Builder // 생성자 생성 역할 어노테이션
public class DeptVO {
private int deptno;
private String dname;
private String loc;
private String filename;
private String fileurl;
}
<dept 리액트 스프링 연동 - RestDeptController.java>
package com.example.demo.controller;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.logic.DeptLogic;
import com.example.demo.vo.DeptVO;
import com.google.gson.Gson;
@RestController
@RequestMapping("/dept/*")
public class RestDeptController {
Logger logger = LoggerFactory.getLogger(RestDeptController.class);
@Autowired
private DeptLogic deptLogic = null;
/**************************************************
* 부서 조회
* @param pMap
* @return
**************************************************/
@GetMapping("deptList")
public String deptList(@RequestParam Map<String, Object> pMap) {
List<Map<String, Object>> dList = null;
dList = deptLogic.deptList(pMap);
Gson g = new Gson();
String temp = g.toJson(dList);
return temp;
}
/**************************************************
* 부서등록 구현
* @param pdVO
* @return insert문 성공 여부 : 1이면 성공, 0이면 실패
**************************************************/
// 단위 테스트 URL은
// <http://localhost:8000/dept/deptInsert이나> 테스트는 불가함(포스트맨 사용)
@PostMapping("deptInsert")
public String deptInsert(@RequestBody DeptVO pdVO) {
logger.info(pdVO.toString());
int result = 0;
result = deptLogic.deptInsert(pdVO);
return String.valueOf(result);
}
@PutMapping("deptUpdate")
public String deptUpdate(@RequestBody DeptVO pdVO) {
logger.info(pdVO.toString());
int result = 0;
result = deptLogic.deptUpdate(pdVO);
return String.valueOf(result);
}
@DeleteMapping("deptDelete")
public String deptDelete(int deptno) {
// 연습용으로 파라미터 원시형타입 deptno
logger.info("사용자가 선택한 부서번호(단, 자손이 없어야 함)=> " + deptno);
int result = 0;
result = deptLogic.deptDelete(deptno);
return String.valueOf(result);
}
}
/*
@Controller는 리턴값이 화면 출력으로 사용됨
@RestController는 리턴값에 대한 마임타입이 text/plain으로 사용됨
리액트 연동시 조회된 결과나 백엔드에서 전달되는 값은 text이거나 json포맷만 가능하므로
@RestController를 사용함
@RequestBody는 post방식으로 사용자가 입력한 값이 보안상 중요한 정보인 경우 사용가능
패킷 헤더가 아닌 바디에 저장되므로 노출 위험이 없음
@RequestParam는 get방식
*/
<dept 리액트 스프링 연동 - DeptLogic.java>
package com.example.demo.logic;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.dao.DeptDao;
import com.example.demo.vo.DeptVO;
@Service
public class DeptLogic {
Logger logger = LoggerFactory.getLogger(DeptLogic.class);
@Autowired
private DeptDao deptDao = null;
public List<Map<String, Object>> deptList(Map<String, Object> pMap) {
List<Map<String, Object>> dList = null;
dList = deptDao.deptList(pMap);
return dList;
}
public int deptInsert(DeptVO pVO) {
int result = 0;
result = deptDao.deptInsert(pVO);
return result;
}
public int deptUpdate(DeptVO pVO) {
int result = 0;
result = deptDao.deptUpdate(pVO);
return result;
}
public int deptDelete(int deptno) {
int result = 0;
result = deptDao.deptDelete(deptno);
return result;
}
}
<dept 리액트 스프링 연동 - DeptDao.java>
package com.example.demo.dao;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.example.demo.vo.DeptVO;
@Repository
public class DeptDao {
Logger logger = LoggerFactory.getLogger(DeptDao.class);
// DatabaseConfigutation에서 @Configuration으로 빈둥록된 객체 주입받기 코드임
// application.properties에서 물리적으로 떨어져있는 오라클 서버 정보 받음
// DML문을 가진 xml문은 src/main/resources아래 있음
@Autowired
private SqlSessionTemplate sqlSessionTemplate = null;
public List<Map<String, Object>> deptList(Map<String, Object> pMap) {
List<Map<String, Object>> dList = null;
dList = sqlSessionTemplate.selectList("deptList", pMap);
return dList;
}
public int deptInsert(DeptVO pVO) {
int result = 0;
result = sqlSessionTemplate.update("deptInsert", pVO);
return result;
}
public int deptUpdate(DeptVO pVO) {
int result = 0;
result = sqlSessionTemplate.update("deptUpdate", pVO);
logger.info("" + result);
return result;
}
public int deptDelete(int deptno) {
int result = 0;
result = sqlSessionTemplate.delete("deptDelete", deptno);
logger.info("" + result);
return result;
}
}
<dept 리액트 스프링 연동 - dept.xml>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo">
<!-- 부서조회 -->
<select id="deptList" parameterType="map" resultType="map">
SELECT deptno, dname, loc, filename, fileurl from dept
<where>
<if test='deptno!=null and deptno > 0'>
AND deptno = #{deptno}
</if>
<if test='gubun!=null and gubun.equals("deptno")'>
AND deptno LIKE '%'||#{keyword}||'%'
</if>
<if test='gubun!=null and gubun.equals("dname")'>
AND dname LIKE '%'||#{keyword}||'%'
</if>
<if test='gubun!=null and gubun.equals("loc")'>
AND loc LIKE '%'||#{keyword}||'%'
</if>
</where>
order by deptno desc
</select>
<!-- 부서입력 -->
<insert id="deptInsert" parameterType="com.example.demo.vo.DeptVO">
INSERT INTO dept(deptno, dname, loc
<if test="filename != null">
,filename
</if>
<if test="fileurl != null">
,fileurl
</if>
)
VALUES (#{deptno}, #{dname}, #{loc}
<if test="filename != null">
,#{filename}
</if>
<if test="fileurl != null">
,#{fileurl}
</if>
)
</insert>
<!-- 부서 정보 수정 -->
<update id="deptUpdate" parameterType="map">
UPDATE dept
SET dname = #{dname}
,loc = #{loc}
<where>
<if test='deptno!=null and deptno > 0'>
AND deptno = #{deptno}
</if>
</where>
</update>
<!-- 부서 정보 삭제 -->
<delete id="deptDelete" parameterType="int">
DELETE from dept
<where>
<if test='value!=null and value > 0'>
AND deptno = #{value}
</if>
</where>
</delete>
</mapper>
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day81 (0) | 2023.03.25 |
---|---|
국비 지원 개발자 과정_Day80 (0) | 2023.03.23 |
국비 지원 개발자 과정_Day78 (0) | 2023.03.21 |
국비 지원 개발자 과정_Day77 (0) | 2023.03.20 |
국비 지원 개발자 과정_Day76 (0) | 2023.03.17 |
댓글