본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.17 React 2 (xhr, fetch, axios, CRUD, JPA)

STS 4

설치 후 ini 파일에서 Xms만 1024로 변경
해당 버전으로는 legacy 프로젝트 생성이 불가능하다.

Back - boot, mybatis

properties 대신 yaml로 작성 시

spring :
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.99.100:3306/mydb
    username: scott
    password: tiger 
    hikari:
      connection-timeout: 3000
      validation-timeout: 1000
      maximum-pool-size: 10


logging:
  level:
    web: info
    '[com.bit.boot04]': debug 

STS 4 설치 후 lombok 설치 (STS가 꺼져있어야 제대로 적용된다)

java -jar C:\Users\BIT\.m2\repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar
또는 공식홈페이지에서 lombok 받아서 실행하면 끝

ResponseEntity

@GetMapping
	public ResponseEntity<?> index() {
		log.debug("index...");
		// 문자열을 보내면 text로 List와같은 자료구조를 보내면 jackson이 알아서 json으로 리턴해줌
//		ResponseEntity resp = new ResponseEntity("성공",HttpStatus.OK);
//		ResponseEntity resp = new ResponseEntity(new ArrayList(),HttpStatus.OK);
//		return resp
//		if (true) {
//			return new ResponseEntity<DeptVo>(DeptVo.builder().deptno(1111).dname("tester").loc("test").build(),
//					HttpStatus.OK);
//			return ResponseEntity.status(HttpStatus.OK).body(DeptVo.builder().deptno(1111).dname("tester").loc("test").build());
			return ResponseEntity.ok(DeptVo.builder().deptno(1111).dname("tester").loc("test").build());
//		} else {
//			return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
//		}
	}

Config

@Configuration
@MapperScan(basePackages = "com.bit.boot04.model")
@AllArgsConstructor
public class ServletConfig implements WebMvcConfigurer {

	// sqlSession 주입을 못 받을 경우...
//    @Bean
//    SqlSession getSqlSession() {
//		return new SqlSessionTemplate(sqlSessionFactory);
//	}
}

Controller

import ...;

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/")
public class DeptController {
	private final DeptService deptService;
	
	@GetMapping
	@CrossOrigin
	public List<?> list() {
		return deptService.selectAll();
	}
	
	@CrossOrigin
	@PostMapping
	public ResponseEntity<?> add(@ModelAttribute("bean") DeptVo bean) {
		try {
			deptService.insertOne(bean);
		} catch (Exception e) {
			e.printStackTrace();
			return ResponseEntity.badRequest().build();
		}
		return ResponseEntity.ok("success");
	}
	
	@CrossOrigin
	@GetMapping("/{deptno}")
//	public DeptVo detail(@PathVariable("deptno") int deptno) {
	//	return deptService.selectOne(deptno);
	public ResponseEntity<?> detail(@PathVariable("deptno") int deptno) {
		DeptVo bean = deptService.selectOne(deptno);
		if(bean!=null) {
			return ResponseEntity.ok(bean);
		} else {
			return ResponseEntity.notFound().build();
		}
	}
	
	@CrossOrigin
	@PutMapping("/{deptno}")
	public ResponseEntity<?> edit(@RequestBody DeptVo bean, HttpServletRequest req) throws URISyntaxException {
		// API 호출할 때 RestTemplate을 이용해서 받아올 수 있다.
		if(deptService.updateOne(bean) > 0 ) {
			RestTemplate template = new RestTemplate();
			URI url = new URI(req.getRequestURL().toString());
			RequestEntity param = new RequestEntity(HttpMethod.GET, url);
			return template.exchange(url, HttpMethod.GET, param, DeptVo.class);
//			return ResponseEntity.ok(bean);
		}
		return ResponseEntity.internalServerError().build();
	}
	
	@CrossOrigin
	@DeleteMapping("/{deptno}")
	public ResponseEntity<?> del(@PathVariable("deptno") int deptno) {
		if(deptService.deleteOne(deptno) > 0) {
			return ResponseEntity.ok("success");
		}
		return ResponseEntity.badRequest().build();
	}
}

Service

package com.bit.boot04.service;

import ...;

@Service
@AllArgsConstructor
public class DeptService {
	private final SqlSession sqlSession;
	
	public List<DeptVo> selectAll() {
		return sqlSession.getMapper(DeptMapper.class).findAll();
	}
	
	public DeptVo selectOne(int deptno) {
		return sqlSession.getMapper(DeptMapper.class).findOne(deptno);
	}
	
	@Transactional
	public void insertOne(DeptVo bean) {
		sqlSession.getMapper(DeptMapper.class).insertOne(bean);
	}
	
	@Transactional
	public int updateOne(DeptVo bean) {
		return sqlSession.getMapper(DeptMapper.class).updateOne(bean);
	}
	
