Open berryberrybin opened 1 year ago
[x] (1) PostLike 취소, 등록 메소드가 분리되어 API를 각각 호출하는편이 좋은가?
[x] post 목록 조회 화면에서 좋아요 수를 위해 Post에 Like필드 추가를 통해 양방향 관계 설정이 베스트인가?
Post.PostLikes의 size
를 가져오기 위해 양방향 매핑하게 되었음Post : PostLike 필드 삭제
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post extends DateTimeEntity {
@Id
@GeneratedValue(generator = "UUID")
@Column(name = "post_id", columnDefinition = "BINARY(16)")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// 삭제
// @OneToMany(fetch = FetchType.LAZY, mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
// private List<PostLike> likes = new ArrayList<>();
@Builder
public Post(User user, String title, String content) {
this.user = user;
this.title = title;
this.content = content;
this.viewCount = 0;
}
// 삭제
// public Integer getLikeCount() { return likes.size();}
// public void addLike(PostLike postLike) { this.likes.add(postLike); }
// public void cancelLike(PostLike like) { this.likes.remove(like); }
}
- PostLike : 기존과 동일
```java
@Getter
@Entity
@Table(uniqueConstraints = { @UniqueConstraint(name = "POST_LIKE_UNIQUE", columnNames = {"user_id", "post_id"})})
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PostLike {
@Id
@GeneratedValue(generator = "UUID")
@Column(name = "post_like_id", columnDefinition = "BINARY(16)")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
@Builder
public PostLike(Post post, User user) {
this.post = post;
this.user = user;
}
}
PostInfoMapper
PostInfo of(Post post);
➔ PostInfo of(Post post, long likeCount);
로 변경
@Mapper
public interface PostInfoMapper {
PostInfoMapper INSTANCE = Mappers.getMapper(PostInfoMapper.class);
@Mapping(target = "userId", source = "post.user.id") @Mapping(target = "postId", source = "post.id") PostInfo of(Post post, long likeCount); }
PostCommandService
Post
와 likeCount
필요하여 변경postLikeRepository.findPostLikeCount(postId);
통해 가져오거나 게시글 처음 등록할때는 0으로 설정
@Service
@Transactional
@RequiredArgsConstructor
public class PostCommandService {
private final PostRepository postRepository;
private final UserRepository userRepository;
private final PostLikeRepository postLikeRepository;
// postLike 등록, 삭제 관련 메서드를 1개의 메서드로 통일 및 내부 코드 리팩토링 // public void cancelLikePost(PostCommand.CancelLikePostCommand cancelLikePostCommand) {...} // public void likePost(PostCommand.LikePostCommand likePostCommand) { ... }
public void savePostLike(PostCommand.LikePostCommand likePostCommand){ UUID userId = likePostCommand.getUserId(); User user = userRepository.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "user.byId", List.of(userId.toString())));
UUID postId = likePostCommand.getPostId();
Post post = postRepository.findById(postId)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "post.byId", List.of(postId.toString())));
postLikeRepository.findByUserIdAndPostId(userId, postId)
.ifPresentOrElse(
postLike -> postLikeRepository.deleteById(postLike.getId()), // 존재하면 좋아요 취소
() -> postLikeRepository.save(new PostLike(post, user)) // 없으면 좋아요 생성
);
} }
registerPost : 게시글 등록시 likecount는 0부터 시작
public PostInfo registerPost(PostCommand.RegisterPostCommand registerPostCommand) {
UUID userId = registerPostCommand.getUserId();
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "user.byId", List.of(userId.toString())));
Post post = Post.builder()
.user(user)
.title(registerPostCommand.getTitle())
.content(registerPostCommand.getContent())
.build();
// mapper변경으로 파라미터 인자 2개 `Post`와 `likeCount` 필요
//return PostInfoMapper.INSTANCE.of(postRepository.save(post));
return PostInfoMapper.INSTANCE.of(postRepository.save(post) , 0);
}
- retrievePost : 단건 게시글 조회시 mapper변경으로 파라미터 2개
```java
public PostInfo retrievePost(PostQuery.RetrievePostByPostId retrievePostQuery) {
UUID postId = retrievePostQuery.getPostId();
Post post = postRepository.findById(postId)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "post.byId", List.of(postId.toString())));
// mapper 수정으로 인해 변경
// return PostInfoMapper.INSTANCE.of(post);
long postLikeCount = postLikeRepository.findPostLikeCount(postId);
return PostInfoMapper.INSTANCE.of(post, postLikeCount);
}
public Slice<SimplePostInfo> retrievePosts(PostQuery.RetrievePosts retrievePostsQuery) {
UUID boardId = retrievePostsQuery.getBoardId();
if (!boardRepository.existsById(boardId)) {
throw new BusinessException(ErrorCode.NOT_FOUND, "board.byId", List.of(boardId.toString()));
}
if (StringUtils.hasText(retrievePostsQuery.getTag())) {
return postRepository.findAllByBoardIdAndTag(retrievePostsQuery.getBoardId(),
retrievePostsQuery.getTag(),
retrievePostsQuery.getPageable())
// .map(SimplePostInfoMapper.INSTANCE::of);
.map(post -> SimplePostInfoMapper.INSTANCE.of(post, postLikeRepository.findPostLikeCount(post.getId())));
}
return postRepository.findAllByBoardId(boardId, retrievePostsQuery.getPageable())
// .map(SimplePostInfoMapper.INSTANCE::of);
.map(post -> SimplePostInfoMapper.INSTANCE.of(post, postLikeRepository.findPostLikeCount(post.getId())));
}
해당 post의 좋아요 수
반환public interface PostLikeRepository extends JpaRepository<PostLike, UUID>, PostLikeRepositoryCustom {
Optional<PostLike> findByUserIdAndPostId(UUID userId, UUID postId);
boolean existsByUserIdAndPostId(UUID userId, UUID postId);
}
public interface PostLikeRepositoryCustom {
long findPostLikeCount(UUID postId);
}
public class PostLikeRepositoryCustomImpl implements PostLikeRepositoryCustom {
private final JPAQueryFactory jpaQueryFactory;
public PostLikeRepositoryCustomImpl(EntityManager entityManager){
this.jpaQueryFactory = new JPAQueryFactory(entityManager);
}
@Override
public long findPostLikeCount(UUID postId){
return jpaQueryFactory
.select(postLike.count())
.from(postLike)
.where(postLike.post.id.eq(postId)).fetchOne();
}
}
@RestController
@RequestMapping("/api/v1/users/{userId}")
@RequiredArgsConstructor
public class PostCommandController {
private final PostCommandService postCommandService;
// likePost와 cancleLikePost 1개로 통합
@PostMapping("/like/posts/{postId}")
public ResponseEntity<CommonResponse<?>> savePostLike(@PathVariable UUID userId,
@PathVariable UUID postId) {
PostCommand.LikePostCommand command = PostCommand.LikePostCommand.builder()
.userId(userId)
.postId(postId)
.build();
postCommandService.savePostLike(command);
return ResponseEntity
.status(HttpStatus.OK)
.body(CommonResponse.success("포스트 좋아요 변경 성공"));
}
}
PostLike table
postId(e3f3ffb0-a724-4ab3-9d71-4bb30f34f87d)로 단건 게시글 조회시 likeCount 2개 나옴
List<PostLike> likes
에 넣은 다음 컬렉션의 수를 반환2022-11-08 13:15:29.410 DEBUG 7298 --- [nio-8080-exec-4] org.hibernate.SQL :
select
post0_.post_id as post_id1_4_0_,
post0_.created_at as created_2_4_0_,
post0_.updated_at as updated_3_4_0_,
post0_.board_id as board_id7_4_0_,
post0_.content as content4_4_0_,
post0_.title as title5_4_0_,
post0_.user_id as user_id8_4_0_,
post0_.view_count as view_cou6_4_0_,
board1_.board_id as board_id1_0_1_,
board1_.created_at as created_2_0_1_,
board1_.updated_at as updated_3_0_1_,
board1_.name as name4_0_1_,
user2_.user_id as user_id1_11_2_,
user2_.email as email2_11_2_,
user2_.nickname as nickname3_11_2_,
user2_.role as role4_11_2_
from
post post0_
left outer join
board board1_
on post0_.board_id=board1_.board_id
left outer join
users user2_
on post0_.user_id=user2_.user_id
where
post0_.post_id=?
2022-11-08 13:15:29.432 DEBUG 7298 --- [nio-8080-exec-4] org.hibernate.SQL :
select
count(postlike0_.post_like_id) as col_0_0_
from
post_like postlike0_
where
postlike0_.post_id=?
2022-11-08 13:15:29.438 DEBUG 7298 --- [nio-8080-exec-4] org.hibernate.SQL :
select
comments0_.post_id as post_id7_1_1_,
comments0_.comment_id as comment_1_1_1_,
comments0_.comment_id as comment_1_1_0_,
comments0_.created_at as created_2_1_0_,
comments0_.updated_at as updated_3_1_0_,
comments0_.content as content4_1_0_,
comments0_.is_deleted as is_delet5_1_0_,
comments0_.parent_comment_id as parent_c6_1_0_,
comments0_.post_id as post_id7_1_0_,
comments0_.user_id as user_id8_1_0_
from
comment comments0_
where
comments0_.post_id=?
order by
comments0_.created_at asc
2022-11-08 13:15:29.443 DEBUG 7298 --- [nio-8080-exec-4] org.hibernate.SQL :
select
posttags0_.post_id as post_id2_7_1_,
posttags0_.post_tag_id as post_tag1_7_1_,
posttags0_.post_tag_id as post_tag1_7_0_,
posttags0_.post_id as post_id2_7_0_,
posttags0_.tag_id as tag_id3_7_0_
from
post_tag posttags0_
where
posttags0_.post_id=?
2022-11-08 13:15:29.454 DEBUG 7298 --- [nio-8080-exec-4] org.hibernate.SQL :
select
tag0_.tag_id as tag_id1_10_0_,
tag0_.created_at as created_2_10_0_,
tag0_.updated_at as updated_3_10_0_,
tag0_.name as name4_10_0_
from
tag tag0_
where
tag0_.tag_id=?
@Formula 어노테이션 사용
@Formula 란?
Fomula(SQL 조각)
을 엔티티 속성으로 사용할 수 있음@Entity(name = "Account")
public static class Account {
@Id
private Long id;
private Double credit;
private Double rate;
@Formula(value = "credit * rate")
private Double interest;
}
doInJPA(this::entityManagerFactory, entityManager -> {
Account account = new Account();
account.setId(1L);
account.setCredit(5000d);
account.setRate(1.25 / 100);
entityManager.persist(account);
});
doInJPA(this::entityManagerFactory, entityManager -> {
Account account = entityManager.find(Account.class, 1L);
assertEquals(Double.valueOf(62.5d), account.getInterest()); //interest 값 = 5000 * (1.25/100) = 62.5가 들어감
});
INSERT INTO Account (credit, rate, id)
VALUES (5000.0, 0.0125, 1)
SELECT
a.id as id1_0_0_,
a.credit as credit2_0_0_,
a.rate as rate3_0_0_,
a.credit * a.rate as formula0_0_
FROM
Account a
WHERE
a.id = 1
객체 패러다임 불일치 문제
를 해결 할 수 있는 점과 영속성 컨텍스트(엔티티를 영구 저장하는 환경) 제공
이 큰 특징 Spring Data JPA
는 JPA를 쓰기 편하게 만들어 놓은 모듈임@Formula 어노테이션에 subselect 를 포함할 수 있도록 변경
@Formula("(select count(*) from post_like l where l.post_id = post_id)")
private int likeCount;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post extends DateTimeEntity {
@Id
@GeneratedValue(generator = "UUID")
@Column(name = "post_id", columnDefinition = "BINARY(16)")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
private String title;
private String content;
// 추가
@Formula("(select count(*) from post_like l where l.post_id = post_id)")
private int likeCount;
@Builder
public Post(Board board, User user, String title, String content) {
this.board = board;
this.user = user;
this.title = title;
this.content = content;
this.viewCount = 0;
}
}
### 결과 쿼리
```Mysql
select
post0_.post_id as post_id1_4_0_,
post0_.created_at as created_2_4_0_,
post0_.updated_at as updated_3_4_0_,
post0_.board_id as board_id7_4_0_,
post0_.content as content4_4_0_,
post0_.title as title5_4_0_,
post0_.user_id as user_id8_4_0_,
post0_.view_count as view_cou6_4_0_,
(select
count(*)
from
post_like l
where
l.post_id = post0_.post_id) as formula1_0_,
board1_.board_id as board_id1_0_1_,
board1_.created_at as created_2_0_1_,
board1_.updated_at as updated_3_0_1_,
board1_.name as name4_0_1_,
user2_.user_id as user_id1_11_2_,
user2_.email as email2_11_2_,
user2_.nickname as nickname3_11_2_,
user2_.role as role4_11_2_
from
post post0_
left outer join
board board1_
on post0_.board_id=board1_.board_id
left outer join
users user2_
on post0_.user_id=user2_.user_id
where
post0_.post_id=?
기존 코드
List<PostLike> likes
의 size를 구함Domain
Post (게시글)
}
Service
Mapper
Controller