프록시와 연관관계

이동건 (불꽃냥펀치)·2025년 3월 9일

프록시

프록시 기초

  • em,getReference(Member.class,memberId): 데이터베이스 조회를 미루는 가짜 엔터티 객체 조회

  • 특징

    • 실제 클래스를 상속 받아서 만들어짐
    • 실제 클래스와 모습이 똑같음
    • 이론상 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨
    • 프록시 객체는 진짜 객체의 참조값을 보관
    • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드 호출
    • 프록시 객체는 처음 사용할 때 한번만 초기화
    • 초기화할때 프록시 객체가 실제 엔터티로 바뀌는 것은 아님, 초기화 되면 프록시 객체를 통해 실제 엔터티에 접근 가능
    • 프록시 객체는 원본 엔터티를 상속 받음, 타입 체크시 == 비교가 아닌 instance of로 해야함
    • 영속성 컨텍스트에 찾는 엔터티가 이미 존재하면 em.getReference()를 호출해도 실제 엔터티 반환



지연/즉시 로딩

지연로딩 LAZY

 @ManyToOne(fetch = FetchType.LAZY) // 이 방법은 프록시객체로 조회히한다
    @JoinColumn(name = "TEAM_ID")
    private Team team;
  • Team 객체를 LAZY 전략으로 조회
select
        m1_0.id,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.EMP_START,
        m1_0.WORK_STREET,
        m1_0.WORK_ZIPCODE,
        m1_0.TEAM_ID,
        m1_0.username 
    from
        Member m1_0 
    where
        m1_0.id=?
  • Member 객체만 조회하면 Member 테이블만 조회함
  • 지연로딩을 사용해서 Team객체를 프록시로 조회함
Member member3= em.find(Member.class, member.getId());
Team team2= member3.getTeam();
System.out.println(team2.getName());

select
        t1_0.TEAM_ID,
        t1_0.name 
    from
        Team t1_0 
    where
        t1_0.TEAM_ID=?
  • Team의 메서드를 호출하니 그때서야 Team 테이블에서 값 조회
  • MemberTeam을 함께 자주 사용하면 즉시 로딩 사용
 @ManyToOne(fetch = FetchType.EAGER) 
 @JoinColumn(name = "TEAM_ID")
 private Team team;
  • 조인 쿼리문이 나가며 동시에 조회됨
    select
        m1_0.id,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.EMP_START,
        m1_0.WORK_STREET,
        m1_0.WORK_ZIPCODE,
        t1_0.TEAM_ID,
        t1_0.name,
        m1_0.username 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.TEAM_ID 
    where
        m1_0.id=?
  • 하지만 가급적이면 지연로딩을 사용하는 것이 좋다
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
  • @ManyToOne,@OneToMany 는 기본이 즉시 로딩이므로 LAZY로 변경해야함



영속성 전이 : CASCADE

  • 특정 엔터테를 영속 상태로 만들 때 연관된 엔터티도 함꼐 영속상태로 만들고 싶을 때 사용
  • 부모엔터티를 저장할 때 자식 엔터티도 함께 저장하는 예시가 있다
 @OneToMany(mappedBy = "parent",cascade = CascadeType.PERSIST,orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();

    public void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }

부모만 저장함에도 자식도 persist 됨

  Parent parent = new Parent();
  parent.setName("Jack");
 Child child = new Child();
 child.setName("Jackson");
 parent.addChild(child);
em.persist(parent);
  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다
  • 엔터티를 영속화 할떄 연관된 엔터티도 함께 영속화 하는 편리함을 제공할 뿐
  • 영속성 전이 종류
    • ALL: 모두 적용
    • PERSIST: 영속
    • REMOVE: 삭제
    • MERGE: 병합
    • REFRESH
    • DETACH



고아 객체

  • 고아 객체 제거: 부모 엔터티와 연관관계가 끊어진 자식엔터티를 자동으로 삭제
  • orphanRemoval=true
 em.persist(parent);
 Parent find= em.find(Parent.class, parent.getId());
find.getChildList().removeAll(parent.getChildList());

발생 쿼리

Hibernate: 
    /* delete for hellojpa.Child */delete 
    from
        Child 
    where
        id=?
  • 자식객체 삭제됨

주의

  • 참조가 제거된 엔터티는 다른 곳에서 참조하지않는 고아객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일때 사용해야함
  • 특정 엔터티가 개인 소유일때 사용
  • @OneToMany,@OneToOne 만 가능



값 타입

기본값 타입

  • 엔터티 타입
    • @Entity로 정의하는 객체
    • 데이터가 변해도 식별자로 지속해서 추적 가능
  • 값 타입
    • int,Integer,String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
    • 식별자가 없고 값만 있어 변경시 추적 불가

값 타입 분류

  • 기본값 타입
    • 자바 기본 타입
    • 래퍼 클래스
    • String
  • 임베디드 타입(복합값 타입)
  • 컬렉션 값 타입

임베디드 타입

  • 새로운 값 타입을 직접 정의 가능
  • JPA는 임베디드 타입이라함
  • 주로 기본값 타입을 모아서 만들어서 복합값 타입이라고도 함
  • int, String과 같은 값 타입

  • 사용법
    • @Embeddable: 값 타입을 정의하는 곳에 표시
    • @Embedded: 값 타입을 사용하는 곳에 표시
    • 기본 생성자 필수

-장점

  • 재사용
  • 높은 응집도
  • Period.isWork()처럼 해당 값 타입만 사용하는 의미있는 메서드를 만들 수 있음
  • 임베디드 타입을 포함한 모든 값 타임은 값타입을 소유한 엔터티의 생명 주기에 의존

  • 임베디드 타입은 엔터티의 값일 뿐이다
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같음



@AttributeOverride : 속성 재정의

  • 한 엔터티에서 값은 값 타입을 사용하면 컬럼 명이 중복됨
  • @AttributeOverrides,@AttributeOverride를 사용해서 컬럼명 속성을 재정의
@Embeddable
@Setter
@Getter
@NoArgsConstructor
public class Address {
    private String city;
    private String street;
    private String zipcode;
}


@Embedded
@AttributeOverrides({
        @AttributeOverride(name = "city", column = @Column(name = "EMP_START")),
        @AttributeOverride(name = "street", column = @Column(name = "WORK_STREET")),
        @AttributeOverride(name = "zipcode", column = @Column(name = "WORK_ZIPCODE"))
})
private Address homeAddress;
  • 이런 값타입의 문제는 바로 객체 타입이라는 것이다
  • 이로 인해 언제든 누군가가 이 객체 타입을 수정할 수 있어 개발자들은 항상 알수없는 위협에 노출되어있다
  • 이 문제를 해결하기 위해서는 setter를 제거해야한다
  • 우리는 값 타입을 불변 타입으로 만들기 위해 노력해야함
  • Embeded로 받은 것은 객체이므로 equals로 동등성을 알아내야함

주의

  • 값 타입은 정말 값 타입이라 판단될 때만 사용
  • 엔터티와 값 타입을 혼동해서 엔터티를 값 타입으로 만들면 안됨
  • 식별자가 필요하고 지속해서 값을 추적/변경해야 한다면 그것은 값 타입이 아닌 엔터티로 만들어야한다
profile
자바를 사랑합니다

0개의 댓글