JPA 입문(2)

김민지·2022년 10월 25일
0

JPA

목록 보기
14/27

프록시

매번 연관된 객체들을 불러오는것보다 일단 가짜객체를 불러와두고 실제로 연관된 객체를 사용할시점에 쿼리를 날려서 불러오는게 성능상의 이점이 있다. 이때 사용하는 가짜 객체가 프록시다.
사용할시점에 쿼리를 날려서 불러오는 것을 지연로딩이라고한다.

  • 매번 연관된 객체들이 항상 사용되는것이 아니다
  • em.find는 요청한 시점에 디비에 쿼리를 날리는데 em.reference는 사용되는 시점에 쿼리를 날린다

em.reference

  • 이 메서드를 호출할때 jpa는 디비를 조회하지도 않고 실제 엔티티 객체를 생성하지도 않는다
    일단 실제 엔티티 객체를 상속받은 프록시 객체를 return한다
    그리고 이 프록시 객체는 실제 엔티티객체의 참조값을 가지고 있다
  • 초기화 : 이 객체가 진짜로 사용될때 디비를 조회해서 실제 엔티티 객체를 생성하는 과정
    즉 client가 getReference로 entity.getName을 할때의 과정은 다음과 같다
  1. 영속성컨텍스트에 초기화 요청
  2. 영속성컨텍스트에 없으면 디비에 요청해서 초기화를 하여 실제 엔티티 객체를 생성
    (디비에 연결하는 과정이 비용이 큰거기때문에 영속성컨텍스트에 있으면 그냥 그 객체를 가져오는거임 그 비용은 안큼)
  3. 프록시 객체는 이 객체의 참조값을 가지고 있고 실제 엔티티의 getName을 호출해서 결과를 반환한다

프록시의 특징

  • 프록시 객체는 처음사용할때 한번만 초기화된다
  • 프록시객체의 타입과 원본객체의 타입은 다르다
  • 초기화는 영속성 컨텍스트의 도움을 받아야하는 문제다. 영속상태가 아닌 엔티티에 대해서는 초기화가 불가능하다
  • 영속성컨텍스트에 찾는 엔티티가 있다면 디비를 조회할필요가 없어서 getReference를 호출해도 프록시가 아닌 실제 엔티티를 반환한다

프록시와 식별자

  • 프록시를 조회할때의 id를 프록시는 기억하고 있기때문에 .getid를 해도 프록시를 초기화하러 가지 않는다 단 @Access(AcessType.PROPERTY))인 경우에만 초기화하지 않고@Access(AcessType.FIELD))인 경우에는 초기화하러간다
  • 프록시는 다음 코드처럼 연관관계를 설정할때 유용하다. 연관관계를 설정할때는 식별자값만 사용하므로 디비접근횟수를 줄일 수 있다.
Member member = em.find(Member.class, 1l);
Team team = em.reference(Team.class, 2l);//sql을 실행해서 가져오지 않음
member.setTeam(team);//team의 id값만 필요하다

즉시로딩

  • find하는 순간 찾아온다. 근데 이때 쿼리를 두번날리는것이 아니라 최적화를위해 join을 한다. 그럼 한번의 쿼리로 둘다 조회할 수 있다

nullable설정에 따른 조인전략

  • @Joincolumn(nullable = true); //null허용 -> 외부조인사용
  • @JOinColumn(nullable = false); //null허용x -> 내부조인사용
  • 내부조인이 성능상으로 더 유리하다

정리

  • 매번 즉시로딩을 사용하는것도 효율적이라고 볼순 없다. 왜냐하면 team엔티티에서 member를 조회하는일이 적다면 그것이 비효율이 될테니깐. 반대로 항상 서로를 같이쓰는데 지연로딩을쓴다면 비효율적일 수 있다
  • 그렇다면 자주같이 쓰이면 즉시로딩으로 그렇지 않으면 지연로딩으로 써보는건 어떨까?

