Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[전규진] 3차 과제 제출 #9

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public static <T> DataResponse<T> from(T data) {
public static <T> DataResponse<Void> ok() {
return new DataResponse<>(HttpStatus.OK, null);
}

public static <T> DataResponse<String> success(String message) {
return new DataResponse<>(HttpStatus.OK, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
public class GlobalExceptionHandler {

@ExceptionHandler(ApiException.class)
public ResponseEntity<Object> handleApiException(ApiException e) {
public ResponseEntity<String> handleApiException(ApiException e) {
log.warn("handleApiException", e);

return makeErrorResponseEntity(e.getHttpStatus(), e.getMessage(), e.getCode());
return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
}

private ResponseEntity<Object> makeErrorResponseEntity(HttpStatus httpStatus, String message, String code) {
Expand Down
30 changes: 30 additions & 0 deletions backend/src/main/java/cotato/backend/domains/post/Post.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
package cotato.backend.domains.post;

import org.hibernate.annotations.ColumnDefault;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.stereotype.Service;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String content;

@Column(nullable = false)
private String name;

@ColumnDefault("0")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이렇게 디폴트값을 지정할 수 있군요 배워갑니다!

private int views;

public Post(String title, String content, String name) {
this.title = title;
this.content = content;
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package cotato.backend.domains.post;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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 cotato.backend.common.dto.DataResponse;
Expand All @@ -23,4 +33,47 @@ public ResponseEntity<DataResponse<Void>> savePostsByExcel(@RequestBody SavePost

return ResponseEntity.ok(DataResponse.ok());
}

@PostMapping("/single")
public ResponseEntity<DataResponse<Void>> savePostBySingle(@RequestBody PostDTO postDTO){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation도 추가해보시면 좋을 거 같아요!

postService.savePostBySingle(postDTO);

return ResponseEntity.ok(DataResponse.ok());
}

@GetMapping("/read/{id}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RESTful API 맞지 않는 url 같습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 동사를 사용하지 않는다
  2. posts/{id}/어찌고저찌고 순서가 더 맞지 않을까 싶습니다.

public ResponseEntity<Post> readPostBySingle(@PathVariable Long id){
Post post = postService.readPostBySingle(id);

return ResponseEntity.ok(post);
}

@GetMapping("/readByViews")
public ResponseEntity<Map<String, Object>> readByView(@RequestParam(defaultValue = "0") int page){
int size = 10;
Page<Post> postPage = postService.getPosts(page, size);
List<Map<String, Object>> filteredPosts = postPage.getContent().stream().map(post -> {
Map<String, Object> postMap = new HashMap<>();
postMap.put("id", post.getId());
postMap.put("title", post.getTitle());
postMap.put("name", post.getName());
return postMap;
}).collect(Collectors.toList());

Map<String, Object> response = new HashMap<>();
response.put("posts", filteredPosts); // 게시글 리스트
response.put("currentPage", postPage.getNumber()); // 현재 페이지
response.put("totalPages", postPage.getTotalPages());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변환을 controller에서 하셨군요!
혹시 servcie에서 하지 않고 controller에서 하신 이유가 있으실까요?

return ResponseEntity.ok(response);
}

@DeleteMapping("/delete/{id}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/delete는 따로 필요없을거같아요!
method: delete
/api/posts/{id}
위 정보만으로도 충분해보입니다!

public ResponseEntity<DataResponse<String>> deletePostBySingle(@PathVariable Long id) {
postService.deletePostBySingle(id);

return ResponseEntity.ok(DataResponse.success("게시글이 성공적으로 삭제 되었습니다."));
}


}
24 changes: 24 additions & 0 deletions backend/src/main/java/cotato/backend/domains/post/PostDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cotato.backend.domains.post;

public class PostDTO {

private String title;

private String content;

private String name;

public String getTitle() {
return title;
}

public String getContent() {
return content;
}

public String getName() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getter를 직접 만드셨군요!
class 위에 @Getter를 붙이면 모든 Getter 메서드가 자동으로 만들어진답니다! 놀랍지 않나요

return name;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cotato.backend.domains.post;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findAllByOrderByViewsDesc(Pageable pageable);
}
34 changes: 34 additions & 0 deletions backend/src/main/java/cotato/backend/domains/post/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
import static cotato.backend.common.exception.ErrorCode.*;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -20,6 +25,8 @@
@Transactional
public class PostService {

private final PostRepository postRepository;

// 로컬 파일 경로로부터 엑셀 파일을 읽어 Post 엔터티로 변환하고 저장
public void saveEstatesByExcel(String filePath) {
try {
Expand All @@ -33,10 +40,37 @@ public void saveEstatesByExcel(String filePath) {
return new Post(title, content, name);
})
.collect(Collectors.toList());
postRepository.saveAll(posts);

} catch (Exception e) {
log.error("Failed to save estates by excel", e);
throw ApiException.from(INTERNAL_SERVER_ERROR);
}
}

public void savePostBySingle(PostDTO postDTO) {
Post post = new Post(postDTO.getTitle(), postDTO.getContent(), postDTO.getName());
postRepository.save(post);
}

public Post readPostBySingle(Long id) {
Optional<Post> optionalPost = postRepository.findById(id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 조금 잘 못 된 거 같습니다!
1번, 만약 id에 해당하는 Post가 없으면 Optional에는 null이 들어옵니다.
그럼 get을 할 경우 NullpointException이 나게 됩니다.
그래서 보통
postRepository.findById(id).orElseThrow(() -> Exception 객체))식으로 예외처리를 해줍니다.
아마 예외 처리에 익숙하지 않으신 거 같은데 이쪽으로 공부해보시면 좋을 거 같습니다!

스프링에서의 전역 예외 처리 방법에 대해 정리해서 다음 과제 pr에 첨부해주세요!

Post post = optionalPost.get();
post.setViews(post.getViews() + 1);
Copy link
Member

@supsup-hae supsup-hae Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entity의 직접적인 Setter 사용은 값의 일관성을 해치는 결과가 나올수도 있어서 생성자@Builder 나 메소드를 통해서 별도로 값을 세팅하는 것을 추천드려요!


return post;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Transactional(readOnly=true)@Transactional에는 성능차이가 있다는 것을 아시나요?
유진님 코드에 관련 리뷰있으니까 참고해보시면 좋을 거 같아요!

public Page<Post> getPosts(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return postRepository.findAllByOrderByViewsDesc(pageable);
}

public void deletePostBySingle(Long id) {
Post post = postRepository.findById(id)
.orElseThrow(() -> ApiException.of(HttpStatus.NOT_FOUND,"해당 ID의 게시글을 찾을 수 없습니다", ""));
postRepository.delete(post);

}

}
12 changes: 12 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
spring.application.name=backend

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username= root
spring.datasource.password = 0000
Comment on lines +10 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터베이스 키값이 외부로 노출되고 있어요! 보안상 문제가 발생할 수도 있으니 gitignore을 이용해서 해당 파일을 숨기는 것을 권장해 드려요