	@Transactional
	public int deleteOne(int deptno) {
		return sqlSession.getMapper(DeptMapper.class).deleteOne(deptno);
	}
}

Mapper

import ...;

@Mapper
public interface DeptMapper {
	@Select("select * from dept")
	List<DeptVo> findAll();
	
	@Select("select * from dept where deptno=#{pk}")
	DeptVo findOne(int pk);
	
	@Insert("insert into dept value(#{deptno}, #{dname}, #{loc})")
	void insertOne(DeptVo bean);
	
	@Update("update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}")
	int updateOne(DeptVo bean);
	
	@Delete("delete from dept where deptno=#{pk}")
	int deleteOne(int pk);
}

Front - React

npx create-react-app .
npm i -D react-router-dom
npm i axios
npm start

index.html - CDN 추가

<head>
	<meta charset="utf-8" />
	<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
	<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
	<!-- Latest compiled and minified CSS -->
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
	<!-- Optional theme -->
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
	<!-- Latest compiled and minified JavaScript -->
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<title>React App</title>
</head>

App.js

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import Layout from './components/Layout';
import DeptAdd from './pages/DeptAdd';
import DeptDetail from './pages/DeptDetail';
import DeptList from './pages/DeptList';
import Home from './pages/Home';
import Intro from './pages/Intro';

