Sunday Study

Hibernate Jpa 2

Hibernate

목차

영속성

엔티티

엔티티 생명주기

Untitled

영속성 컨텍스트

EntityManager 인스턴스는 영속성 컨텍스트와 연결됩니다. 영속성 컨텍스트는 영구 엔터티 ID에 대해 고유한 엔터티 인스턴스가 있는 엔터티 인스턴스 집합입니다. 영속성 컨텍스트 내에서 엔터티 인스턴스와 해당 생명 주기가 관리됩니다. EntityManager API는 영구 엔터티 인스턴스를 생성 및 제거하고 기본 키로 엔터티를 찾고 엔티티를 쿼리하는 데 사용됩니다.

  • 영속성 컨텍스트는 영속성이 부여된 엔티티의 생명 주기를 관리하는 엔티티 인스턴스들의 집합
  • 영속성 컨텍스트는 엔티티를 식별자(@Id)로 구분

영속성 컨텍스트의 생명주기 → 트랜잭션

Lazy Loding(지연 로딩)

Hibernate에서는 데이터 베이스의 데이터를 로드할 때 일부분만 로드하도록 제공합니다.

  • 지연로딩은 어떻게 가능한걸까??

정답은 ‘프록시’

프록시

프록시 초기화

도식화

Untitled

code

Member

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    Team team;
}

Team

@Entity
public class Team {
    @OneToMany(fetch = FetchType.EAGER || LAZY,mappedBy = "team")
    List<Member> members = new LinkedList<>();
}

Test

Optional<Member> tmp = memberRepository.findById(1l);

즉시 로딩

Untitled

지연 로딩

Untitled

Untitled

식별자

AbstractLazyInitalizer

@Override
	public final Serializable getIdentifier() {
		if ( isUninitialized() && isInitializeProxyWhenAccessingIdentifier() ) {
			initialize();
		}
		return id;
	}

Id 식별자 접근시 프록시를 초기화 하는 옵션을 켰을 때 true가 되는 조건문 기본적으로 false hibernate.jpa.compliance.proxy

equals() 난제

		@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Team team = (Team) o;

        return Objects.equals(id, team.id);
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
Team t1 = member.get().getTeam();
Team t2 = member.get().getTeam();
System.out.println(t1.equals(t2)); // false

https://tecoble.techcourse.co.kr/post/2022-10-17-jpa-hibernate-proxy/

1차 캐시

Untitled

동작 과정

  1. 식별자(PK)를 기준으로 1차 캐시에 데이터가 있는지 확인 , 있으면 데이터를 가져온다.
  2. 1차 캐시에 데이터가 없으면 데이터 베이스에서 데이터를 요청
  3. 데이터 베이스에서 가져온 데이터를 1차 캐시에 저장

Example

		@Test
    public void test2()
    {
        Optional<Member> member = memberRepository.findById(1l);
        Optional<Member> member2 = memberRepository.findById(1l);
    }

1차 캐시와 식별자

Example

		@Test
    @Transactional
    public void test2()
    {
        Optional<Member> member = memberRepository.findByName("진주원");
        Optional<Member> member2 = memberRepository.findById(1l);
    }

DefaultLoadEventListener

private Object doLoad(
			final LoadEvent event,
			final EntityPersister persister,
			final EntityKey keyToLoad,
			final LoadEventListener.LoadType options) {

		final EventSource session = event.getSession();
		final boolean traceEnabled = LOG.isTraceEnabled();
		if ( traceEnabled ) {
			LOG.tracev(
					"Attempting to resolve: {0}",
					MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
			);
		}

		CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE.loadFromSessionCache(
				event,
				keyToLoad,
				options
		); // 1차 캐시로부터 확인
		Object entity = persistenceContextEntry.getEntity();

		if ( entity != null ) {
			return persistenceContextEntry.isManaged() ? entity : null;
		}
		// 2차 캐시로부터 확인
		entity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( event, persister, keyToLoad );
		if ( entity != null ) {
			if ( traceEnabled ) {
				LOG.tracev(
						"Resolved object in second-level cache: {0}",
						MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
				);
			}
		}
		else {
			if ( traceEnabled ) {
				LOG.tracev(
						"Object not resolved in any cache: {0}",
						MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
				);
			}
			entity = loadFromDatasource( event, persister ); // 데이터베이스로부터 조회
		}

		if ( entity != null && persister.hasNaturalIdentifier() ) {
			final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
			final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
			naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
					persister,
					event.getEntityId(),
					naturalIdHelper.extractNaturalIdValues(
							entity,
							persister
					)
			);
		}

		return entity;
	}

쓰기 지연

Untitled

변경 감지(dirty checking)

JPQL

https://www.youtube.com/watch?v=kJexMyaeHDs&t=854s

Code

식별자를 이용한 다중 조회

List<Person> samePersons = session
		.byMultipleIds( Person.class )
		.enableSessionCheck( true )
		.multiLoad( 1L, 2L, 3L );

Filtering entities and association

@Where

@Where(clause = "account_type = 'DEBIT'")
@OneToMany(mappedBy = "client")
private List<Account> debitAccounts = new ArrayList<>();

@Where(clause = "account_type = 'CREDIT'")
@OneToMany(mappedBy = "client")
private List<Account> creditAccounts = new ArrayList<>();

@Filter

Refresh

준영속

준영속 → 영속

Untitled