kok202
영속성 컨텍스트 정리 (2)

2019. 11. 14. 13:26[정리] 기능별 개념 정리/JPA

1. Query 어노테이션을 사용하여 만든 쿼리는 반드시 SQL 문을 날린다 (같은 파라미터 일지라도)

코드

@Repository
public interface TeamRepository extends JpaRepository<TeamEntity, Long> {
    Optional<TeamEntity> findById(long teamId);
}
TeamEntity teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();

결과

Hibernate: select teamentity0_.id as id1_1_0_, teamentity0_.name as name2_1_0_ from team_entity teamentity0_ where teamentity0_.id=?

 

 

 

코드

@Repository
public interface TeamRepository extends JpaRepository<TeamEntity, Long> {
    @Query("SELECT te FROM TeamEntity te WHERE te.id = :teamId")
    Optional<TeamEntity> findById(long teamId);
}
TeamEntity teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();
teamEntity = teamRepository.findById(teamId).get();

결과

Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.id=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.id=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.id=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.id=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.id=?

@Query 가 달린 경우는 JPQL 을 직접 DB에 때려보겠다는 의미이기 때문이다.

 

 

 

 

 

2. JPA 인터페이스가 기본 만들어주는 쿼리일지라도 SQL 문은 중복해서 발생한다. (특정 칼럼이 unqiue 제약이 걸려있어도 같은 결과를 낸다.)

코드

@Repository
public interface TeamRepository extends JpaRepository<TeamEntity, Long> {
    Optional<TeamEntity> findByName(String teamName);
}
TeamEntity teamEntity = teamRepository.findByName(teamName).get();
teamEntity = teamRepository.findByName(teamName).get();
teamEntity = teamRepository.findByName(teamName).get();
teamEntity = teamRepository.findByName(teamName).get();
teamEntity = teamRepository.findByName(teamName).get();

결과

Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.name=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.name=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.name=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.name=?
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_ where teamentity0_.name=?

해석

영속성 컨텍스트가 1차 캐싱 하는건 맞는데, @Id 가 달려있는 필드가 key, Entity 가 value 로 되어있는 맵핑 테이블을 통해 캐싱하기 때문.

 

 

 

 

 

 

3. findAll 메소드도 중복해서 발생한다.

코드

@Repository
public interface TeamRepository extends JpaRepository<TeamEntity, Long> {
    Optional<TeamEntity> findByName(String teamName);
}
List<TeamEntity> teamEntities = teamRepository.findAll();
teamEntities = teamRepository.findAll();
teamEntities = teamRepository.findAll();
teamEntities = teamRepository.findAll();
teamEntities = teamRepository.findAll();

결과

Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_
Hibernate: select teamentity0_.id as id1_1_, teamentity0_.name as name2_1_ from team_entity teamentity0_

 

 

 

 

 

4. 트랙잭션이 없어도 하위 컴포넌트에서 findById 쿼리를 사용하는 경우가 있다면 중복을 알아서 제거해준다.

코드

@Service
public class TeamService {
    @Autowired
    private TeamRepository teamRepository;

    @Autowired
    private MemberRepository memberRepository;

    public Team findById(long teamId){
        TeamEntity teamEntity = teamRepository.findById(teamId).get();
        List<MemberEntity> memberEntities = memberRepository.findByTeamId(teamId);
        return EntityConverter.convert(teamEntity, Team.class);
    }
}
teamService.findById(team.getId());
teamService.findById(team.getId());
teamService.findById(team.getId());
teamService.findById(team.getId());
teamService.findById(team.getId());

결과

Hibernate: select teamentity0_.id as id1_1_0_, teamentity0_.name as name2_1_0_ from team_entity teamentity0_ where teamentity0_.id=?
Hibernate: select memberenti0_.id as id1_0_, memberenti0_.name as name2_0_, memberenti0_.team_id as team_id3_0_ from member_entity memberenti0_ left outer join team_entity teamentity1_ on memberenti0_.team_id=teamentity1_.id where teamentity1_.id=?
TeamService findById done
Hibernate: select memberenti0_.id as id1_0_, memberenti0_.name as name2_0_, memberenti0_.team_id as team_id3_0_ from member_entity memberenti0_ left outer join team_entity teamentity1_ on memberenti0_.team_id=teamentity1_.id where teamentity1_.id=?
TeamService findById done
Hibernate: select memberenti0_.id as id1_0_, memberenti0_.name as name2_0_, memberenti0_.team_id as team_id3_0_ from member_entity memberenti0_ left outer join team_entity teamentity1_ on memberenti0_.team_id=teamentity1_.id where teamentity1_.id=?
TeamService findById done
Hibernate: select memberenti0_.id as id1_0_, memberenti0_.name as name2_0_, memberenti0_.team_id as team_id3_0_ from member_entity memberenti0_ left outer join team_entity teamentity1_ on memberenti0_.team_id=teamentity1_.id where teamentity1_.id=?
TeamService findById done
Hibernate: select memberenti0_.id as id1_0_, memberenti0_.name as name2_0_, memberenti0_.team_id as team_id3_0_ from member_entity memberenti0_ left outer join team_entity teamentity1_ on memberenti0_.team_id=teamentity1_.id where teamentity1_.id=?
TeamService findById done

 

 

 

 

 

