01 Fetch Join, Join의 차이점 및 일반조인으로 n+1문제를 해결하지 못하는 이유
02 JPQL과 영속성 컨텍스트 자세히 알기
03 Spring Jpa에서 더티 체킹이란?
[1] 일반 Join
[2] Fetch Join
Team team1 = new Team();
team1.setName("Team1");
em.persist(team1);
Team team2 = new Team();
team2.setName("Team2");
em.persist(team2);
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
member1.changeTeam(team1);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
member2.changeTeam(team1);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("member3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
member3.changeTeam(team1);
em.persist(member3);
Member member4 = new Member();
member4.setUsername("회원4");
member4.setAge(20);
member4.setMemberType(MemberType.ADMIN);
member4.changeTeam(team2);
em.persist(member4);
Member member5 = new Member();
member5.setUsername("회원5");
member5.setAge(20);
member5.setMemberType(MemberType.ADMIN);
member5.changeTeam(team2);
em.persist(member5);
String query = "select distinct t From Team t join t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();
tx.commit();
Hibernate:
/* select
distinct t
From
Team t
join
t.members */ select
distinct team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
Team의 칼럼인 id와 name만을 가져오는 모습이 확인된다.
// TeamService.java
@Transactional
public List<Team> findAllWithMemberUsingJoin(){
return teamRepository.findAllWithMemberUsingJoin();
}
// FetchJoinApplicationTests.java
@BeforeEach
public void init(){
teamService.initialize();
}
@Test
public void joinTest() {
List<Team> memberUsingJoin = teamService.findAllWithMemberUsingJoin();
System.out.println(memberUsingJoin);
}
@Query
를 이용해 Repository에서 일반 join을 만들고 Test코드에서 List를 출력해보면 LazyInitializationException
이 발생LazyInitializationException
발생Team team1 = new Team();
team1.setName("Team1");
em.persist(team1);
Team team2 = new Team();
team2.setName("Team2");
em.persist(team2);
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
member1.changeTeam(team1);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
member2.changeTeam(team1);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("member3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
member3.changeTeam(team1);
em.persist(member3);
Member member4 = new Member();
member4.setUsername("회원4");
member4.setAge(20);
member4.setMemberType(MemberType.ADMIN);
member4.changeTeam(team2);
em.persist(member4);
Member member5 = new Member();
member5.setUsername("회원5");
member5.setAge(20);
member5.setMemberType(MemberType.ADMIN);
member5.changeTeam(team2);
em.persist(member5);
String query = "select distinct t From Team t join fetch t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();
tx.commit();
Hibernate:
/* select
distinct t
From
Team t
join
fetch t.members */ select
distinct team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as TEAM_ID5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as TEAM_ID5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
// TeamService.java
@Transactional
public List<Team> findAllWithMemberUsingFetchJoin(){
return teamRepository.findAllWithMemberUsingFetchJoin();
}
//FetchJoinApplicationTests.java
@Test
public void fetchJoinTest() {
List<Team> memberUsingFetchJoin = teamService.findAllWithMemberUsingFetchJoin();
System.out.println(memberUsingFetchJoin);
}
[실행결과]
[
Team(
id=1,
name=team1,
members=[
Member(
id=1,
name=team1member1,
age=1
),
Member(
id=2,
name=team2member2,
age=2
),
Member(
id=3,
name=team3member3,
age=3
)
]
),
Team(
id=2,
name=team2,
members=[
Member(
id=4,
name=team2member4,
age=4
),
Member(
id=5,
name=team2member5,
age=5
)
]
)
]
team2member4 라는 이름을 가지는 member가 속해있는 Team조회
라는 요구사항의 경우에는 일반 조인을 이용해 Team만을 조회해오면 됨을 알 수 있음user 4개를 persist를 한 후, JPQL를 활용해 user4의 이름을 변경해보고 DB와 영속성 컨텍스트의 내용과 비교해보는 내용
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("member3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
em.persist(member3);
Member member4 = new Member();
member4.setUsername("회원4");
member4.setAge(20);
member4.setMemberType(MemberType.ADMIN);
em.persist(member4);
int updateNUM = em.createQuery(
"UPDATE Member m SET m.username= 'songhee' where m.username = :name")
.setParameter("name", "회원4")
.executeUpdate();
Member findMember = em.find(Member.class, member4.getId());
System.out.println("findMember name = " + findMember.getUsername());
tx.commit();
출력을 해보니 회원4로 그대로 였고, 강의에서는 영속성 컨텍스트에 반영이 되지 않았다고 하였으나 궁금하여 이 내용을 더 찾아보았다.
em.clear()
가 필수적이다.int updateNUM = em.createQuery(
"UPDATE Member m SET m.username= 'songhee' where m.username = :name")
.setParameter("name", "회원4")
.executeUpdate();
em.clear();
Member findMember = em.find(Member.class, member4.getId());
System.out.println("findMember name = " + findMember.getUsername());
tx.commit();
이렇게 되면 변경사항이 적용됨을 알 수 있었다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); //트랜잭션 시작
try{
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
em.persist(member1); //Identity전략 중 AUTO는 유일하게 persist하는 시점에 insert쿼리가 나간다
member1.setAge(10); //엔티티만 변경
tx.commit(); //트랜잭션 커밋
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
이러한 코드가 있을 때, 별도로 DB에 update하는 코드가 없는 것을 확인할 수 있다.
Hibernate:
/* update
hellojpa.Member */
update
Member
set
age=?,
TEAM_ID=?,
type=?,
username=?
where
id=?
직접적인 upate 코드가 없어도 update 쿼리가 실행됨을 알 수 있는데, 이유는 Dirty checking 때문이다.
*이때, 변화가 있다의 기준은 최초 조회 상태를 기준으로,
jpa에서는 엔티티 조회 당시 엔티티 조회 상태를 그대로 스냅샷
으로 만들어 놓고
트랜잭션이 끝나는 시점에 스냅샷과 비교해 차이가 있다면 UPDATE 쿼리를 실행한다.
*이러한 Dirty checking은 jpa에서 관리하는 영속성 컨텍스트의 대상만 적용되며, 아래는 해당되지 않는다.
이 둘은 값을 변경해도 Dirty checking되지 않아서 자동으로 DB에 값이 반영되지 x
[Spring Data JPA와 @Transactional을 함께 사용하는 경우]
@Slf4j
@RequiredArgsConstructor
@Service
public class Service {
private final SendRepository sendRepository;
@Transactional
public void update(Long id, String sendNo) {
Send send = sendRepository.getOne(id);
send.changeSendNo(sendNo);
}
}
Hibernate:
/* update
hellojpa.Member */
update
Send s
set
time=?,
Order_ID=?,
type=?,
username=?
where
id=?
UPDATE 쿼리가 실행
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); //트랜잭션 시작
try{
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
em.persist(member1); //Identity전략 중 AUTO는 유일하게 persist하는 시점에 insert쿼리가 나간다
member1.setAge(10); //엔티티만 변경
tx.commit(); //트랜잭션 커밋
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
이 예제에서 Member 엔티티 클래스에 @DynamicUpdate를 붙여주면
Hibernate:
/* update
hellojpa.Member */
update
Member
set
age=?
where
id=?
로 변경부분만 update가 나감을 알 수 있다.