function App() {
  return (
      <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="intro" element={<Intro />} />
          <Route path="dept/" element={<DeptList />} />
          <Route path="dept/add" element={<DeptAdd />} />
          <Route path="dept/:deptno" element={<DeptDetail />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Layout.js

import React from 'react'
import { Link, NavLink, Outlet } from 'react-router-dom'

export default function Layout() {
  return (
    <>
    <nav className="navbar navbar-inverse">
        <div className="container-fluid">
            <div className="navbar-header">
            <button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span className="sr-only">Toggle navigation</span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
            </button>
            <Link className="navbar-brand" to="/">비트교육센터</Link>
            </div>

            <div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul className="nav navbar-nav">
                    <li><NavLink className={({ isActive, isPending }) =>isPending ? "pending" : isActive ? "active" : ""} to="/" end>Home</NavLink></li>
                    <li><Link to="/intro">Intro</Link></li>
                    <li><Link to="/dept/">Dept</Link></li>
                </ul>
            </div>
        </div>
    </nav>
    <div className='container'>
        <Outlet/>
    </div>
    </>
  )
}

DeptList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'

export default function DeptList() {
  const [depts, setDepts] = useState([]);
  // const getList = new Promise((resolve, reject)=>{
  //   const xhr = new XMLHttpRequest();
  //   xhr.onload=(e)=>{
  //     if(xhr.readyState===4 && xhr.status===200){
  //       const result = JSON.parse(xhr.response);
  //       resolve(result);
  //     } else {
  //       reject(xhr.status);
  //     }
  //   };
  //   xhr.open('get', 'http://localhost:8080/api/');
  //   xhr.send();
  // })

  useEffect(()=>{
    // xhr
    // getList.then(function(result){
    //   setDepts(result);
    // })

    // fetch
    // fetch('http://localhost:8080/api/')
    // .then((e)=>e.json())
    // .then((e)=>{
    //     setDepts(e);
    // })

    //axios
    axios.get('http://localhost:8080/api/')
    .then(e=>setDepts(e.data));
  }, []);
  return (
    <>
    <div className="page-header">
      <h1>Dept List</h1>
    </div>

    <Link to='add' className='btn btn-primary btn-block' role='button'>add</Link>
    
    {depts.map((ele)=>(
      <Link to={"./"+ele.deptno} key={ele.deptno}>
      <div className="panel panel-primary">
        <div className="panel-heading">{ele.dname}</div>
        <div className="panel-body">{ele.loc}</div>
      </div>
      </Link>
    ))}
    </>
  )
}

DeptDetail.js

import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'

export default function DeptDetail() {
  const {deptno} = useParams();

  // 디테일 출력
  const [bean, setBean] = useState({});
  useEffect(()=>{
    axios.get('http://localhost:8080/api/'+deptno)
    .then(e=>{setBean(e.data); setDeptInput(e.data)});
  },[]);

  // 디테일 <-> 수정 스왑
  const [edit, setEdit] = useState(false);
  const editForm = e =>{
    setEdit(true);
  };

  // submit
  const sbt = e =>{
    e.preventDefault();
    // axios.put('http://localhost:8080/api/'+deptno, {
    //   deptno:e.target.deptno.value ,
    //   dname:e.target.dname.value,
    //   loc:e.target.loc.value
    // }).then(e=>navigate(-1));
    let params = {
      deptno:e.target.deptno.value ,
      dname:e.target.dname.value,
      loc:e.target.loc.value
    }
    axios({
      url: 'http://localhost:8080/api/'+deptno,
      method: 'put',
      data: params
    }).then(e=>navigate(-1));
  };
  
  // back
  const navigate = useNavigate();
  const back = e => {
    if(edit){
      setEdit(!edit);
    } else {
      navigate(-1);
    }
  };

  // 내용 입력할 수 있도록
  const [deptInput, setDeptInput] = useState({dname:bean.dname, loc:bean.loc});
  const dnameInput = e=>{
    setDeptInput({...deptInput, dname: e.target.value});
  }
  const locInput = e=>{
    setDeptInput({...deptInput, loc: e.target.value});
  }

  // 삭제
  const del = () => {
    axios.delete('http://localhost:8080/api/'+deptno)
    .then(e=>navigate(-1));
  }


  return (
    <>
    <div className="page-header">
      <h1>{!edit ?'Detail ':'Update '}Page</h1>
    </div>

    <form onSubmit={sbt}>
        <div className='form-group'>
          {!edit?bean.deptno:<input className='form-control' name='deptno' value={bean.deptno}/>}
        </div>
        <div className='form-group'>
          {!edit?bean.dname:<input className='form-control' name='dname' value={deptInput.dname} onChange={dnameInput}/>}
        </div>
        <div className='form-group'>
          {!edit?bean.loc:<input className='form-control' name='loc' value={deptInput.loc} onChange={locInput}/>}
        </div>
        <div className='form-group'>
          {!edit
          ?<>
          <button onClick={editForm} className='btn btn-primary btn-block' type='button'>수정</button>
          <button onClick={del} className='btn btn-danger btn-block' type='button'>삭제</button>
          </>
          :<button className='btn btn-primary btn-block' type='submit'>입력</button>
          }
          <button className='btn btn-default btn-block' type='reset'>취소</button>
          <button onClick={back} className='btn btn-default btn-block' type='button'>뒤로</button>
        </div>
    </form>
    </>
  )
}

DeptAdd.js

import axios from 'axios';
import { useNavigate } from 'react-router-dom';

export default function DeptAdd() {
    const navigate = useNavigate();
    const back = () => navigate(-1);
    const sub = e => {
        e.preventDefault();
        console.log(e);
        // @ResponseBody
        // let params = {
        //     deptno:Number(e.target.deptno.value),
        //     dname: e.target.dname.value,
        //     loc: e.target.loc.value
        // };

        // @ModelAttribute
        let params = 'deptno='+Number(e.target.deptno.value)
                    + '&dname='+e.target.dname.value
                    + '&loc='+e.target.loc.value

        // axios.post('http://localhost:8080/api/',params);
        axios({
            method:'post',
            url:'http://localhost:8080/api/',
            data: params
        }).then(()=>{
            return navigate('/dept');
        })
    };
  return (
    <>
    <div className="page-header">
      <h1>Dept List</h1>
    </div>

    <form onSubmit={sub}>
        <div className='form-group'>
            <input className='form-control' name='deptno' placeholder='deptno'/>
        </div>
        <div className='form-group'>
            <input className='form-control' name='dname' placeholder='dname'/>
        </div>
        <div className='form-group'>
                <input className='form-control' name='loc' placeholder='loc'/>
        </div>
        <div className='form-group'>
            <button className='btn btn-primary btn-block' type='submit'>입력</button>
            <button className='btn btn-default btn-block' type='reset'>취소</button>
            <button onClick={back} className='btn btn-default btn-block' type='button'>뒤로</button>
        </div>

    </form>
    </>
  )
}

JPA - 프로젝트 생성시 JPA 라이브러리 추가 필요

properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.99.100/mydb
spring.datasource.username=scott
spring.datasource.password=tiger

logging.level.web=info
logging.level.db=debug
logging.level.com.bit.boot05=debug

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Entity

import ...;

@Entity
@ToString
@Getter
public class Dept2 {
	@Id
	private int deptno;
	private String dname;
	private String loc;
}

DeptRepo

package com.bit.boot05.domain;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bit.boot05.domain.entity.Dept2;

public interface DeptRepo extends JpaRepository<Dept2, Integer> {
	
}

Controller

import ...;

@RestController
@RequestMapping("/api/")
@AllArgsConstructor
public class DeptController {
	private final DeptService deptService; 
	
	@GetMapping
	public List<DeptVo> getList() {
		return (List<DeptVo>) deptService.selectAll();
	}
}

Service

package com.bit.boot05.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.bit.boot05.domain.DeptRepo;
import com.bit.boot05.domain.entity.DeptVo;

import lombok.AllArgsConstructor;

@Service
@AllArgsConstructor
public class DeptService {
	private final DeptRepo deptRepo;
	
	public List<?> selectAll() {
		List<DeptVo> list = new ArrayList<>();
		deptRepo.findAll().forEach((ele)->{
			list.add(
					DeptVo.builder()
					.deptno(ele.getDeptno())
					.dname(ele.getDname())
					.loc(ele.getLoc())
					.build());
		});
		return list;
	}
	
}