dependencies 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
WebConfig
package com.kcs.community.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/post/**")
.addResourceLocations("file:src/main/resources/static/images/post/");
registry.addResourceHandler("/images/profile/**")
.addResourceLocations("file:src/main/resources/static/images/profile/");
}
};
}
}
QueryDSLConfig
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class QueryDSLConfig {
private final EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
앞 게시물에서 작성한 BaseEntity, UserEntity는 그대로 사용할 예정이다.
JPA, Querydsl은 Post와 Commnet에서만 적용할 예정이니 참고바랍니다.
Post: 게시글
@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name="posts")
public class Post extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="user_id")
private User user;
private String postImage;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private String type;
private Long view;
private Long commentCount;
}
Comment: 댓글
@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@Table(name="comments")
@NoArgsConstructor
@AllArgsConstructor
public class Comment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String comment;
@ManyToOne
@JoinColumn(name ="post_id")
private Post post;
@ManyToOne
@JoinColumn(name="user_id")
private User user;
}
PostController
공통 url: /api/posts
@Log4j2
@RequestMapping("/api/posts")
@RestController
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
private final UserService userService;
private final JWTUtil jwtUtil;
@GetMapping("")
public ResponseEntity<Map<String, Object>> getPosts() {
try {
List<PostDto> posts = postService.findAll();
return ResponseUtil.createResponse(200, posts, null);
} catch (Exception e) {
log.error("Error getting posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/other")
public ResponseEntity<Map<String, Object>> getOtherPosts() {
try {
List<PostDto> posts = postService.findOtherAll();
return ResponseUtil.createResponse(200, posts, null);
} catch (Exception e) {
log.error("Error getting other posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/coding")
public ResponseEntity<Map<String, Object>> getCodingPosts() {
try {
List<PostDto> posts = postService.findCodingAll();
return ResponseUtil.createResponse(200, posts, null);
} catch (Exception e) {
log.error("Error getting coding posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/myPosts")
public ResponseEntity<Map<String, Object>> getMyPosts(@RequestHeader("access") String token) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
List<PostDto> posts = postService.findMyPosts(userIdFromToken);
return ResponseUtil.createResponse(200, posts, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token provided", e);
return ResponseUtil.createResponse(400, null, "invalid_token");
} catch (Exception e) {
log.error("Error getting my posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/top10")
public ResponseEntity<Map<String, Object>> getTopPosts() {
try {
List<PostDto> posts = postService.findTop();
return ResponseUtil.createResponse(200, posts, null);
} catch (Exception e) {
log.error("Error getting top posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getPost(@PathVariable Long id) {
try {
PostDto post = postService.findByIdUpdate(id);
if (post == null) {
return ResponseUtil.createResponse(404, null, "post_not_found");
}
return ResponseUtil.createResponse(200, post, null);
} catch (Exception e) {
log.error("Error getting post", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getPostsInfo() {
try {
Long postCount = postService.findPostsInfo();
return ResponseUtil.createResponse(200, postCount, null);
} catch (Exception e) {
log.error("Error getting top posts", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/{id}/update")
public ResponseEntity<Map<String, Object>> getUpdatePost(@PathVariable Long id) {
try {
PostDto post = postService.findById(id);
if (post == null) {
return ResponseUtil.createResponse(404, null, "post_not_found");
}
return ResponseUtil.createResponse(200, post, null);
} catch (Exception e) {
log.error("Error getting post for update", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PostMapping("")
public ResponseEntity<Map<String, Object>> createPost(@RequestHeader("access") String token, @RequestBody PostDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long created = postService.create(userIdFromToken, dto);
if (created == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
return ResponseUtil.createResponse(201, created, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error creating post", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PostMapping("/checkOwner")
public ResponseEntity<Map<String, Object>> checkPostOwner(@RequestHeader("access") String token, @RequestBody PostDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long check = postService.checkUser(userIdFromToken, dto);
if (check == null) {
return ResponseUtil.createResponse(403, null, "unauthorized_user");
}
return ResponseUtil.createResponse(200, null, "authorized_user");
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error checking post owner", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PatchMapping("/{id}")
public ResponseEntity<Map<String, Object>> updatePost(@RequestHeader("access") String token, @PathVariable Long id, @RequestBody PostDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long updated = postService.update(userIdFromToken, id, dto);
if (updated == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
if (updated == -1L) {
return ResponseUtil.createResponse(403, null, "unauthorized_user");
}
return ResponseUtil.createResponse(200, updated, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error updating post", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deletePost(@RequestHeader("access") String token, @PathVariable Long id) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long deleted = postService.erase(userIdFromToken, id);
if (deleted == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
if (deleted == -1L) {
return ResponseUtil.createResponse(403, null, "unauthorized_user");
}
return ResponseUtil.createResponse(200, deleted, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error deleting post", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
}
CommentController
공통 url: /api/posts/{pid}/comments
@Log4j2
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/posts/{pid}/comments")
public class CommentController {
private final CommentService commentService;
private final PostService postService;
private final JWTUtil jwtUtil;
private final UserService userService;
@GetMapping("")
public ResponseEntity<Map<String, Object>> getComments(@PathVariable(name = "pid") Long pid) {
try {
PostDto post = postService.findById(pid);
if (post == null) {
return ResponseUtil.createResponse(404, null, "posts_not_found");
}
List<CommentDto> comments = commentService.findAll(pid);
if (comments == null) {
return ResponseUtil.createResponse(404, null, "comments_not_found");
}
return ResponseUtil.createResponse(200, comments, null);
} catch (Exception e) {
log.error("Error getting comments", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@GetMapping("/{commentId}")
public ResponseEntity<Map<String, Object>> getComment(@PathVariable(name = "pid") Long pid, @PathVariable(name = "commentId") Long cid) {
try {
PostDto post = postService.findById(pid);
if (post == null) {
return ResponseUtil.createResponse(404, null, "posts_not_found");
}
CommentDto comment = commentService.findById(pid, cid);
if (comment == null) {
return ResponseUtil.createResponse(404, null, "comments_not_found");
}
return ResponseUtil.createResponse(200, comment, null);
} catch (Exception e) {
log.error("Error getting comment", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PostMapping("")
public ResponseEntity<Map<String, Object>> createComment(@RequestHeader("access") String token, @PathVariable(name = "pid") Long pid, @RequestBody CommentDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
PostDto post = postService.findById(pid);
if (post == null) {
return ResponseUtil.createResponse(404, null, "posts_not_found");
}
Long created = commentService.create(userIdFromToken, pid, dto);
if (created == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
return ResponseUtil.createResponse(201, created, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error creating comment", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PostMapping("/checkOwner")
public ResponseEntity<Map<String, Object>> checkCommentOwner(@RequestHeader("access") String token, @RequestBody CommentDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long check = commentService.checkUser(userIdFromToken, dto);
if (check == null) {
return ResponseUtil.createResponse(403, null, "unauthorized_user");
}
return ResponseUtil.createResponse(200, null, "authorized_user");
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error checking comment owner", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@PatchMapping("/{commentId}")
public ResponseEntity<Map<String, Object>> updateComment(@RequestHeader("access") String token, @PathVariable(name = "pid") Long pid, @PathVariable(name = "commentId") Long cid, @RequestBody CommentDto dto) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
CommentDto comment = commentService.findById(pid, cid);
if (comment == null) {
return ResponseUtil.createResponse(404, null, "comments_not_found");
}
Long updated = commentService.update(userIdFromToken, cid, dto);
if (updated == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
return ResponseUtil.createResponse(200, updated, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error updating comment", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
@DeleteMapping("/{commentId}")
public ResponseEntity<Map<String, Object>> deleteComment(@RequestHeader("access") String token, @PathVariable(name = "pid") Long pid, @PathVariable(name = "commentId") Long cid) {
try {
Long userIdFromToken = userService.findEmail(jwtUtil.getEmail(token)).getId();
Long deleted = commentService.erase(userIdFromToken, pid, cid);
if (deleted == null) {
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
return ResponseUtil.createResponse(200, deleted, null);
} catch (IllegalArgumentException e) {
log.error("Invalid token or input data", e);
return ResponseUtil.createResponse(400, null, "invalid_input");
} catch (Exception e) {
log.error("Error deleting comment", e);
return ResponseUtil.createResponse(500, null, "internal_server_error");
}
}
}
PostServiceImpl
@Service
@Log4j2
@RequiredArgsConstructor
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
private final UserRepository userRepository;
@Override
public List<PostDto> findAll() {
List<Post> results = postRepository.findAllPosts();
if (results == null) {
return null;
}
return results.stream().map(this::mapPostToPostDto).collect(Collectors.toList());
}
@Override
public PostDto findById(Long id) {
return postRepository.findById(id).map(this::mapPostToPostDto).orElse(null);
}
@Override
public List<PostDto> findOtherAll() {
List<Post> results = postRepository.findOtherPosts();
if (results == null) {
return null;
}
return results.stream().map(this::mapPostToPostDto).collect(Collectors.toList());
}
@Override
public List<PostDto> findCodingAll() {
List<Post> results = postRepository.findCodingPosts();
if (results == null) {
return null;
}
return results.stream().map(this::mapPostToPostDto).collect(Collectors.toList());
}
@Override
public List<PostDto> findMyPosts(Long uid) {
List<Post> results = postRepository.findPostsByUserId(uid);
if (results == null) {
return null;
}
return results.stream().map(this::mapPostToPostDto).collect(Collectors.toList());
}
@Override
public List<PostDto> findTop() {
List<Post> results = postRepository.findTopPosts();
if (results == null) {
return null;
}
return results.stream().map(this::mapPostToPostDto).collect(Collectors.toList());
}
@Override
@Transactional
public PostDto findByIdUpdate(Long id) {
postRepository.incrementViewCount(id);
return postRepository.findById(id).map(this::mapPostToPostDto).orElse(null);
}
@Override
public Long findPostsInfo() {
return postRepository.countBy();
}
@Override
@Transactional
public Long create(Long uid, PostDto dto) {
User user = userRepository.show(uid);
String postServerUrl = savePostImage(dto.getPost_image());
Post post = Post.builder()
.user(user)
.title(dto.getTitle())
.postImage(postServerUrl)
.content(dto.getContent())
.type(dto.getType())
.view(0L)
.commentCount(0L)
.build();
return postRepository.save(post).getId();
}
@Override
@Transactional
public Long update(Long uid, Long pid, PostDto dto) {
User user = userRepository.show(uid);
if (!postRepository.existsByIdAndUser(pid, user)) {
return -1L;
}
Post post = postRepository.findById(pid).orElse(null);
if (post == null) {
return null;
}
updatePostDetails(post, dto);
postRepository.save(post);
return post.getId();
}
@Override
@Transactional
public Long erase(Long uid, Long pid) {
User user = userRepository.show(uid);
if (!postRepository.existsByIdAndUser(pid, user)) {
return -1L;
}
postRepository.deleteById(pid);
return pid;
}
@Override
@Transactional(readOnly = true)
public Long checkUser(Long uid, PostDto dto) {
User user = userRepository.show(uid);
return postRepository.existsByIdAndUser(dto.getPost_id(), user) ? 1L : null;
}
// Utility methods
private String savePostImage(String base64Image) {
if (base64Image == null || base64Image.isEmpty() || base64Image.contains("localhost")) {
return base64Image;
}
String[] parts = base64Image.split(",");
if (parts.length != 2) {
return null;
}
String imageString = parts[1];
byte[] imageBytes = Base64.getDecoder().decode(imageString);
String imageName = "post_image_" + System.currentTimeMillis() + ".png";
Path imagePath = Paths.get("src/main/resources/static/images/post", imageName);
try {
Files.createDirectories(imagePath.getParent());
Files.write(imagePath, imageBytes);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return "http://localhost:8080/images/post/" + imageName;
}
private PostDto mapPostToPostDto(Post post) {
return new PostDto(
post.getId(),
post.getUser().getId(),
post.getTitle(),
post.getPostImage(),
post.getContent(),
post.getType(),
post.getView(),
post.getCreated_at(),
post.getCommentCount(),
post.getUser().getNickname(),
post.getUser().getProfileImage()
);
}
private void updatePostDetails(Post post, PostDto dto) {
post.setTitle(dto.getTitle());
post.setPostImage(savePostImage(dto.getPost_image()));
post.setContent(dto.getContent());
post.setType(dto.getType());
}
}
CommentServiceImpl
@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {
private final CommentRepository commentRepository;
private final PostRepository postRepository;
private final UserRepository userRepository;
@Override
public List<CommentDto> findAll(Long pid) {
List<Comment> results = commentRepository.findAllByPostId(pid);
if (results == null) {
return null;
}
return results.stream().map(result -> new CommentDto(
result.getId(),
result.getComment(),
result.getUser().getId(),
result.getPost().getId(),
result.getCreated_at(),
result.getUser().getNickname(),
result.getUser().getProfileImage()
)).collect(Collectors.toList());
}
@Override
public CommentDto findById(Long pid, Long cid) {
Comment result = commentRepository.findByCommentIdAndPostId(cid, pid);
if (result == null) {
return null;
}
return new CommentDto(
result.getId(),
result.getComment(),
result.getUser().getId(),
result.getPost().getId(),
result.getCreated_at(),
result.getUser().getNickname(),
result.getUser().getProfileImage()
);
}
@Override
@Transactional
public Long create(Long uid, Long pid, CommentDto dto) {
User user = userRepository.show(uid);
Post post = postRepository.findById(pid).orElse(null);
Comment comment = Comment.builder()
.user(user)
.post(post)
.comment(dto.getComment())
.build();
Comment savedComment = commentRepository.save(comment);
postRepository.incrementCommentCount(pid);
return savedComment.getId();
}
@Override
@Transactional
public Long update(Long uid, Long cid, CommentDto dto) {
User user = userRepository.show(uid);
if (!commentRepository.existsByIdAndUser(cid, user)) {
return -1L;
}
commentRepository.updateComment(cid, dto.getComment());
return cid;
}
@Override
@Transactional
public Long erase(Long uid, Long pid, Long cid) {
User user = userRepository.show(uid);
if (!commentRepository.existsByIdAndUser(cid, user)) {
return -1L;
}
commentRepository.deleteById(cid);
postRepository.decrementCommentCount(pid);
return cid;
}
@Override
@Transactional(readOnly = true)
public Long checkUser(Long uid, CommentDto dto) {
User user = userRepository.show(uid);
return commentRepository.existsByIdAndUser(dto.getComment_id(), user) ? 1L : null;
}
}
PostRepository
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements CustomPostRepository {
private final JPAQueryFactory queryFactory;
//전체 게시물 조회
public List<Post> findAllPosts() {
return queryFactory.selectFrom(post)
.orderBy(post.created_at.desc())
.fetch();
}
//고민 게시물 조회
public List<Post> findOtherPosts() {
return queryFactory.selectFrom(post)
.where(post.type.eq("other"))
.orderBy(post.created_at.desc())
.fetch();
}
//코딩 게시물 조회
public List<Post> findCodingPosts() {
return queryFactory.selectFrom(post)
.where(post.type.eq("coding"))
.orderBy(post.created_at.desc())
.fetch();
}
//본인 게시물 조회
public List<Post> findPostsByUserId(Long userId) {
return queryFactory.selectFrom(post)
.where(user.id.eq(userId))
.orderBy(post.created_at.desc())
.fetch();
}
//조회수 top 10 게시물 조회
public List<Post> findTopPosts() {
return queryFactory.selectFrom(post)
.orderBy(post.view.desc())
.limit(10)
.fetch();
}
public Long incrementViewCount(Long postId) {
return queryFactory.update(post)
.set(post.view, post.view.add(1))
.where(post.id.eq(postId))
.execute();
}
public Long incrementCommentCount(Long postId) {
return queryFactory.update(post)
.set(post.commentCount, post.commentCount.add(1))
.where(post.id.eq(postId))
.execute();
}
public Long decrementCommentCount(Long postId) {
return queryFactory.update(post)
.set(post.commentCount, post.commentCount.subtract(1))
.where(post.id.eq(postId))
.execute();
}
}
CommentRepository
@Repository
@RequiredArgsConstructor
public class CommentRepositoryImpl implements CustomCommentRepository {
private final JPAQueryFactory queryFactory;
@Override
public List<Comment> findAllByPostId(Long postId) {
return queryFactory.selectFrom(comment1)
.where(post.id.eq(postId))
.fetch();
}
@Override
public Comment findByCommentIdAndPostId(Long commentId, Long postId) {
return queryFactory.selectFrom(comment1)
.where(comment1.id.eq(commentId).and(post.id.eq(postId)))
.fetchOne();
}
@Override
@Transactional
public void updateComment(Long commentId, String commentText) {
queryFactory.update(comment1)
.set(comment1.comment, commentText)
.where(comment1.id.eq(commentId))
.execute();
}
}