Setting
Oracle DB 주석 하고 H2 Database 주석 지우기
ddl-auto 를 `create-drop` 으로 변경
public interface DeptRepository extends JpaRepository<Dept, Integer>{
}
DeptRepository 의존 주입@Autowired
DeptRepository deptRepo;
Spring09JpaApplication 에 부서 샘플 데이터 주입
Emp가 Dept 테이블의 deptno 를 참조하기에 Dept 샘플 데이터를 먼저 주입해야 한다
// 부서 정보 저장하기
Dept d10 = new Dept(10, "ACCOUNTING", "NEW YORK");
Dept d20 = Dept.builder().deptno(20).dname("RESEARCH").loc("DALLAS").build();
Dept d30 = Dept.builder().deptno(30).dname("SALES").loc("CHICAGO").build();
Dept d40 = new Dept(40, "OPERATIONS", "BOSTON");
deptRepo.save(d10);
deptRepo.save(d20);
deptRepo.save(d30);
deptRepo.save(d40);
List<Dept> 를 전달해서 한 번에 저장할 수도 있다deptRepo.saveAll(List.of(d10, d20, d30, d40));
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy");
// 2) 사원 저장 (Builder 사용)
empRepo.saveAll(List.of(
Emp.builder().empno(7369).ename("SMITH").job("CLERK").mgr(7902)
.hiredate(LocalDate.parse("17-12-1980", fmt)).sal(800.0).comm(null).dept(d20).build(),
Emp.builder().empno(7499).ename("ALLEN").job("SALESMAN").mgr(7698)
.hiredate(LocalDate.parse("20-02-1981", fmt)).sal(1600.0).comm(300.0).dept(d30).build(),
Emp.builder().empno(7521).ename("WARD").job("SALESMAN").mgr(7698)
.hiredate(LocalDate.parse("22-02-1981", fmt)).sal(1250.0).comm(500.0).dept(d30).build(),
Emp.builder().empno(7566).ename("JONES").job("MANAGER").mgr(7839)
.hiredate(LocalDate.parse("02-04-1981", fmt)).sal(2975.0).comm(null).dept(d20).build(),
Emp.builder().empno(7654).ename("MARTIN").job("SALESMAN").mgr(7698)
.hiredate(LocalDate.parse("28-09-1981", fmt)).sal(1250.0).comm(1400.0).dept(d30).build(),
Emp.builder().empno(7698).ename("BLAKE").job("MANAGER").mgr(7839)
.hiredate(LocalDate.parse("01-05-1981", fmt)).sal(2850.0).comm(null).dept(d30).build(),
Emp.builder().empno(7782).ename("CLARK").job("MANAGER").mgr(7839)
.hiredate(LocalDate.parse("09-06-1981", fmt)).sal(2450.0).comm(null).dept(d10).build(),
Emp.builder().empno(7839).ename("KING").job("PRESIDENT").mgr(null)
.hiredate(LocalDate.parse("17-11-1981", fmt)).sal(5000.0).comm(null).dept(d10).build(),
Emp.builder().empno(7844).ename("TURNER").job("SALESMAN").mgr(7698)
.hiredate(LocalDate.parse("08-09-1981", fmt)).sal(1500.0).comm(0.0).dept(d30).build(),
Emp.builder().empno(7900).ename("JAMES").job("CLERK").mgr(7698)
.hiredate(LocalDate.parse("03-12-1981", fmt)).sal(950.0).comm(null).dept(d30).build(),
Emp.builder().empno(7902).ename("FORD").job("ANALYST").mgr(7566)
.hiredate(LocalDate.parse("03-12-1981", fmt)).sal(3000.0).comm(null).dept(d20).build(),
Emp.builder().empno(7934).ename("MILLER").job("CLERK").mgr(7782)
.hiredate(LocalDate.parse("23-01-1982", fmt)).sal(1300.0).comm(null).dept(d10).build()
));
<li><a th:href="@{/emps}">사원 목록</a></li>
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmpDto {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private LocalDate hiredate;
private Double sal;
private Double comm;
private Integer deptno;
public static EmpDto toDto(Emp e) {
return EmpDto.builder()
.empno(e.getEmpno())
.ename(e.getEname())
.job(e.getJob())
.mgr(e.getMgr())
.hiredate(e.getHiredate())
.sal(e.getSal())
.comm(e.getComm())
.deptno(e.getDept().getDeptno()) // 부서번호를 넣어주는 부분에 주목
.build();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeptDto {
private Integer deptno;
private String dname;
private String loc;
public static DeptDto toDto(Dept d) {
return DeptDto.builder()
.deptno(d.getDeptno())
.dname(d.getDname())
.loc(d.getLoc())
.build();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmpDeptDto {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private LocalDate hiredate;
private Double sal;
private Double comm;
//Emp 와 Dept 에 같이 있는 정보
private Integer deptno;
//Dept Entity 에만 있는 정보
private String dname;
private String loc;
public static EmpDeptDto toDto(Emp emp) {
return EmpDeptDto.builder()
.empno(emp.getEmpno())
.ename(emp.getEname())
.job(emp.getJob())
.mgr(emp.getMgr())
.hiredate(emp.getHiredate())
.sal(emp.getSal())
.comm(emp.getComm())
.deptno(emp.getDept().getDeptno())
.dname(emp.getDept().getDname())
.loc(emp.getDept().getLoc())
.build();
}
}
public interface EmployService {
public List<EmpDto> getEmpList();
public List<DeptDto> getDeptList();
public EmpDeptDto getEmpDetail(int empno);
public DeptDto getDeptDetail(int deptno);
}
@Service
@RequiredArgsConstructor
public class EmployServiceImpl implements EmployService{
private final EmpRepository empRepo;
private final DeptRepository deptRepo;
@Transactional(readOnly = true)
@Override
public List<EmpDto> getEmpList() {
return empRepo.findAll().stream().map(EmpDto :: toDto).toList();
}
@Transactional(readOnly = true)
@Override
public List<DeptDto> getDeptList() {
return deptRepo.findAll().stream().map(DeptDto :: toDto).toList();
}
@Transactional(readOnly = true)
@Override
public EmpDeptDto getEmpDetail(int empno) {
// 사원 번호를 이용해서 Emp entity 를 얻어내고
Emp e = empRepo.findById(empno).get();
// Emp entity 를 EmpDeptDto 로 변경해서 리턴한다
return EmpDeptDto.toDto(e);
}
@Transactional(readOnly = true)
@Override
public DeptDto getDeptDetail(int deptno) {
return DeptDto.toDto(deptRepo.findById(deptno).get());
}
}

@RequiredArgsConstructor
@Controller
public class EmployController {
private final EmployService employService;
}
empList 메소드 생성@GetMapping("/emps")
public String empList(Model model) {
// 사원 목록을 Model 객체에 담는다
model.addAttribute("empList", employService.getEmpList());
return "emps/list";
}
<div class="container">
<h1>사원 목록</h1>
<table class="table table-striped">
<thead>
<tr>
<th>사원 번호</th>
<th>사원 이름</th>
<th>직책</th>
<th>자세히</th>
</tr>
</thead>
<tbody>
<tr th:each="tmp : ${empList}">
<td th:text="${tmp.empno}"></td>
<td th:text="${tmp.ename}"></td>
<td th:text="${tmp.job}"></td>
<td>
<a th:href="@{|/emps/${tmp.empno}|}">보기</a>
</td>
</tr>
</tbody>
</table>
</div>
empDetail 메소드 생성@GetMapping("/emps/{empno}")
public String empDetail(@PathVariable int empno, Model model) {
// 사원의 자세한 정보
EmpDeptDto dto = employService.getEmpDetail(empno);
// 응답에 필요한 데이터를 "emp" 라는 키값으로 담기
model.addAttribute("emp", dto);
return "emps/detail";
}
<div class="container my-5">
<!-- 제목 -->
<h2 class="mb-4">사원 상세 정보</h2>
<!-- 사원 카드 -->
<div class="card shadow-lg rounded-3">
<div class="card-header bg-primary text-white">
<strong th:text="${emp.ename}"></strong>
<span class="ms-2 text-light">(사원번호: <span th:text="${emp.empno}"></span>)</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<h5 class="card-title">기본 정보</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">직책: <span th:text="${emp.job}"></span></li>
<li class="list-group-item">상사번호: <span th:text="${emp.mgr}"></span></li>
<li class="list-group-item">입사일: <span th:text="${#temporals.format(emp.hiredate, 'yyyy-MM-dd')}"></span></li>
</ul>
</div>
<div class="col-md-6">
<h5 class="card-title">급여 정보</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">급여: <span th:text="${emp.sal}"></span></li>
<li class="list-group-item">커미션:
<span th:text="${emp.comm != null ? emp.comm : '-'}"></span>
</li>
</ul>
</div>
</div>
<hr>
<h5 class="card-title">부서 정보</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">부서번호: <span th:text="${emp.deptno}"></span></li>
<li class="list-group-item">부서명: <span th:text="${emp.dname}"></span></li>
<li class="list-group-item">지역: <span th:text="${emp.loc}"></span></li>
</ul>
</div>
<div class="card-footer text-end">
<a th:href="@{/emps}" class="btn btn-secondary">목록으로</a>
</div>
</div>
</div>
<li><a th:href="@{/depts}">부서 목록</a></li>
deptList 메소드 생성@GetMapping("/depts")
public String deptList(Model model) {
// 부서 목록
List<DeptDto> deptList = employService.getDeptList();
model.addAttribute("deptList", deptList);
return "depts/list";
}
<div class="container">
<h1>부서 목록</h1>
<table class="table table-striped">
<thead>
<tr>
<th>부서 번호</th>
<th>부서명</th>
<th>자세히</th>
</tr>
</thead>
<tbody>
<tr th:each="tmp : ${deptList}">
<td th:text="${tmp.deptno}"></td>
<td th:text="${tmp.dname}"></td>
<td>
<a th:href="@{|/depts/${tmp.deptno}|}">보기</a>
</td>
</tr>
</tbody>
</table>
</div>
deptDetail 메소드 생성@GetMapping("/depts/{deptno}")
public String deptDetail(@PathVariable int deptno, Model model) {
// 경로 변수에 전달된 부서 번호를 이동해서 부서의 자세한 정보를 얻어와서
DeptDto dto = employService.getDeptDetail(deptno);
// Model 객체에 담고
model.addAttribute("dept", dto);
// 응답하기
return "depts/detail";
}
<div class="container">
<h1>부서 목록</h1>
<table class="table table-striped">
<thead>
<tr>
<th>부서 번호</th>
<th>부서명</th>
<th>자세히</th>
</tr>
</thead>
<tbody>
<tr th:each="tmp : ${deptList}">
<td th:text="${tmp.deptno}"></td>
<td th:text="${tmp.dname}"></td>
<td>
<a th:href="@{|/depts/${tmp.deptno}|}">보기</a>
</td>
</tr>
</tbody>
</table>
</div>
@Param("deptno") ⟷ :deptno// 실행할 query 문 (JPQL) 을 직접 작성한다
@Query("SELECT e FROM Emp e WHERE e.dept.deptno = :deptno ORDER BY e.ename ASC")
public List<Emp> findEmps(@Param("deptno") Integer deptno);
⭐ findAllByOrderByEnameAsc 메소드의 경우
정해진 규칙으로 메소드명을 생성
// 메소드에 전달된 매개변수의 순서를 이용해서 값을 바인딩할 수도 있다
@Query("SELECT e FROM Emp e WHERE e.dept.deptno = ?1 ORDER BY e.ename ASC")
public List<Emp> findEmps2(@Param("deptno") Integer deptno);
Emp entity @ManyToOne Dept dept 가 있기 때문에 이걸 할용해서 메소드 만들기
Dept_Deptno ➜ Emp 의 dept 필드를 타고 들어가 Dept entity 의 deptno 속성을 조건으로 사용extends JpaRepository<Emp, Integer> 에서 첫번째 generic type 이 Emp 이기 때문에 Emp entity 에서 dept 라는 필드를 타고 들어가는 것// 정해진 규칙으로 메소드명을 작성해서 위와 같은 결과 얻어내기
public List<Emp> findByDept_DeptnoOrderByEnameAsc(Integer deptno);
getEmpListByDeptno 메소드 생성public List<EmpDto> getEmpListByDeptno(int deptno);
@Transactional(readOnly = true)
@Override
public List<EmpDto> getEmpListByDeptno(int deptno) {
List<EmpDto> empList1 = empRepo.findEmps(deptno).stream().map(EmpDto :: toDto).toList();
List<EmpDto> empList2 = empRepo.findEmps2(deptno).stream().map(EmpDto :: toDto).toList();
List<EmpDto> empList3 = empRepo.findByDept_DeptnoOrderByEnameAsc(deptno)
.stream().map(EmpDto :: toDto).toList();
return empList1;
}
deptDetail 메소드에 코드 추가// 해당 부서에 근무하는 사원의 정보도 얻어와서 Model 객체에 담는다
List<EmpDto> empList = employService.getEmpListByDeptno(deptno);
model.addAttribute("empList", empList);
<h2>부서에서 근무하는 사원목록 <span class="text-primary" th:text="${empList.size()}"></span> 명</h2>
<ul>
<li th:each="tmp : ${empList}">
<a th:href="@{|/emps/${tmp.empno}|}" th:text="${tmp.ename}"></a>
</li>
</ul>
React
Terminal ➜ New Terminal 실행
npm 입력PS C:\playground\react\hello-app> npm
npm 다시 입력PS C:\playground\react\hello-app> Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
PS C:\playground\react\hello-app> npm
npm <command>
Usage:
npm install install all the dependencies in your project
npm install <foo> add the <foo> dependency to your project
npm test run this project's tests
npm run <foo> run the script named <foo>
npm <command> -h quick help on <command>
npm -l display usage info for all commands
npm help <term> search for help on <term> (in a browser)
npm help npm more involved overview (in a browser)
All commands:
access, adduser, audit, bugs, cache, ci, completion,
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
edit, exec, explain, explore, find-dupes, fund, get, help,
help-search, hook, init, install, install-ci-test,
install-test, link, ll, login, logout, ls, org, outdated,
owner, pack, ping, pkg, prefix, profile, prune, publish,
query, rebuild, repo, restart, root, run-script, sbom,
search, set, shrinkwrap, star, stars, start, stop, team,
test, token, uninstall, unpublish, unstar, update, version,
view, whoami
Specify configs in the ini-formatted file:
C:\Users\USER\.npmrc
or on the command line via: npm <command> --key=value
More configuration info: npm help config
Configuration fields: npm help 7 config
npm@10.9.3 C:\Program Files\nodejs\node_modules\npm
PS C:\playground\react\hello-app>
npm run dev 입력PS C:\playground\react\hello-app> npm run dev
package.json 파일 = pom.xml 파일과 비슷한 기능을 하는 문서
public 폴더 = 클라이언트에게 공개되는 폴더 = static 폴더와 같은 폴더
ctrl + c 로 서버 끄기PS C:\playground\react\hello-app> ^C
npm create vite react-basic
React & JavaScript 선택
생성한 폴더로 이동
cd react-basic
npm install
react-basic 폴더로 VSCODE 열기
react 서버 실행
npm run dev
App.jsx 에서 내용 지우고 뼈대만 남기기
function App() {
return (
<>
</>
)
}
export default App
div & h1 요소 추가<div className="container">
<h1>인덱스 페이지</h1>
</div>
index.css 안의 코드 다 지우기
assets 폴더 안에 images 폴더 삽입
import koreaImg from './assets/images/SouthKorea.png'
object 로 작성const myStyle = {
width:"100px",
height:"100px",
border:"1px solid green",
borderRadius:"50%"
};
<img src={koreaImg} alt="대한민국" style={myStyle}/>
h1{
color:red;
}
import './assets/css/custom.css'
bootstrap 설치PS C:\playground\react\react-basic> npm install bootstrap
import 'bootstrap/dist/css/bootstrap.css'
<button className="btn btn-primary">버튼</button>




컨트롤러에서 리턴하는 데이터를 json 으로 응답하고자 할 때 사용하는 어노테이션
hello 메소드 생성@RestController
public class RestMemberController {
@GetMapping("/v1/member/hello")
public String hello() {
return "hello, world";
}
}
hello, world
hello2 메소드 생성@GetMapping("/v1/member/hello2")
public MemberDto hello2() {
return MemberDto.builder().num(1).name("유재석").addr("압구정").build();
}
{"num":1,"name":"유재석","addr":"압구정"}
hello3 메소드 생성@GetMapping("/v1/member/hello3")
public List<String> hello3(){
return List.of("유재석", "박명수", "정준하");
}
["유재석","박명수","정준하"]
@RequestMapping 어노테이션 추가 &v1 지우기@RequestMapping("/v1")
@RestController
public class RestMemberController {
@GetMapping("/member/hello")
public String hello() {
return "hello, world";
}
@GetMapping("/member/hello2")
public MemberDto hello2() {
return MemberDto.builder().num(1).name("유재석").addr("압구정").build();
}
@GetMapping("/member/hello3")
public List<String> hello3(){
return List.of("유재석", "박명수", "정준하");
}
}
/api 로 시작하는 요청을 http://localhost:8888 로 프록시 한다(path) => path.replace(/^\/api/, '')/api 를 백엔드에서 필요하면 삭제하지 말고 그대로 사용한다예제
fetch('/api/users')
.then((res) => res.json())
.then((data) => console.log(data));
➜ 실제로는 http://localhost:8888/users 로 요청이 간다

proxy 코드 추가server: {
proxy: {
'/api': { // 프록시할 경로
target: 'http://localhost:9000', // 백엔드 서버 주소
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, ''), // '/api' 제거
},
},
},
button 에 onClick 코드 추가<button className="btn btn-primary" onClick={()=>{
fetch("/api/v1/member/hello")
.then(res=>res.text())
.then(data=>{
console.log(data);
});
}}>버튼</button>
hello2 & hello3 요청 코드 추가<button className="btn btn-primary" onClick={()=>{
fetch("/api/v1/member/hello2")
.then(res=>res.json())
.then(data=>{
console.log(data);
});
}}>버튼</button>
{num: 1, name: '유재석', addr: '압구정'}
<button className="btn btn-primary" onClick={()=>{
fetch("/api/v1/member/hello3")
.then(res=>res.json())
.then(data=>{
console.log(data);
});
}}>버튼</button>
(3) ['유재석', '박명수', '정준하']
MemberService 의존 객체 주입 &list 메소드 생성private final MemberService memberService;
@GetMapping("/members")
public List<MemberDto> list(){
List<MemberDto> list = memberService.getAll();
return list;
}
[{"num":3,"name":"원숭이","addr":"동물원"},{"num":2,"name":"해골","addr":"행신동"},{"num":1,"name":"김구라","addr":"노량진"}]
import { useState } from "react"
export default function App2(){
const sample1 = <tr>
<td>1</td>
<td>유재석</td>
<td>압구정</td>
</tr>;
const sample2 = <tr>
<td>2</td>
<td>박명수</td>
<td>이태원</td>
</tr>;
const sample3 = <tr>
<td>3</td>
<td>정준하</td>
<td>서래마을</td>
</tr>;
// 아래의 상태값을 member 는 sampleArray 와 같은 구조의 배열이다
const sampleArray = [sample1, sample2, sample3];
// tr jsx 객체가 여러 개 들어갈 배열을 상태값으로 관리
const [members, setMembers] = useState([]);
const handleClick = () => {
fetch("/api/v1/members")
.then(res=>res.json())
.then(data => {
// data 는 회원 목록이 들어 있는 이런 모양의 배열 [{},{},{}]
// tr 에 회원 정보 각각의 회원 정보가 들어 있는 새로운 배열을 만들어내서
const newArray = data.map(item=><tr>
<td>{item.num}</td>
<td>{item.name}</td>
<td>{item.addr}</td>
</tr>);
// 상태값을 변경한다
setMembers(newArray);
});
};
return(
<div className="container">
<button onClick={handleClick}>목록 받아오기</button>
<h3>회원 목록</h3>
<table>
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>주소</th>
</tr>
</thead>
<tbody>
{members}
</tbody>
</table>
</div>
)
}
export default function App3(){
// jsx 객체가 들어 있는 배열
const foods = [
<li>김밥</li>,
<li>라면</li>,
<li>떡볶이</li>
];
return(
<div className="container">
<h1>친구 목록</h1>
<ul>
{foods}
</ul>
</div>
)
}
친구 목록
김밥
라면
떡볶이
배열 랜더링
<li>{item}</li> :: 람다식// 배열에 들어 있는 string 을 이용해서
const data = ["java", "jsp", "spring"]
// li 요소 여러 개가 들어 있는 jsx 의 배열을 만들어서 아래에서 렌더링하기
const programming = data.map(item=><li>{item}</li>);
return(
<h1>교육 과정</h1>
<ul>
{programming}
</ul>
)
교육 과정
java
jsp
spring
data.map(item=><li>{item}</li>) 풀어서 나열한다면 해당 코드const programming1 = data.map((item)=>{
return <li>{item}</li>
});
App2.jsx 를 지우고 App3.jsx 를 App2.jsx 로 Rname 한 후 App3.jsx 생성
rsf + 엔터 하면 기본 뼈대가 자동으로 생성import React, { useState } from 'react';
function App3() {
// 회원 목록을 상태값으로 관리하기
const [members, setMembers] = useState([]);
return (
<div className='container'>
<button onClick={()=>{
// 서버로부터 받아온 배열이라고 가정하자
const newArray = [
{num:1, name:"유재석", addr:"압구정"},
{num:2, name:"박명수", addr:"이태원"},
{num:3, name:"정준하", addr:"서래마을"}
]
// 새로운 배열로 상태값을 변경한다
setMembers(newArray);
}}>받아오기</button>
<h1>회원 목록</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>주소</th>
</tr>
</thead>
<tbody>
{members.map(item=><tr>
<td>{item.num}</td>
<td>{item.name}</td>
<td>{item.addr}</td>
</tr>)}
</tbody>
</table>
</div>
);
}
export default App3;