SNS하면 빼놓을 수 없는 해시태그 기능을 구현해보겠다😎
Post
테이블과 Tag
테이블이 다대다 매핑인 만큼, 가운데에 매핑 테이블을 따로 생성하는 것이 좋다고 피드백을 받았었다.
해시태그를 생성하는 로직은 다음과 같다.
content
필드를 가져와 정규식을 활용하여 해시태그 리스트를 만든다. ' '
, #
기준으로 추출Tag
테이블에 존재하지 않는 해시태그라면 Tag
, tagPostMapping
테이블에 모두 추가tagPostMapping
테이블에만 추가Tag
테이블의 tagCount
필드 값을 1 증가시킨다.tagPostMapping
테이블 안의 데이터도 함께 삭제된다.✔️ vo/Tag.java
, TagPostMapping.java
@AllArgsConstructor
@Getter @Setter
public class Tag {
Integer tagId;
String content;
Integer tagCount;
Integer weekCount;
public Tag(Integer tagId, String content) {
this.tagId = tagId;
this.content = content;
}
}
@AllArgsConstructor
@Getter @Setter
public class TagPostMapping {
// Integer mapId;
Integer tagId;
Integer postId;
String content;
public TagPostMapping(Integer tagId, Integer postId) {
// this.mapId = mapId;
this.tagId = tagId;
this.postId = postId;
}
}
+) 개발 과정에서 tagPostMapping
테이블의 mapId
필드는 굳이 필요하지 않아서 같아서 없앴다!
✔️ service/PostService.java
수정
.
.
public boolean savePost(Post post, MultipartFile multipartFile) throws IOException {
if (multipartFile != null) {
String imgUrl = s3Service.uploadObject(multipartFile);
post.setImgUrl(imgUrl);
}
Integer result = postMapper.save(post);
// 생성된 post 객체에서 태그 리스트 생성하기
tagService.createTagList(post);
return result == 1;
}
생성된 post 객체를 기반으로 태그 리스트를 생성하기 위해 tagService
의 createTagList()
메소드를 호출한다.
✔️ service/TagService.java
@Service
@AllArgsConstructor
public class TagService {
private TagMapper tagMapper;
private TagPostMapper tagPostMapper;
public void createTagList(Post post) {
Pattern MY_PATTERN = Pattern.compile("#(\\S+)");
Matcher mat = MY_PATTERN.matcher(post.getContent());
List<String> tagList = new ArrayList<>();
while(mat.find()) {
tagList.add((mat.group(1)));
}
System.out.println("Create HashTags Success! -----> " + tagList);
saveTag(tagList, post.getPostId());
}
public Boolean saveTag(List<String> tagList, Integer postId) {
Integer result = 1;
for (String tag : tagList) {
Tag findResult = tagMapper.findTagByContent(tag);
// 등록된 태그가 아니라면 태그부터 추가
if (findResult == null) {
tagMapper.saveTag(tag);
}
// 태그-포스트 매핑 테이블에 데이터 추가
Tag findTag = tagMapper.findTagByContent(tag);
tagMapper.addTagCount(findTag.getTagId());
result = tagPostMapper.saveTagPost(findTag.getTagId(), postId);
}
return result == 1;
}
}
post
객체의 content
필드를 가져와 정규식을 통해 해시태그 리스트를 추출한다.post
객체의 postId
값을 saveTag()
메소드로 전달한다.Mybatis
에서 객체를 저장하고 난 뒤에 바로 그 객체의 pk값을 찍어보면 null
값이 뜬다.
tagPostMapping
테이블에 태그 아이디와 게시글 아이디를 함께 저장해야 했기 때문에 이 부분에서 꽤나 애를 먹었다😢
tagPostMapper.saveTagPost(findTag.getTagId(), postId);
와 같이 객체 저장 후 바로 pk값을 가져오고 싶다면, post-mapper.xml
를 다음과 같이 수정해야 한다.
✔️ post-mapper.xml
<insert id="save" useGeneratedKeys="true"
keyProperty="postId" keyColumn="postId" parameterType="com.codepresso.meu.vo.Post" >
INSERT INTO Post(userId, content, imgUrl)
VALUES (#{post.userId}, #{post.content}, #{post.imgUrl});
</insert>
insert 태그 안에 useGeneratedKeys="true" keyProperty="postId" keyColumn="postId" parameterType="com.codepresso.meu.vo.Post"
옵션을 추가해주면, 바로 pk값을 가져다 쓸 수 있다!
Tag
테이블을 조회한 뒤 등록된 태그가 아니라면 두개의 테이블에 모두 추가하고, 등록이 된 태그라면 태그-포스트 매핑 테이블에만 데이터를 추가한다.✔️ mapper/TagMapper.java
@Mapper
public interface TagMapper {
Tag findTagByContent(@Param("content") String content);
Integer saveTag(@Param("content") String content);
Integer addTagCount(@Param("tagId") Integer tagId);
}
✔️ mapper/TagPostMapper.java
@Mapper
public interface TagPostMapper {
Integer saveTagPost(@Param("tagId") Integer tagId, @Param("postId") Integer postId);
}
✔️ resources/mybatis/mapper/tag-mapper.xml
<mapper namespace="com.codepresso.meu.mapper.TagMapper">
<select id="findTagByContent" resultType="com.codepresso.meu.vo.Tag">
SELECT *
FROM Tag
where content=#{content};
</select>
<insert id="saveTag">
INSERT INTO Tag(content)
VALUES (#{content});
</insert>
<update id="addTagCount">
UPDATE Tag
SET tagCount = tagCount + 1
WHERE tagId=#{tagId};
</update>
</mapper>
✔️ resources/mybatis/mapper/tagPost-mapper.xml
<mapper namespace="com.codepresso.meu.mapper.TagPostMapper">
<insert id="saveTagPost">
INSERT INTO TagPostMapping(tagId, postId)
VALUES (#{tagId}, #{postId});
</insert>
</mapper>
태그 내용 기반 검색 기능의 로직은 다음과 같다.
tag.content
를 통해 tag.tagId
를 받아온다.tagPostMapping
테이블에서 (조인 사용) tagPost.tagId
를 가지는 모든 tagPost.postId
를 조회한다.따라서 게시글이 삭제가 됐다면, 삭제된 postId
를 가지는 모든 태그 정보도 삭제되어야 한다!
✔️ mapper/TagPostMapper.java
@Mapper
public interface TagPostMapper {
Integer saveTagPost(@Param("tagId") Integer tagId, @Param("postId") Integer postId);
// 추가
Integer deleteTagPost(@Param("postId") Integer postId);
}
deleteTagPost
추가
✔️ resources/mybatis/mapper/tagPost-mapper.xml
수정
<mapper namespace="com.codepresso.meu.mapper.TagPostMapper">
<insert id="saveTagPost">
INSERT INTO TagPostMapping(tagId, postId)
VALUES (#{tagId}, #{postId});
</insert>
<delete id="deleteTagPost">
DELETE FROM TagPostMapping
WHERE postId=#{postId};
</delete>
</mapper>
deleteTagPost
추가
✔️ service/TagService.java
수정
.
.
.
// 추가
public boolean deleteTagPost(Integer postId) {
Integer result = tagPostMapper.deleteTagPost(postId);
return result == 1;
}
✔️ service/PostService.java
수정
.
.
.
public boolean deletePost(Integer id, Integer logInUserId) {
Post originalPost = postMapper.findOne(id);
if(!originalPost.getUserId().equals(logInUserId)) {
return false;
}
Integer result = postMapper.delete(id);
// postId 를 기반으로 관련 태그 정보 삭제
tagService.deleteTagPost(id);
return result == 1;
}
Explore 페이지는 가장 많이 언급된 태그들을 보여주는 페이지이다.
가장 많이 언급된 태그들이 포함된 게시글 리스트를 보여주는 방식으로 구현하려고 했는데,
웨.......템플릿에 존재하지 않는거야...?
뚝딱뚝딱 만들어내기에는 내가 너무 프론트 바보라 최~대한 간단하게, 태그 개수를 기준으로 제일 많이 사용된 10개의 태그"만" 보여주는 것으로 방향을 틀었다^^ㅠ
✔️ mapper/TagMapper.java
수정
@Mapper
public interface TagMapper {
Tag findTagByContent(@Param("content") String content);
Integer saveTag(@Param("content") String content);
Integer countTag(@Param("tagId") Integer tagId);
// 추가
List<Tag> findByTagCount();
}
findByTagCount()
추가
✔️ resources/mybatis/mapper/tag-mapper.xml
수정
<mapper namespace="com.codepresso.meu.mapper.TagMapper">
<select id="findTagByContent" resultType="com.codepresso.meu.vo.Tag">
SELECT *
FROM Tag
where content=#{content};
</select>
<insert id="saveTag">
INSERT INTO Tag(content)
VALUES (#{content});
</insert>
<update id="addTagCount">
UPDATE Tag
SET tagCount = tagCount + 1
WHERE tagId=#{tagId};
</update>
<select id="findByTagCount" resultType="com.codepresso.meu.vo.Tag">
select *
FROM Tag
ORDER BY tagCount DESC
LIMIT 10;
</select>
</mapper>
findByTagCount
추가
➡️ tagCount
기준 상위 10개만 조회
✔️ controller/IndexController.java
수정
.
.
.
// Explore Page
@RequestMapping(value = "/explore")
public String getExplorePage(Model model) {
List<Tag> tagList = tagService.findByTagCount();
model.addAttribute("tagList", tagList);
return "explore";
}
본문 내에 해시태그를 여러개 작성해보았다.
게시글 생성!
태그를 작성한 대로 카운팅이 잘 되었고, Explore 페이지에도 조금 많이 허전하지만(?) 가장 많이 언급된 태그 순서대로 10개가 잘 뜬다😅
기존에 존재하는 태그도 섞어서 넣어보았다.
역시나 성공~~
이렇게 기본적인 태그 기능을 구현했다.
앞으로 태그 검색 기능 구현을 마저 진행하고, Explore 페이지를 원래 구현하려 했던 방식으로 업그레이드 시켜봐야겠다😂🔥