

{
"name": "조회",
"request": {
"method": "GET",
"header": []
},
"response": []
},
{
"name": "생성",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"name\" : \"test\",\n \"title\" : \"test\",\n \"contents\" : \"test\",\n \"pwd\" : \"pwd\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "localhost:8080/board/create"
},
"response": []
},
{
"name": "상세조회",
"request": {
"method": "GET",
"header": [],
"url": "localhost:8080/board/detail/2"
},
"response": []
},
{
"name": "수정",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"name\" : \"test\",\n \"title\" : \"test\",\n \"contents\" : \"test\",\n \"pwd\" : \"1234\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "localhost:8080/board/update/2"
},
"response": []
},
{
"name": "삭제",
"request": {
"method": "DELETE",
"header": [
{
"key": "X-Password",
"value": "1234",
"type": "text"
}
],
"url": "localhost:8080/board/delete/2"
},
"response": []
}

- 게시글 작성 기능 (
제목,작성자명,비밀번호,작성 내용,작성일)- 선택한 게시글 조회 기능
- 게시글 목록 조회 기능 (
작성일기준 내림차순)- 선택한 게시글 수정 기능 (
제목,작성자명,작성 내용, +비밀번호일치)- 선택한 게시글 삭제 기능 (
비밀번호일치)
- 프로젝트 만들기
- Dependencies 추가
- build.gradle에서 확인
- build 확인
- 패키지 및 클래스 생성하기
- application.properties 세팅
- DB 만들기
- DB 연결
- Entity!
- Repository
- Service
- Controller
- RestController
- Repository
: Spring Data JPA로 구현
→ Repository는 DB의 SQL문장을 작성, JpaRepository를 상속하여 기본적인 Query문장 사용 가능
→ JpaRepository 인터페이스의 구현체인 SimpleJpaRepository→ 필요한 쿼리는 만들어서 사용 (QueryMethod)
- Service
: 비즈니스 로직 수행 →Repository에서 DB를 통해 얻어온 정보를 자바 문법을 활용하여 가공 후,Controller로 보내는 역할Why? 원본 데이터의 손상을 방지하기 위해서, DTO의 사용 목적과 동일
DTO: 데이터 손상을 막기 위한 DB 정보의 복사본
- 전체 데이터 출력
Repository에서List<T>(제네릭 Board)형태로 받은 값을 stream()으로 펼친 뒤, BoardResponseDto에 담아서 다시 List화BoardResponseDto::new→BoardResponseDto의 생성자가Board를 매개변수로 받기 때문에 가능- 선택 데이터 출력
- Id를 통해 값을 찾는 기능을 자주 사용하기에, 메서드화
- return값이
Optional<T>이므로,Optional<T>로 받거나, 예외 던지기- 예외 처리는 어디서...?
- 게시글 생성
Spring이 Front단에서JSON형태로 넘어온 값을RequestDto에 담아서 전달- 해당 값을
Board객체에 담은 뒤,Repository를 통해 DB에 저장saveBoard에는 Board객체에는 없는 save될 때, 추가된 생성일과 수정일을 포함 → 포함된 객체를ResponseDto로 전달- 게시글 수정
@Transactional: 메소드 레벨에 적용하면, 해당 메소드 내에서 실행되는 모든 DB관련 작업이 하나의 트랜잭션으로 연결
- 데이터 일관성을 보장하기 위해 사용
findBoard()에서 사용하는Repository의findById()메서드는 ReadOnly속성만 있기 때문에, 수정이 이뤄지기 위해서 영속성 컨텍스트의 스냅샷과 비교하는 과정 필요 →Transaction- Update의 경우, 영속성 컨텍스트에서
dirty Checking, 즉Loaded state의 값을 비교하는 과정을 진행
entity의 값이 변경되면 자동으로 반영하여 적용entity의 값을 변경하는 메서드 작성RequestDto에서 전달된pwd의 값과 DB에 저장된pwd의 값을 비교하여, 맞을 경우 수행하는 로직
- 만약 일치하지 않는다면, 예외를 만들어서
Controller로 던지기Controller는 해당 예외를 처리해야 한다.서버에서 예외 다루기 ExceptionHandler
- 게시글 삭제
- 삭제도 비밀번호 확인 로직 필요 →
Controller에서 처리
- Controller & RestController
:Web Server와Client간에 이루어지는Request와Response를 연결하는 최초의 창구
→Web Server에 설계한 API를 사용하기 위한 URL이Mapping되어 모여진 곳
- Controller
: 전통적인 웹 애플리케이션,Client가 요청한 페이지로 이동
→ 주로HTML페이지의 생성 및 반환을 담당
→Client(Browser)의Request를 받아 수행한 뒤,HTML View를 렌더링하여Client(Browser)로 전송
→Model을 매개변수로 사용하여, 데이터와 함께View를 보내는 역할
- 전체 리스트 출력
- 게시글 전체를
Model에 담아서 board 디렉토리의list.html을 렌더링- 선택 게시글 상세보기
Front에서 url에 담아 보내온id값을 받기 위해@PathVariable사용- 게시글 작성 페이지로 이동
- 게시글 작성 페이지에서 등록시 이동
- HTTP Method가 다를 경우, 엔드포인트가 동일해도 상관없음
- 게시글 수정 페이지로 이동
- RestController
:RESTful한 웹 서비스를 개발할 때 사용 RESTful이란?
→ JSON 혹은 XML형식의 API 데이터를 반환하는데 사용
→@Controller에서@ResponseBody어노테이션을 사용하여 직렬화된 데이터를 반환할 수 있음
- 게시글 수정 버튼 클릭
Service에서 던져진 예외를 처리하는 부분ResponseEntity<>를 통해 데이터 수정, DB에 저장한 후 서버의 상태를 return- 자세한 내용: 서버에서 예외 다루기 ExceptionHandler
- 게시글 삭제 버튼 클릭
- 비밀번호 로직을 처리한 뒤, 서버의 상태 return
@RequestHeader는 입력된 비밀번호가 url에 노출되지 않도록 프론트에서 Header에 담아서 보냈기 때문에 사용
관계형 데이터베이스에서 테이블마다 공통적으로 들어가는 사항(생성일, 수정일, 식별코드 등등)의 중복을 줄이기 위해, JPA에서 시간에 대한 컬럼을 자동으로 Mapping 해주는 기능
audit = 감시하다
@MappedSuperclass // 해당 클래스를 상속할 경우, 클래스의 필드 사용 가능
@EntityListeners(AuditingEntityListener.class) // auditing 기능임을 알리기
@CreatedDate // 생성 시간 저장
@LastModifiedDate // 수정 시간 저장
Auditing 사용시, @EnableJpaAuditing 어노테이션 필요!
// Timestamped class
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped { // 생성되지 않도록 추상클래스화
@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime modifiedAt;
}
// create 코드
<script>
function submitForm() {
let name = $('#name').val();
let title = $('#title').val();
let contents = $('#contents').val();
let pwd = $('#pwd').val();
let data = {
'name': name,
'contents': contents,
'title': title,
'pwd': pwd
};
$.ajax({
type: "POST",
url: "/board/create",
contentType: "application/json",
data: JSON.stringify(data),
success: function (response) {
alert('글이 성공적으로 작성되었습니다.');
window.location.href = "/board";
}
});
}
</script>
// update 코드
<script>
function submitForm() {
let name = $('#name').val();
let title = $('#title').val();
let contents = $('#contents').val();
let pwd = $('#pwd').val();
let id = $('#id').val();
let data = {
'name': name,
'contents': contents,
'title': title,
'pwd': pwd
};
$.ajax({
type: "PUT",
url: "/board/update/"+id,
contentType: "application/json",
data: JSON.stringify(data),
success: function (response) {
alert('글이 성공적으로 수정되었습니다.');
window.location.href = "/board/list"; // 성공 시 상세 페이지로 이동
},
error: function (error) {
alert('글 수정에 실패했습니다. 비밀번호를 확인해주세요.');
}
});
}
</script>
// Delete -> Header에 pwd 담기
<script>
function deleteBoard(id) {
let pwd = $('#pwd').val();
$.ajax({
type: "DELETE",
url: "/board/delete/" + id,
headers: {
'X-Password': pwd
},
success: function (response) {
alert('글이 성공적으로 삭제되었습니다.');
window.location.href = "/board/list";
},
error: function (error) {
alert('비밀번호가 다릅니다.');
}
});
}
</script>