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차 과제 제출 #4

Closed
wants to merge 12 commits into from
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/10th-BE-Networking-2.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules/backend.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
application.*

### STS ###
.apt_generated
Expand Down Expand Up @@ -34,4 +35,4 @@ out/
/.nb-gradle/

### VS Code ###
.vscode/
.vscode/
6 changes: 5 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation 'org.springframework.batch:spring-batch-test'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Apache POI for reading Excel files
implementation 'org.apache.poi:poi:5.2.3'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cotato.backend.common.exception;

import java.util.NoSuchElementException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

Expand All @@ -19,6 +22,16 @@ public ResponseEntity<Object> handleApiException(ApiException e) {
return makeErrorResponseEntity(e.getHttpStatus(), e.getMessage(), e.getCode());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
return makeErrorResponseEntity(HttpStatus.BAD_REQUEST, e.getBindingResult().getAllErrors().get(0).getDefaultMessage(), "POST-001");
Copy link
Member

Choose a reason for hiding this comment

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

ErrorCode를 사용하지 않고, 직접 POST-001 처럼 code를 명시한 전역 예외 핸들러를 구현하신 이유가 궁금합니다!

}

@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<Object> handleNoSuchElementException(NoSuchElementException e){
Copy link
Member

Choose a reason for hiding this comment

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

이런 예외가 존재하는지 몰랐는데 배워갑니다 👍

return makeErrorResponseEntity(HttpStatus.BAD_REQUEST, e.getMessage(), "POST-002");
}

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

import java.util.Map;

import cotato.backend.domains.post.dto.request.SavePostRequest;
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
Copy link
Member

Choose a reason for hiding this comment

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

무분별한 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;

@Column(nullable = false)
private int views;
kych4n marked this conversation as resolved.
Show resolved Hide resolved

public Post(String title, String content, String name) {
this.title = title;
this.content = content;
this.name = name;
this.views = 0;
}

public static Post createdFrom(SavePostRequest request) {
return new Post(request.getTitle(), request.getContent(), request.getName());
}

public static Post createdFrom(Map<String, String> row) {
return new Post(row.get("title"), row.get("content"), row.get("name"));
}

public void increaseViews() {
this.views += 1;
}

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

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Repository
public class PostBulkRepository {

private static final int BATCH_SIZE = 500;

private final JdbcTemplate jdbcTemplate;

public void saveAllByExcel(List<Post> posts) {
Copy link
Member

Choose a reason for hiding this comment

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

데이터베이스 저장 작업을 하기 때문에 Transactional 어노테이션을 고려하는 것도 좋을 거 같습니다!

jdbcTemplate.batchUpdate(
"INSERT INTO post (title, content, name, views) VALUES (?, ?, ?, ?)",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, posts.get(i).getTitle());
ps.setString(2, posts.get(i).getContent());
ps.setString(3, posts.get(i).getName());
ps.setLong(4, posts.get(i).getViews());
}

@Override
public int getBatchSize() {
return BATCH_SIZE;
}
}
kych4n marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package cotato.backend.domains.post;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
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.RestController;

import cotato.backend.common.dto.DataResponse;
import cotato.backend.domains.post.dto.request.SavePostRequest;
import cotato.backend.domains.post.dto.request.SavePostsByExcelRequest;
import cotato.backend.domains.post.dto.response.FindPostResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
Expand All @@ -17,10 +26,34 @@ public class PostController {

private final PostService postService;

@PostMapping
public ResponseEntity<DataResponse<Void>> savePost(@RequestBody @Valid SavePostRequest request){
postService.savePost(request);
return ResponseEntity.ok(DataResponse.ok());
}

@PostMapping("/excel")
public ResponseEntity<DataResponse<Void>> savePostsByExcel(@RequestBody SavePostsByExcelRequest request) {
postService.saveEstatesByExcel(request.getPath());

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

@GetMapping("/{id}")
public ResponseEntity<FindPostResponse> findPost(@PathVariable Long id) {
return ResponseEntity.ok(postService.findPostById(id));
}

@GetMapping("/list")
public ResponseEntity<?> findPosts(@PageableDefault(size = 10, sort = "views", direction = Sort.Direction.DESC)
Copy link
Member

Choose a reason for hiding this comment

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

@PageableDefault 어노테이션으로도 처리가 가능하네요 !! 배워갑니당

Pageable pageable) {
return ResponseEntity.ok(DataResponse.from(postService.findPostsWithPaging(pageable)));
}

@DeleteMapping("/{id}")
public ResponseEntity<DataResponse<String>> deletePost(@PathVariable Long id){
postService.deletePostById(id);

return ResponseEntity.ok(DataResponse.from("게시글이 성공적으로 삭제되었습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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> {

@Override
Page<Post> findAll(Pageable pageable);

Copy link
Member

Choose a reason for hiding this comment

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

이 코드에서 @Repository@Override를 명시하신 이유가 궁금합니다!

}
Loading