영속성 컨텍스트 

강의 : https://www.youtube.com/watch?v=xqEVS8LzxZM

앤티티 매니저

- 스프링에서 앤티티 매니저 팩토리가 사용자의 요청당 엔티티 매니저를 하나 할당 해준다.

- 엔티티 매니터를 통해서 영속성 컨텍스트에 접근 할 수 있다.

 

J2SE, 생 자바 환경: 앤티티 매니저와 영속성 컨텍스트의 관계는 1:1

J2EE, 스프링 환경 : 앤티티 매니저와 영속성 컨텍스트의 관계는 N:1

 

em.persist 하면 저장 되는게 아니다.

em.persist 하면 POJO 데이터를 영속성 컨텍스트에 저장하는 것일 뿐이다.

em.persist 하면 영속성 컨텍스트안에 있는 '쓰기 지연 SQL 저장소'에 SQL 문을 만들어서 저장해둔다.

transaction.commit() 을 해야 DB 에 쿼리가 날라간다.

 

JPA 의 전제 : JPA 의 데이터의 변경은 무조건  트랜잭션 안에서 해야한다.

영속성 컨텍스트가 1차 캐싱 하는건 맞는데, @Id 가 달려있는 필드가 key, Entity 가 value 로 되어있는 맵핑 테이블을 통해 캐싱하기 때문.

 

영속성 컨텍스트를 플러시하는 방법

1. 직접 호출 : em.flush() 

2. 자동 호출 : transaction.commit()

3. 자동 호출 : JPQL 호출

 

플러시 동작

1. 더티 체킹

2. 쓰기 지연 SQL 저장소에 Update 쿼리 등록

3. 쓰기 지연 SQL 저장소에 있는 쿼리를 DB에 호출

*) 영속성 컨텍스트를 비우는 동작은 아니다.

 

주의 : JPQL 이 호출 되면 영속성 컨텍스트가 플러시가 된다.

왜냐하면 JPQL 은 DB 에 직접 SQL 을 하는 것이기 때문에 영속성 컨텍스트를 DB 에 반영시켜야 데이터 정합성이 깨지지 않기 때문 

@Query 가 달린 경우도 JPQL 이다.

 

스프링 프레임워크에서 영속성 컨텍스트는 어떻게 동작하는가??

트랜잭션 범위에 영속성 컨텍스트를 할당한다.

- @Transactional 이 생성된다 = 영속성 컨텍스트가 생성된다.

- @Transactional 이 종료된다 = 영속성 컨텍스트를 날린다.

- @Transactional 이 같다 = 항상 같은 영속성 컨텍스트를 참조한다.

* 트랜잭션 전파 : 현재 Transaction 에서 다른 Transaction 메소드를 호출할 때 발생하는 것. 최초에 호출된 트랜잭션 이름으로 이후 호출되는 트랜잭션에 전파되어진다. 즉 후에 호출하는 트랜잭션 이름과 처음 호출된 트랜잭션의 이름은 같다.

* 트랜잭션의 최대 약점 : 트랜잭션이 끝나면 준영속 상태로 바뀌어 버린다. -> 지연 로딩이 안된다.

* 트랜잭션이 롤백될 경우 DB 의 데이터는 롤백되지만 객체는 롤백이 되지 않을 수 있으므로 조심한다.

 

 

 

 

 

'[정리] 기능별 개념 정리 > JPA' 카테고리의 다른 글

Persist vs Merge  (0) 2020.02.04
JPA : Query 발생 시점  (0) 2019.06.17
JPA : Embeddable  (0) 2019.06.17
@EntityGraph : FetchType.EAGER 일 경우 Select 쿼리 줄이기  (1) 2019.06.15
JPA fetch 맵핑별 기본값  (0) 2019.06.14