[MeU] Hashtag 기능 개발

sorzzzzy·2022년 4월 11일
3

Spring Project

목록 보기
16/18
post-thumbnail

SNS하면 빼놓을 수 없는 해시태그 기능을 구현해보겠다😎

Post 테이블과 Tag 테이블이 다대다 매핑인 만큼, 가운데에 매핑 테이블을 따로 생성하는 것이 좋다고 피드백을 받았었다.


해시태그를 생성하는 로직은 다음과 같다.

  1. 게시글을 생성하는 동시에 content 필드를 가져와 정규식을 활용하여 해시태그 리스트를 만든다.
    ➡️ ' ' , # 기준으로 추출
  2. 해시태그 리스트를 하나하나 보면서 해시태그가 존재하는 지 확인한다.
    ➡️ 기존 Tag 테이블에 존재하지 않는 해시태그라면 Tag , tagPostMapping 테이블에 모두 추가
    ➡️ 기존에 존재하는 해시태그라면, tagPostMapping 테이블에만 추가
  3. Tag 테이블의 tagCount 필드 값을 1 증가시킨다.
  4. 이후에 개발할 태그 기반 검색 기능을 위해, 게시글이 삭제되면 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 객체를 기반으로 태그 리스트를 생성하기 위해 tagServicecreateTagList() 메소드를 호출한다.


✔️ 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;
    }

}
  1. 전달받은 post 객체의 content 필드를 가져와 정규식을 통해 해시태그 리스트를 추출한다.
  2. 태그 리스트와 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값을 가져다 쓸 수 있다!


  1. 태그의 개수를 카운트한다.
  2. 태그 이름을 기반으로 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>

게시글 삭제시 태그 정보도 함께 삭제

태그 내용 기반 검색 기능의 로직은 다음과 같다.

  1. 검색된 tag.content 를 통해 tag.tagId 를 받아온다.
  2. 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 기능 구현

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 페이지를 원래 구현하려 했던 방식으로 업그레이드 시켜봐야겠다😂🔥

profile
Backend Developer

0개의 댓글