mybatis에서 oneToMany 형태의 객체를 조회 할때 사용할수 있는 기능

mybatis에서 oneToMany 형태의 객체를 조회 할때 사용할수 있는 기능

JPA에서 OneToMany를 사용하면 객체의 구조적으로 하위 테이블 정보를 select 할수 있다.

Mybatis에서도 가능한데 해당 기능은 아래 처럼 2가지 방법으로 설정 할수 있다.

먼저 테이블 생성을 위해 Entity를 만들면 Member 와 MemberDetail로 만들어 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.github.sejoung.test.sample.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "MEMBER")
@Entity
@ToString
public class Member {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "member_id")
private Long memberId;

private String name;

@Column(name = "create_dt")
private LocalDateTime createDate;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "member")
private List<MemberDetail> details = new ArrayList<>();

@Builder
private Member(String name, LocalDateTime createDate) {
this.name = name;
this.createDate = createDate;
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package com.github.sejoung.test.sample.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "MEMBER_DETAIL")
@Entity
@ToString
public class MemberDetail {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "member_detail_id")
private Long memberDetailId;

@ManyToOne
@JoinColumn(name = "member_id")
private Member member;

private String type;

private String description;

@Builder
private MemberDetail(Member member, String type, String description) {
this.member = member;
this.type = type;
this.description = description;
}
}


여기서 그럼 mybatis로 조회를 할때 XML로 설정을 하면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.github.sejoung.test.sample.mapper.MemberMapper">
<resultMap id="MemberDTOResult" type="com.github.sejoung.test.sample.dto.MemberDTO">
<id property="memberId" column="memberId"/>
<result property="name" column="name"/>
<result property="createDate" column="createDate"/>
<collection property="details" column="memberId" select="selectMemberDetailListXML"/>
</resultMap>

<select id="selectMemberListXML" resultMap="MemberDTOResult">
select member_id as memberId, name, create_dt as createDate from MEMBER
</select>

<select id="selectMemberDetailListXML"
resultType="com.github.sejoung.test.sample.dto.MemberDetailDTO">
select member_detail_id as memberDetailId, type, description from MEMBER_DETAIL where member_id = #{memberId}
</select>

</mapper>

위 처럼 설정할수 있고 아래 처럼 DTO로 받을수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

package com.github.sejoung.test.sample.dto;

import java.time.LocalDateTime;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
public class MemberDTO {

private Long memberId;
private String name;
private LocalDateTime createDate;
private List<MemberDetailDTO> details;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.github.sejoung.test.sample.mapper;

import com.github.sejoung.test.sample.dto.MemberDTO;
import com.github.sejoung.test.sample.dto.MemberDetailDTO;
import java.util.List;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface MemberMapper {

List<MemberDTO> selectMemberListXML();

List<MemberDetailDTO> selectMemberDetailListXML(Long memberId);

@Select("select member_id as memberId, name, create_dt as createDate from MEMBER")
@Results(value = {
@Result(property = "memberId", column = "memberId"),
@Result(property = "name", column = "name"),
@Result(property = "createDate", column = "createDate"),
@Result(property = "details", column = "memberId", javaType = List.class, many = @Many(select = "selectMemberDetailList"))
})
List<MemberDTO> selectMemberList();

@Select("select member_detail_id as memberDetailId, type, description from MEMBER_DETAIL where member_id = #{memberId}")
@Results(value = {
@Result(property = "memberDetailId", column = "memberDetailId"),
@Result(property = "type", column = "type"),
@Result(property = "description", column = "description")
})
List<MemberDetailDTO> selectMemberDetailList(Long memberId);
}


어너테이션 기반으로 셋팅을 해서 하는 방법은 위처럼 처리가 될수 있다.

테스트 코드를 짜서 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.github.sejoung.test.sample.mapper;

import com.github.sejoung.test.sample.domain.Member;
import com.github.sejoung.test.sample.domain.MemberDetail;
import com.github.sejoung.test.sample.dto.MemberDTO;
import java.time.LocalDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureMybatis
class MemberMapperTest {

@Autowired
private MemberMapper memberMapper;

@Autowired
private TestEntityManager entityManager;

@Transactional
@DisplayName("XML 설정")
@Test
void selectMemberListXML() {

var member = Member.builder().name("완구").createDate(LocalDateTime.now()).build();

entityManager.persist(member);
entityManager
.persist(MemberDetail.builder().description("레고").type("장난감").member(member).build());
entityManager
.persist(
MemberDetail.builder().description("플레이모빌").type("장난감").member(member).build());
entityManager.flush();

List<MemberDTO> memberList = memberMapper.selectMemberListXML();
log.debug("memberList {}", memberList);
Assertions.assertThat(memberList.size()).isOne();
Assertions.assertThat(memberList.get(0).getDetails().size()).isEqualTo(2);
}

@Transactional
@DisplayName("어너테이션 설정")
@Test
void selectMemberList() {

var member = Member.builder().name("완구").createDate(LocalDateTime.now()).build();

entityManager.persist(member);
entityManager
.persist(MemberDetail.builder().description("레고").type("장난감").member(member).build());
entityManager
.persist(
MemberDetail.builder().description("플레이모빌").type("장난감").member(member).build());
entityManager.flush();
List<MemberDTO> memberList = memberMapper.selectMemberList();
log.debug("memberList {}", memberList);
Assertions.assertThat(memberList.size()).isOne();
Assertions.assertThat(memberList.get(0).getDetails().size()).isEqualTo(2);
}
}

정상적으로 데이터가 들어가서 처리되는것을 볼수가 있다.

참조