작성
·
2K
·
수정됨
0
영한님 안녕하세요, QueryDSL 공부 중 막히는 부분이 있어 질문드립니다.
1:N 연관관계를 가지고 있는 두 엔티티 Team 과 Member 가 있을 때, QueryDSL 로 leftJoin & fetchJoin 으로 두 테이블을 조인하여 Member 목록을 조회하고 싶은데, 만약 Team 테이블에 FK 에 해당하는 row 가 존재하지 않는 경우에는 Member.team
에 그냥 null 이 들어있고 객체에 접근하더라도 추가적인 select 쿼리가 실행되지 않도록 하고 싶습니다. 그런데 제 바람과는 달리 Member.team
을 참조하는 시점에 lazy loading 이 되면서 select 쿼리가 실행되더라고요.
실제 코드를 바탕으로 설명해보겠습니다.
아래와 같이 1:N 연관 관계를 갖는 Team 과 Member 라는 엔티티가 있습니다.
@Table(name = "member")
@Entity
class Member(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int = 0,
@Column(name = "team_id")
var teamId: Long? = null,
@Column(name = "name")
var name: String? = null,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", insertable = false, updatable = false, foreignKey = ForeignKey(name = "none"))
val team: Team? = null,
)
@Table(name = "team")
@Entity
class Team(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
@Column(name = "name")
var name: String? = null,
)
여기서 아래의 코드로 left outer join 쿼리를 실행합니다.
val members = from(member)
.leftJoin(member.team, team).fetchJoin()
.fetch()
DB 는 아래와 같이 데이터가 저장되어 있습니다.
// team
+----+-------+
| id | name |
+----+-------+
| 1 | team1 |
+----+-------+
// member
+----+---------+------+
| id | team_id | name |
+----+---------+------+
| 1 | 2 | John |
+----+---------+------+
그럼 저는 아래와 같은 Member 객체 하나로만 이루어진 List 를 얻을 수 있을 거라고 생각했고, team 변수에 접근할 때 select 쿼리 실행 없이 null 만을 반환할 것이라고 기대했습니다.
{
"id": 1,
"team_id": 2,
"name": "John",
"team": null
}
하지만 아래와같이 member 테이블을 lazy loading 하는 로그가 찍히네요.
Hibernate: insert into team (id, name) values (default, ?)
Hibernate: insert into member (id, name, team_id) values (default, ?, ?)
Hibernate: select member0_.id as id1_7_0_, team1_.id as id1_9_1_, member0_.name as name2_7_0_, member0_.team_id as team_id3_7_0_, team1_.name as name2_9_1_ from member member0_ left outer join team team1_ on member0_.team_id=team1_.id
Hibernate: select team0_.id as id1_9_0_, team0_.name as name2_9_0_ from team team0_ where team0_.id=?
그런데 만약 DB 의 데이터 중 member 의 team_id 만 1로 변경하니 쿼리 후 member.team
에 접근하더라도 아래와 같이 lazy loading 하는 로그가 찍히지 않았습니다.
Hibernate: insert into team (id, name) values (default, ?)
Hibernate: insert into member (id, name, team_id) values (default, ?, ?)
Hibernate: select member0_.id as id1_7_0_, team1_.id as id1_9_1_, member0_.name as name2_7_0_, member0_.team_id as team_id3_7_0_, team1_.name as name2_9_1_ from member member0_ left outer join team team1_ on member0_.team_id=team1_.id
테스트에 사용한 코드는 아래와 같습니다.
@DataJpaTest
@Import(MemberService::class) // MemberService.listMembers() 에서 QueryDsl 로 쿼리를 합니다.
class MyTest(
private val sut: MemberService,
private val em: EntityManager,
) : FunSpec(
{
beforeEach {
val team = Team(name = "team1")
em.persist(team)
val member = Member(
name = "John",
teamId = team.id + 1, // 이것만 team.id 로 바꾸면 team 접근 시 select 로그가 찍히지 않습니다.
)
em.persist(member)
em.clear()
}
test("my test") {
val members = sut.listMembers()
members.shouldNotBeEmpty()
val team = members.first().team
println(team)
}
},
)
어차피 조회한 엔티티에 변경을 가하지는 않을 것이라, Member 엔티티를 detach 시키고 team 에 접근하면 lazy loading 이 안될까 싶어서 해보았는데 여전히 lazy loading 이 되더라구요 ^^;
일단 @QueryProjection 을 붙인 별도의 DTO 를 정의해 아래와같이 쿼리하는 식으로 해결하려고 하는데 더 좋은 방법은 없을까요?
class MemberDto @QueryProjection constructor(
val id: Int,
val teamId: Long? = null,
val name: String? = null,
val teamName: String? = null,
)
@Service
@Transactional(readOnly = true)
class MemberService : QuerydslRepositorySupport(Member::class.java) {
private val member = QMember.member
private val team = QTeam.team
fun listMembers(): List<MemberDto> {
val members = from(member)
.select(QMemberDto(member.id, member.teamId, member.name, team.name))
.leftJoin(member.team, team)
.fetch()
return members
}
}
감사합니다.
답변 1
0
안녕하세요. 인플언님
fetch join을 사용했기 때문에 이미 데이터를 다 불러왔습니다. 따라서 지연 로딩이 발생하지 않아야 합니다.
해당 코드를 자바로 작성해보시면 정상 동작하는 것을 확인하실 수 있을거에요(혹시 정상 동작하지 않는다면 댓글 남겨주세요)
이 문제는 아마도 코틀린 이슈인 것 같아요. 코틀린 lazy loading 이슈로 검색해보시면 원하시는 답을 찾으실 수 있을거에요^^
감사합니다.
안녕하세요. 인플언님
해당 코드를 자바로 작성해서 실행해보시면 정상 동작하는 것을 확인할 수 있을거에요.
만약 정상 동작하지 않는다면 코드를 올려주시면 도움을 드리겠습니다.
코틀린 관련해서 발생하는 이슈는 저도 잘 모르겠습니다.
감사합니다.
영한님 안녕하세요, 답변 감사합니다!
답이 조금 늦었습니다.
말씀 주신대로 코틀린 lazy loading 으로 검색하여 이것저것 많이 살펴보았지만 거의 모든 글이, 코틀린은 기본적으로 final class 이기 때문에 Hibernate 가 프록시 객체를 만들지 못해서 lazy loading 이 되지 않고 eager loading 이 되는 이슈에 대한 글들이고, all-open 플러그인 적용시 바로 해결되는 경우였습니다. left outer join 을 하는 케이스는 아니었습니다.
제 경우는 lazy loading 이 되지 않고 eager loading 이 되는 이슈라기보다는,
fetch join 을 했는데도 값이 불러와지지 않고 lazy loading 이 되는 경우라(부모 엔티티가 없는 경우) 조금 다른 경우라고 생각이 드는데요,
혹시 생각하셨던 이슈는 다른 이슈일까요?
감사합니다.