Backend

[MyBatis] 객체 안에 리스트, 1:N 관계 데이터 가져오기 (feat. ResultMap, Association, Collection)

연_우리 2022. 1. 20. 22:59
반응형

 

 

[MyBatis] 동작원리, 사용방법 정리

목차 MyBatis 등장배경 [JDBC] 사용방법 JDBC : JAVA DataBase Connectivity 기존 자바에서는 DB를 조작하기 위해서 JDBC API를 사용했다. JDBC는 데이터베이스 종류에 상관없이 JDBC만 알면 어떤 데이터베이스를..

lotuus.tistory.com

위에 글도 보시면 좋아용

 


 

포트폴리오 삽질(..)중이라 블로그가 뜸하다

오늘 하루종일 삽질한 결과를 써보겠다

모르게써여..

 

게시판을 구현하고 있다.

게시글 1개에 파일은 최대 10개까지 저장될 수 있다.

DB도 아래 필드와 동일하게 구현되어있다고 생각하자(=post테이블과 postfile테이블)

class Post{
	Long id;
	String title;
	String content;
}

class PostFile{
	Long postId;
	String filePath;
	String fileName;
}
@Mapper
@Repository
public interface PostRepository {
	@Select("select * from post where id=#{postId}")
	Post findById(Long postId);
}


@Mapper
@Repository
public interface PostFileRepository {
	@Select("select * from postfile where postid=#{postId}")
	List<PostFile> findById(Long postId);
}

 

게시글 상세보기를 일단 되는대로 구현해보자

저장된 post와 postfile을 가져오는데, postfile에서는 이름만 있어도되니까 이름만 걸러내서 데이터를 응답해준다

    public Map<String, Object> detail(Long postId) {
        Post post = postRepository.findById(postId);
        PostDTO.Response postRes = new PostDTO.Response(post);

        Map<String, Object> result = new HashMap<>();
        result.put("post", postRes);

        List<PostFile> postFiles = postFileRepository.findById(postId);
        List<String> postFileNames = new ArrayList<>();
        for (PostFile postFile : postFiles) {
            postFileNames.add(postFile.getFileName());
        }
        result.put("postFileNames", postFileNames);

        return result;
    }

 

일단 post같은 경우엔 엔터티를 직접 view로 내려주고있다

향후 확장성을 고려해서 엔터티를 dto로 변환해서 내려주자

class PostDTO{
	Long id;
	String title;
	String content;
	List<String> fileNames;
}

 

 

그리고 굳이 DB에 2번 접근해야하나? JOIN으로 해결할 수 있지 않을까?

SELECT post.id, post.title, post.content, postfile.filename 
FROM post LEFT JOIN postfile ON post.id=postfile.postid;

21번 게시글 같은 경우엔 파일이 6개가 있기때문에 6번 중복해서 나온다

id, title, content 중복은 어떻게 해결하지? 또 filename을 리스트로 가져올수는없을까???

 

 

 

검색해보니 아래와 같이 나온다

//Xml
//실행 : findPostAndFileById(Long postId);

<resultMap id="postAndFileMap" type="PostDTO">
    <id property="id" column="id"></id>
    <result property="title" column="title"></result>
    <result property="content" column="content"></result>
    <collection property="fileNames" column="filename" javaType="List" ofType="String" select="findFileById"></collection>
</resultMap>

<select id="findPostAndFileById" resultMap="postAndFileMap" parameterType="Long">
	select * from post where id=#{postId}
</select>

<select id="findFileById" resultType="String" parameterType="Long">
	select filename from postfile where postid=#{postId}
</select>
//어노테이션
//실행 : findPostAndFileById(Long postId);

@Select("select * from post where id=#{postId}")
@Results(
	id="id", value = {
	@Result(property="title", column="title"),
	@Result(property="content", column="content"),
	@Result(property = "fileNames", javaType = List.class, many=@Many(select="findFileById"))
})
PostDTO findPostAndFileById(Long postId);

@Select("select filename from postfile where postid=#{postId}")
List<String> fileNames = findFileById(Long postId);

xml의 <id>, 어노테이션의 id를 사용하면 post처럼 중복되는 결과는 제외해서 성능을 향상시킨다고 한다

 

 

 

여기서 단점은 <collection>과 @Many에서 select문을 사용하면 N+1문제가 발생할 수 있다는 것!!

findPostAndFileById()를 실행하면 자동으로 findFileById()가 실행되고, List 결과를 만들어낸다.

다시말해,

findPostAndFileById()를 실행하고 (DB에 요청 1번)

리스트 결과를 만들기위해 파일이 있는만큼 findFileById()를 실행한다(파일이 6개니까 DB에 요청 6번..)

 

 

 

그러면 어떻게 해야하나..

Nested ResultMap을 사용하면 된다고한다

//Xml
//실행 : findPostAndFileById(Long postId);

//나는 List<String> 이라서 ResultMap에 한번에 몰아주겠다
<resultMap id="postAndFileMap" type="PostDTO">
    <id property="id" column="id"></id>
    <result property="title" column="title"></result>
    <result property="content" column="content"></result>
    <collection property="fileNames" column="id" select="fileMap"></collection>
</resultMap>

<resultMap id="fileMap" type="String">
    <result column="filename"></result>	//property가 없는 이유는 List<String>이라 필드가 없어서
</resultMap>

<select id="findPostAndFileById" resultMap="postAndFileMap" parameterType="Long">
    select post.*, postfile.filename 
    from post left join postfile on post.id=postfile.postid
    where post.id=#{postId}
</select>

 

아쉽게도 <collection>에 대응되는 어노테이션이 @Many라서.. XML로만 사용해야될 것 같다

 

 

 

자 근데 여기서 또하나 문제

PostDTO는 이렇게 생겼었다. 그래서 resultMap에 하나하나 필드명 적어가면서 맞춰주고...

class PostDTO{
	Long id;
	String title;
	String content;
	List<String> fileNames;
}

 

만약 PostDTO가... 객체도 받게된다면?!

class PostDTO{
	Post post;
	List<String> fileNames;
}

(뭔가 ORM스럽게 변해가고있다)

 

 

<resultMap id="postMap" type="Post">
    <id property="id" column="id"></id>
    <result property="title" column="title"></result>
    <result property="content" column="content"></result>
</resultMap>


<resultMap id="fileMap" type="String">
    <result column="filename"></result>
</resultMap>


<resultMap id="postAndFileById" type="PostDTO">
    <association property="post" column="id" select="postMap"></association>
    <collection property="fileNames" column="id" select="fileMap"></collection>
    //column은 둘다 id이다 findPostAndFileById에서의 id로 맵핑한다
</resultMap>

<select id="findPostAndFileById" resultMap="postAndFileById" parameterType="Long">
    select post.*, postfile.filename 
    from post left join postfile on post.id=postfile.postid
    where post.id=#{postId}
</select>

association으로 받아오면된다!

resultMap으로 필드 하나하나 추가해주는건 어쩔수없는 것 같다

 

association은 has one 관계로 객체를 받을 때,

collection은 has many 관계로 컬렉션을 받을 때 사용하자

 

 

 

 

반응형
  • 네이버 블러그 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 구글 플러스 공유하기
  • 카카오톡 공유하기