즉시로딩 주의

  1. 컬렉션을 두개이상 즉시 로딩하는것은 안됨 너무 양이 많아짐
  2. 컬렉션 즉시 로딩은 JPA에서 항상 외부조인을 사용하게 되어있음
  • 예를들어 팀과 멤버가 있을때 내부조인을 사용해버리면 팀이 존재해도 조회되지않는 그런문제가 발생할 수 있음 (근데 이건 일다다 다대일관계면 어쩔수없이 발생하는 문제아닌가? 뭔가 헷갈리는데 일단은 넘어가야겠다)

영속성 전이

  • JPA는 연관된객체를 한번만으로 모두 저장하거나 제거할 수 있다
  • FLUSH 시점에 영속성이 전이된다

값 타입

JPA의 데이터 타입을 나눠보면 1)엔티티타입 2)값타입 으로 나눌 수 있다
값 타입은 다음과 같이 나눌 수 있다
1) 기본값 타입(기본타입 + 래퍼클래스 + String)
2) 임베디드 타입(서로다른 1번의 종류들로 구성된)
ㄴ 새로운 값타입을 정의해서 사용할 수 있음
ㄴ entity에는 @Embedded를, 새로운값타입이 되는 class에는 @Embeddable을 쓴다
ㄴ 해당 타입은 기본생성자가 필수
3) 컬렉션 값 타입
@ElementCollection
@CollectionTable(name, joincolumns)

한 엔티티에 임베디드 타입 중복

  • 예를들어 회원에게 집주소도 필요하고 회사주소도 필요할때
  • @AttributeOverrides(@AttributeOverride())사용

값 타입의 쿼리

//입베디드 값 타입
		member.setHomeAddress(new Address("통영", "해수욕장", "220-22"));
		//기본값 타입 컬렉션
		member.getFavoriteFoods().add("짬뽕");
		member.getFavoriteFoods().add("짜장면");
		member.getFavoriteFoods().add("국수");
		//임베디드 값 타입 컬렉션
		member.getAddressHistory().add(new Address("서울", "강남", "20-290"));
		member.getAddressHistory().add(new Address("서울", "강북", "20-290"));
        
        //
        em.persist(member);
  • 위의 코드는 쿼리가 6번 나간다
    member insert(임베디드 값 타입은 여기에 포함돼서 insert가 된다)
    기본값 타입 컬렉션 insert 3회
    임베디드 값 타입 컬렉션 insert 2회
  • 값 타입 컬렉션도 조회할때의 fetch전략을 선택할 수 있다

값 타입의 수정

  • 값타입은 불변해야한다. 그니까 수정이라기보단 제거하고 새로 삽입하는 행위를 한다
    그러다 보니 쿼리가 많이 발생한다
Address address1 = new Address("1", "2");
		Address address2 = new Address("3", "4");
		Address address3 = new Address("12", "123");
		List<Address> addressList = new ArrayList<>();
		addressList.add(address1);
		addressList.add(address2);
		addressList.add(address3);
		Member member = new Member();
		member.setAddressList(addressList);
		em.persist(member);
		//변경
		em.flush();
		address1 = new Address("213", "!23");
		addressList.remove(0);
		addressList.add(address1);
	Hibernate: insert into member (member_id, name) values (default, ?)
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
	Hibernate: delete from address where member_id=?
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
	Hibernate: insert into address (member_id, code, zip_code) values (?, ?, ?)
    

그렇기때문에 값타입 컬렉션이 매핑된 테이블에 데이터가 많다면 값타입 컬렉션 대신에 일대다 관계를 고려해야한다

  • 그리고 값타입은 컬럼에 null을 입력할 수 없다
  • 값 타입은 반드시 equals()와 hashcode()를 적절하게 정의해주자
    JPA는 값 타입의 equals()와 hashcode() 를 이용해서 특정 값을 찾아서 삭제할 수 있다.
member.getValues().remove(3) // 이런 건 당연히 된다.
member.getValues().add(4)
 
// 이런 것도 가능하다. JPA가 equals와 hashcode 메서드를 이용해서 비교 후 삭제한다.
member.getValues().remove( new Address("old1", "street", "10000") );

그래서 값 타입을 만들 땐 equals와 hashcode를 제대로 구현하도록 하자.

  • 값타입은 불변객체로 만들어야한다

출처
https://jiwondev.tistory.com/231

profile
안녕하세요!

0개의 댓글