• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

양방향 연관관계와 연관관계의 주인 2 -주의점, 정리

23.10.19 00:07 작성 23.10.19 00:16 수정 조회수 273

0

 

 안녕하십니까 선생님 다름이아니오라 em.flush(), em.clear()를 했을 때와 안했을 때 query를 호출하는 경우와 호출되지 않는 경우에 대해서 이해가 잘 되지 않아 질문드립니다.

 

Team team = new Team();
team.setName("TeamA");
em.persist(team);
 
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
 
em.flush();
em.clear();
 
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
 
for (Member m : members) {
    System.out.println("m = " + m.getUsername());
}
 
tx.commit();

 위 코드에서

em.flush();
em.clear();

를 했든 안했든 findTeam.getMembers(); 은 비어있는 상태 아닌가요..?
둘다 비어있는 상태인데 했을 경우에는 query를 사용하고 안했을 경우에는 query를 호출하지 않는 이유가 궁금합니다.

members 객체가 flush를 했을 때는 프록시 객체고 하지 않았을 경우는 실제 객체라서 그런걸까요?

 

 

 



답변 1

답변을 작성해보세요.

0

y2gcoder님의 프로필

y2gcoder

2023.10.19

안녕하세요. softcamp.study님, 공식 서포터즈 y2gcoder입니다.

13분 11초 쯤 Member 클래스의 setTeam() 메서드를 보시면

this.team = team;
team.getMembers().add(this);

를 해주는 모습을 볼 수 있습니다.

그러면 이제 em.flush(); 와 em.clear()를 해줬을 때와 해주지 않았을 때 동작이 달라지는 이유를 말씀드리겠습니다.

em.flush(), em.clear() 를 해주지 않았을 때

member와 team 모두 영속성 컨텍스트에 있고, member의 setTeam()을 통해 team의 members에 member가 들어가 있기 때문에, em.find()로 team을 호출할 때, 영속성 컨텍스트에 있는 team 엔티티를 조회합니다. 영속성 컨텍스트에 이미 team(member를 들고 있음), member가 존재하기 때문에, 쿼리를 따로 호출하지 않습니다.

em.flush(), em.clear() 를 해줬을 때,

영속성 컨텍스트에 team, member 모두 존재하지 않습니다.

em.find()를 통해 team을 호출하면(Lazy loading 상태) team만 쿼리로 조회하고, member는 프록시 상태입니다. 그 후 team 안의 members의 실제 정보가 필요하니 프록시 객체를 초기화하고, 이 때 member 에 대한 조회 쿼리가 나가게 됩니다.

 

감사합니다.

this.team = team;
team.getMembers().add(this);

를 진행하지 않았을 때도 쿼리가 발생하지 않는 부분을 영상 6분 40초? 부분에서 확인할 수 있습니다.

이 경우에서 em.flush(), em.clear() 를 하지 않으면 컨텍스트에 올라간 객체들은 Team(member 없음), Member 이렇게 존재하게 되는데,

em.find(Team) 을 통해서 얻어온 Team에는 Member가 없는 상황이라고 생각합니다.

이후 team.getMembers() 를 진행하면 Member 가 없는 상황이기에 쿼리가 발생하면서 DB에서 조회할 것 이라고 생각하나, 쿼리를 실행하지 않고 바로 요소가 비어있는 Persistentbag(Collection 구현체) 를 반환합니다.

그렇기 때문에 for ( var m : team.getMembers() ) 을 순회하면 team.getMembers() 가 비어있기 때문에 반복문이 실행되지 않아 표준출력이 발생하지 않습니다.

이와 마찬가지로

em.flush(), em.clear() 를 해줬을 때, em.find()를 통해 team을 호출하면 team 객체를 가져오는 쿼리를 실행하게 되는데, 이때도 team 에대한 정보만 조회하기에 Team에는 Member가 없는 상황이라고 생각합니다.

이후 team.getMembers() 를 하면 마찬가지로 Persistentbag(Collection 구현체) 을 반환하는데 이 비어있는 Collection 에 대해서 for ( var m : team.getMembers() ) 을 진행하면 member에 대한 query를 진행하여 member정보를 얻어옵니다.

정리해보자면 em.flush(), em.clear() 한 경우와 안한 경우 모두 team.getMembers()의 반환값으로Persistentbag 을 반환하는데, 어떤 기준으로 동작방식이 (쿼리가 실행되고 안되고) 달라지는지 궁금합니다ㅠ

em.flush(), em.clear() 한 경우에는
Persistentbag<ProxyMember> 이고

em.flush(), em.clear() 안한 경우에는
Persistentbag<Member> 이고,

Persistentbag.itera() 함수 내부에서

<E> 가 ProxyMember 일 경우에 hasNext() == false 면 Query 를 진행해서 db에서 조회 해보고,
<E> 가 Member(순수한 객체) 일 경우에는 hasNext() == false 면 그냥 query없이 return 하게되는 걸까요..?

y2gcoder님의 프로필

y2gcoder

2023.10.19

6분 40초 경에서는 em.flush(), em.clear() 를 붙이고 있고, 쿼리가 그대로 보이고 있습니다. 그 이후의 8분 경에 em.flush(), em.clear()를 주석 처리하는 부분에서는 그 위쪽에 team.getMembers().add(member);(31번 라인) 를 추가한 상태입니다.
혹시 8분 경의 이 코드를 말씀하시는 것일까요?

그래서 9분 50초 경부터 em.flush(), em.clear()를 주석처리하고, team.getMembers().add(member); 라인을 주석처리한 부분에서는 select 쿼리가 나가지 않는 모습을 볼 수 있고 이에 대해서도 설명을 해주고 계십니다!

나머지 부분은 하시는 말씀이 맞습니다. PersistentBag 내부에서는 프록시를 활용해서 Lazy Loading의 동작을 관리하고 있습니다. em.flush(), em.clear() 후 team.getMembers() 에서는 초기화되지 않은 PersistentBag 를 반환합니다. 이 상태에서 반복문 순회 등을 통해 실제 컬렉션 내의 데이터에 접근하려고 할 때, 데이터베이스 쿼리가 발생하여 컬렉션을 실제 값으로 넣어주는 초기화작업을 수행하고 있습니다. :)