QueryDSL์ ์ฌ์ฉํ๋ฉด์ ์กฐํ Query ์ฑ๋ฅ์ ๋์ด๋ ๋ฐฉ๋ฒ ์ค์ ํ๋๋, ์ํฐํฐ๊ฐ ์๋ DTO๋ก ๊ฐ์ ๋ฐ์ผ๋ฉด ์๋ ํฅ์์ ์ป์ ์ ์์ต๋๋ค.
๊ทธ ์ด์ ๋ ์๋์ ๊ฐ์๋ฐ์.
1. ํ์ํ ํ๋๋ง ์กฐํํ๋ฏ๋ก ๋น์ฐํ๊ฒ๋ ์กฐํ ์๋๊ฐ ์ฆ๊ฐํ๋ค.
SQL์ SELECT๋ฌธ์ ๋ณด๋ฉด, ํ์ํ ์ปฌ๋ผ๋ง ์ป์ด์ค๋ ๊ฒ์ด ํ ์ด๋ธ ์ ์ฒด๋ฅผ ์ป์ด์ค๋ ๊ฒ๋ณด๋ค ๋น์ฐํ๊ฒ๋ ๋น ๋ฆ ๋๋ค. DTO๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ํ ๋ฐ์ดํฐ๋ง ์กฐํํ๋ ค๋ ์๋๊ฐ ๋ดํฌ๋์ด ์์ต๋๋ค.
๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง ์์์ผ๋ก์จ ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์ต์ ํ ํ ์ ์์ต๋๋ค.
2. JPA์ ์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ฆฌ๋์์ด ์๋๋ค.
์ํฐํฐ๋ JPA์ ๊ด๋ฆฌ ๋์์ด๋ฏ๋ก ์์์ฑ ์ปจํ ์คํธ์ ์ํด ๊ด๋ฆฌ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์๋์ ์ผ๋ก ๋ ๋ง์ ์ ์์ต๋๋ค.
๋ฐ๋ฉด DTO๋ ์์ํ ๋ฐ์ดํฐ ๊ฐ์ฒด์ด๋ฏ๋ก ์์์ฑ ์ปจํ ์คํธ์ ์ํด ๊ด๋ฆฌ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ DTO๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ผ ์ ์์ต๋๋ค.
๋ํ Lazy Loading ๋ฑ์ ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฐ์ ์ฌ์ง๋ ์์ต๋๋ค.
์ถ๊ฐ์ ์ผ๋ก ์ฑ๋ฅ๊ณผ ๋ณ๊ฐ๋ก, ์ํฐํฐ๋ DDD์์๋ ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ์ด๋ฏ๋ก ๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํ์ง๋ง, DTO๋ ๋ณ๊ฐ์ ๋ฐ์ดํฐ ์ ์ก ๊ฐ์ฒด์ด๋ฏ๋ก ๋ถ๋ฆฌ๋ ํํ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ์์คํ ์ ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ์ป์ ์ ์์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก๋ถํฐ ์กฐํ๋ฅผ ํ๊ธฐ ์ํ๋ค๋ฉด DTO๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์๋ฐ, QueryDSL์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ก DTO๋ฅผ ์ป์ผ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
๋ฐ๋ก DTO Projection์ ํตํด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
๐ DTO Projection
DTO Projection์ ์ํด QueryDSL์ com.querydsl.core.types ํจํค์ง์์ Projections ํด๋์ค๋ฅผ ์ ๊ณตํ๊ฒ ๋ฉ๋๋ค.
์ฟผ๋ฆฌ๋ฅผ ํตํด ์ป์ด๋ธ ์ํฐํฐ์ ๊ฒฐ๊ณผ๋ฌผ์ DTO๋ก ๋งคํ์ํค๊ธฐ ์ํ Projection ๋ฐฉ๋ฒ์ ํฌ๊ฒ 4๊ฐ์ง๊ฐ ์์ต๋๋ค.
1-1. Projections.bean
public class UserDTO {
private Long id;
private String username;
public UserDTO(){}
public Long getId(){
return this.id;
}
public void setId(Long id){
this.id = id;
}
public String getUserName(){
return this.username;
}
public void setUserName(String username){
this.username = username;
}
}
List<UserDTO> userDTOs = queryFactory
.select(Projections.bean(UserDTO.class, user.id, user.username))
.from(user)
.fetch();
์ฒซ ๋ฒ์งธ๋ ๊ธฐ๋ณธ ์์ฑ์์ setter, getter๋ฅผ ์ฌ์ฉํ๋ Projections.bean ๋ฐฉ์์ ๋๋ค.
DTO์ ๊ธฐ๋ณธ ์์ฑ์์ setter, getter๊ฐ ์๋ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Projections.bean(DTO์ ํด๋์ค, ๋งคํ๋ ํ๋1, ๋งคํ๋ ํ๋2, ...)
์์ ๊ฐ์ ํ์์ ํตํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ์ ๊ทผ์ ์ด์๊ฐ public์ธ ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ๋ฐ๋์ ์์ฑํด์ผ ํฉ๋๋ค.
public class UserDTO {
private Long id;
private String name;
public UserDTO(){}
public Long getId(){
return this.id;
}
public void setId(Long id){
this.id = id;
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
}
List<UserDTO> userDTOs = queryFactory
.select(Projections.bean(UserDTO.class, user.id, user.username.as("name")))
.from(user)
.fetch();
์ฟผ๋ฆฌ์ ๋ํ ๊ฒฐ๊ณผ์ DTO์ ํ๋ ๋ช ์ด ๋ค๋ฅด๋ค๋ฉด, ์์ ๊ฐ์ด .as()๋ฅผ ํตํด ๋ณ์นญ์ ์ ์ํด์ค์ผ Projection์ด ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํฉ๋๋ค.
1-2. Projections.fields
public class UserDTO {
public Long id;
public String username;
}
List<UserDTO> userDTOs = queryFactory
.select(Projections.fields(UserDTO.class, user.id, user.username))
.from(user)
.fetch();
์์ Projections.bean ๋ฐฉ์๊ณผ ๊ต์ฅํ ์ ์ฌํ์ง๋ง, ํ๋์ ์ด๋ฆ์ ํตํด ํ๋ก์ ์ ์ ์งํํฉ๋๋ค.
1-3. Projections.constructor
public class UserDTO {
private Long id;
private String username;
public UserDTO(Long id, String username) {
this.id = id;
this.username = username;
}
// getter ์๋ต
}
List<UserDTO> userDTOs = queryFactory
.select(Projections.constructor(UserDTO.class, user.id, user.username))
.from(user)
.fetch();
์ ์๋ ์์ฑ์๋ฅผ ํตํด DTO์ ํ๋ก์ ์ ์ ์งํํ๋ ๋ฐฉ์์ ๋๋ค.
์ฌ์ฉ๋ฒ์ผ๋ก๋ Projections.constructor๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉฐ, ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ธ DTO ํด๋์ค ์ดํ์๋ ์์ฑ์์ ํ๋ผ๋ฏธํฐ ์์๋๋ก ๊ฐ์ ๋ฃ์ด์ค์ผ ํฉ๋๋ค.
1-4. @QueryProjection
public class UserDTO {
private final Long id;
private final String username;
@QueryProjection
public UserDTO(Long id, String username) {
this.id = id;
this.username = username;
}
// getter ์๋ต
}
List<UserDTO> userDTOs = queryFactory
.select(new QUserDTO(user.id, user.username))
.from(user)
.fetch();
๋ง์ง๋ง์ผ๋ก @QueryProjection์ ์ด์ฉํ๋ ๋ฐฉ์์ ๋๋ค.
DTO์ ์์ฑ์ ์์ @QueryProjection ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ฃผ๋ฉด, ํด๋น DTO ์ญ์ Qํ์ ํด๋์ค๊ฐ ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค.
QueryDSL ์ฝ๋์์ new QUserDTO์ ๊ฐ์ด QDTO๊ฐ์ฒด๋ฅผ ์์ฑํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋นํด ์ปดํ์ผ ํ์์ ํ๋ ํ์ ์ฒดํน์ด ๊ฐ๋ฅํด ํ์ ์ธ์ดํํ๊ณ , DTO ๊ฐ์ฒด๋ฅผ new ํค์๋๋ฅผ ํตํด ์์ฑํ๊ธฐ ๋๋ฌธ์ ์ข ๋ ์น์ํ ์ฝ๋๊ฐ ๋๋ ๊ฒ์ด ์ฅ์ ์ ๋๋ค.
์ข์ ๋ฐฉ๋ฒ์ด์ง๋ง, DTO๊ฐ QueryDSL์ ์ข ์์ ์ด๋ฏ๋ก, ๊ฐ ๊ณ์ธต๋ณ๋ก DTO ๋ณํ์ด ์ด๋ค์ง๋ ๊ตฌ์กฐ์๋ ์ฌ์ฉํด๋ ํฐ ๋ฌธ์ ๊ฐ ์์ง๋ง ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ DTO๊ฐ QueryDSL์ ์ํด ์์กด๊ด๊ณ๊ฐ ๋ณต์กํด์ง ์ผ๋ ค๊ฐ ์์ด ์ด ์ ์ ์ถฉ๋ถํ ๊ณ ๋ คํ ํ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๐ DTO Transform / Groupby
์์ ํ ์คํธ ์ฝ๋ ์๋ฃ๋ฅผ ์ดํด๋ณด๋ฉด, ์ฝ๊ฒ transform, groupBy ๋ฉ์๋์ ๋ํด ์ดํดํ ์ ์์ต๋๋ค.
QueryDSL์์๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ ๋ฉ๋ชจ๋ฆฌ์์ ๋ฐ์ดํฐ๋ฅผ ์ํ๋ ์๋ฃํ์ผ๋ก ๊ฐ๊ณตํ ์ ์๋ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ ๋ฐ, ๊ทธ๊ฒ์ด ๋ฐ๋ก transfrom()๊ณผ groupBy()์ ๋๋ค.
transform ๋ด์์ ์ฌ์ฉ๋๋ groupBy ๋ฉ์๋๋ SQL์์์ GroupBy ์๋ ๋ค๋ฅด๋ฉฐ, transform์์ groupBy๋ฅผ ์ด๋ค๊ณ ํด์ ์ฟผ๋ฆฌ์์ groupBy ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง ์์ต๋๋ค.
์์ ์ฝ๋๋ฅผ ํ๋ ์ดํด๋ด ์๋ค.
private static final List<Post> posts = Arrays.asList(
new Post(1, "Post 1", users.get(0)),
new Post(2, "Post 2", users.get(0)),
new Post(3, "Post 3", users.get(1)));
private static final List<Comment> comments = Arrays.asList(
new Comment(1, "Comment 1", users.get(0), posts.get(0)),
new Comment(2, "Comment 2", users.get(1), posts.get(1)),
new Comment(3, "Comment 3", users.get(2), posts.get(1)),
new Comment(4, "Comment 4", users.get(0), posts.get(2)),
new Comment(5, "Comment 5", users.get(1), posts.get(2)),
new Comment(6, "Comment 6", users.get(2), posts.get(2)));
private static final QComment comment = QComment.comment;
private static final QPost post = QPost.post;
@Test
public void group_min() {
Map<Integer, String> results = CollQueryFactory.from(post, posts).from(comment, comments)
.where(comment.post.id.eq(post.id))
.transform(groupBy(post.id).as(min(comment.text)));
assertEquals("Comment 1", results.get(1));
assertEquals("Comment 2", results.get(2));
assertEquals("Comment 4", results.get(3));
}
ํฌ์คํธ์ ํด๋น ํฌ์คํธ์ ๋๊ธ์ ๊ฐ์ ธ์ ๊ฒฐ๊ณผ๋ฅผ ์ง๊ณํ ๋, fetch()๊ฐ ์๋ transform()์ ํตํด ๋ค๋ฅด๊ฒ ์ง๊ณํ ์ ์์ต๋๋ค.
.transform(groupBy(๊ธฐ์ค Key ๊ฐ).as(Key ๊ฐ์ ๋งคํ๋ ๊ฐ))์ ์ฌ์ฉํ๋ฉด Map ํ์ ์ผ๋ก ๋งคํ๋ฉ๋๋ค.
.groupBy ๋ฉ์๋(.transform ์ด์ ์ ๊ฑฐ๋ .groupBy ๋ฉ์๋)์๋ ๋ค๋ฅธ ์ ์, .groupBy ๋ฉ์๋๋ฅผ ํตํด SQL ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ฉด SELECT ๋ฌธ์ groupBy ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง๋ง Map<Integer, String>๊ณผ ๊ฐ์ด ์๋ฃํ์ด ๋งคํ๋์ง ์๊ณ QueryDSL์ ์๋ฃํ์ธ Tuple์ผ๋ก ๋ฆฌํด๋ฉ๋๋ค.
๋ฌผ๋ก .groupBy ๋ฉ์๋์ .transform(groupBy())๋ฅผ ๊ฐ์ด ์ฌ์ฉํด SELECT์๋ ํ์ํ ๋ฐ์ดํฐ๋ง ์ ๋ ํธ ํ๊ณ ์ง๊ณ ์ญ์ Java์์ ์ฌ์ฉํ๊ณ ์ถ์ ์๋ฃํ์ผ๋ก ๋ณ๊ฒฝํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
์๋์ ์์๋ถํฐ๋ ์ ๊ฐ transform ๋ฉ์๋๋ฅผ ๊ฐ๋ฐ์์ ํ์ฉํ ์์์ ๋๋ค.
๐ค ์ฌ์ฉ๋ ๋๋ฉ์ธ ์ฝ๋
๐ StarPagePost Aggregate Root
@Entity
@Table(name = "star_page_post")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class StarPagePost extends NEOTimeDefaultEntity {
@Id
@Column(name = "post_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
@Column(name = "category_id")
private CategoryId categoryId;
@Column(name = "post_title")
private String title;
@Embedded
@Column(name = "author_name")
private Author author;
@Column(name = "status")
@Convert(converter = PostStatusConverter.class)
private PostStatus status;
@Column(name = "like_count", nullable = false)
private int likeCount;
@Column(name = "host_heart", nullable = false)
private boolean hostHeart;
@Column(name = "exposure_at")
private LocalDateTime exposureAt;
@Column(name = "post_type", nullable = false)
@Convert(converter = PostTypeConverter.class)
private PostType postType;
@ElementCollection
@CollectionTable(name = "star_page_post_like", joinColumns = @JoinColumn(name = "post_id"))
private Set<PostLike> likes = new HashSet<>();
// .. ์๋ต
}
๐ Category Aggregate Root
@Entity
@Table(name = "neo_starpage_category")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Category {
@Getter
@EmbeddedId
@Column(name = "category_id")
private CategoryId categoryId;
@Getter
@Column(name = "starpage_id")
private StarPageId starPageId;
@Column(name = "category_status")
@Convert(converter = CategoryStatusConverter.class)
private CategoryStatus categoryStatus;
@Getter
@Column(name = "content_type")
@Convert(converter = ContentTypeConverter.class)
private ContentType contentType;
@Getter
@Embedded
@AttributeOverride(name = "categoryTitle", column = @Column(name = "title"))
private CategoryInformation categoryInformation;
@Embedded
private ContentRestriction restriction;
}
๐ StarPagePost Aggregate Root
@Entity
@Table(name = "star_page_post")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class StarPagePost extends NEOTimeDefaultEntity {
@Id
@Column(name = "post_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
@Column(name = "category_id")
private CategoryId categoryId;
@Column(name = "post_title")
private String title;
@Embedded
@Column(name = "author_name")
private Author author;
@Column(name = "status")
@Convert(converter = PostStatusConverter.class)
private PostStatus status;
@Column(name = "like_count", nullable = false)
private int likeCount;
@Column(name = "host_heart", nullable = false)
private boolean hostHeart;
@Column(name = "exposure_at")
private LocalDateTime exposureAt;
@Column(name = "post_type", nullable = false)
@Convert(converter = PostTypeConverter.class)
private PostType postType;
@ElementCollection
@CollectionTable(name = "star_page_post_like", joinColumns = @JoinColumn(name = "post_id"))
private Set<PostLike> likes = new HashSet<>();
}
๐ VotePost Aggregate Root
@Entity
@Table(name = "star_page_vote_post")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class VotePost extends StarPagePost {
@Column(name = "question")
private String question;
@Column(name = "vote_status")
@Enumerated(value = EnumType.STRING)
private VoteStatus voteStatus;
@Column(name = "time_to_live")
private int timeToLive;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
@OrderColumn(name = "vote_item_order")
private List<VoteItem> voteItems = new ArrayList<>();
}
๐ VotePostHeadLine DTO
@Getter
@ToString
@RequiredArgsConstructor
public class StarPageHeadLine {
private final Long postId;
private final int likeCount;
private final String categoryType;
}
@Getter
@ToString(callSuper = true)
public class VotePostHeadLine extends StarPageHeadLine {
private final String question;
private final Map<String, Integer> optionCount;
public VotePostHeadLine(Long postId, int likeCount, String question, Map<String, Integer> optionCount){
super(postId, likeCount, ContentType.VOTE.name());
this.question = question;
this.optionCount = optionCount;
}
}
๐ QueryDSL ์ฝ๋
@Override
public List<VotePostHeadLine> searchVotePostHeadLineByCategoryId(StarPageId starPageId, CategoryId categoryId, long limit) {
return queryFactory
// 1. ์ฐ์ Entity๊ฐ ์๋ Tuple ํํ์ ๊ฒฐ๊ณผ๋ฌผ์ด ๋์ค๋๋ก Select
.select(
starPagePost.id,
starPagePost.likeCount,
votePost.question,
voteItem.optionText,
voteItem.voterSet.size()
)
.from(starPagePost)
.innerJoin(category).on(starPagePost.categoryId.eq(category.categoryId))
.innerJoin(votePost).on(eqPostId(votePost.id))
.innerJoin(votePost.voteItems, voteItem)
.where(
eqStarPageId(starPageId),
eqCategoryId(categoryId),
eqPostStatus(PostStatus.OPEN).or(eqPostStatus(PostStatus.MAIN_EXPOSED))
)
.orderBy(starPagePost.createdAt.desc())
.limit(limit)
// 2. transform๊ณผ groupby๋ฅผ ํตํด ๋ฉ๋ชจ๋ฆฌ์์ DTO๋ก ๋ณํ
.transform(GroupBy.groupBy(starPagePost.id)
.list(Projections.constructor(
VotePostHeadLine.class,
starPagePost.id,
starPagePost.likeCount,
votePost.question,
map(voteItem.optionText, voteItem.voterSet.size())
)));
}
QueryDSL์์ DTO ํ๋ก์ ์ ๊ณผ transform์ ๊ฐ์ด ์ฌ์ฉํ ์์์ ๋๋ค.
์ฝ๋๋ฅผ ๋ณด๋ฉด, ํฌ์คํธ์ ID / ์ข์์ ์ / ํฌํ ํฌ์คํธ์ ์ง๋ฌธ / ์ง๋ฌธ ํญ๋ชฉ / ์ง๋ฌธ ํญ๋ชฉ ๋น ํฌํ ์๋ฅผ SELECTํ๊ณ ์์ต๋๋ค.
Tuple(1, 0, "๋ช ์์ ๋ง๋ ๊น์?", "3์", 0)
Tuple(1, 0, "๋ช ์์ ๋ง๋ ๊น์?", "4์", 0)
Tuple(1, 0, "๋ช ์์ ๋ง๋ ๊น์?", "5์", 0)
ํ ํฌํํ ํฌ์คํธ๋น ์ฌ๋ฌ ํญ๋ชฉ๊ณผ ๊ทธ ํญ๋ชฉ์ ๋ํ ํฌํ์๋ฅผ ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์ 1:N์ ์๋ฃํํ๊ฐ ๋ฉ๋๋ค.
์ด๋ .transform(groupBy())์์ starPagePost.id ํค ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์ง๊ณ๋ฅผ ์๋ํฉ๋๋ค.
.as()๋ฅผ ์ฌ์ฉํ๋ฉด Map์ผ๋ก ๋งคํ๋์ง๋ง, list๋ก๋ ๋งคํํ ์ ์์ต๋๋ค.
GroupBy.groupBy().list()์์ list ๋ฉ์๋, ๋ค์ ๋์ค๋ list ์์ map ๋ฉ์๋๋ ์ฌ์ฉํ๋ ค๋ฉด ์๋์ ๊ฐ์ด import๋ฅผ ํด์ผ ํฉ๋๋ค.
import static com.querydsl.core.group.GroupBy.list;
import static com.querydsl.core.group.GroupBy.map;
VotePostHeadLine์์ ํ๋๋ก Map<String, Integer> ์๋ฃํ์ ๊ฐ๊ณ ์๋๋ฐ, ํด๋น ํ๋๋ฅผ map() ๋ฉ์๋๋ฅผ ํตํด ๋งคํํ ๋ชจ์ต์ ๋๋ค.
์๋๋ ํด๋น ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ ๊ฐ์ ๋๋ค.
Select ๋ฌธ์ ๋ณด๋ฉด, ์๊ตฌ์ฌํญ์ ๋ง๋ ๊ฐ๋ค์ SELECTํ๊ณ , ์ต์ข ์๋์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด VotePostHeadLine์ ์๋ง๊ฒ Select ํ ๊ฐ์ด transform ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. (option_text์ select count ์๋ธ์ฟผ๋ฆฌ๋ฅผ Map์ผ๋ก ๋ณ๊ฒฝ)
1:N ๊ด๊ณ๋ฅผ ๊ฐ์ก๋ optionCount์๋ ์ฌ๋ฐ๋ฅด๊ฒ 3์, 4์, 5์์ ๋ํ ๋ด์ฉ์ด ์ ๋งคํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.