본문 바로가기
학습/DB

JPA 값 타입

by 코동이 2021. 9. 18.

JPA 데이터 타입 

1. 엔티티 타입(@Entity)

데이터가 변해도 식별자로 지속해서 추적 가능하다.

예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능하다.

 

2. 값타임

데이터가 변하면 식별자가 없고 값만 있으므로 변경시 추적 불가하다.

예) 숫자 00을 200으로 변경하면 완전히 다른 값으로 대체된다. 단, 추적이 불가능하다.

 

생명주기를 엔티티에 의존한다. 회원을 삭제하면 이름, 나이 필드도 함께 삭제된다. 값타입은 공유하면 안된다. 회원 이름 변경시, 다른 회원의 이름도 함께 변경되면 안되기 때문에 주의한다.

 

int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체이다. 기본타입은 변경 시, 값을 복사해서 괜찮지만, 참조로 공유하는 래퍼 클래스나, 다른 객체들은 값을 변경 시 원치않는 부분까지 변경될 수 있고, 따로 막을 방법이 없다. 따라서, 아예 변경 자체가 불가능하게 해서 부작용을 방지하도록 한다.

 

1. 기본값 타입

자바 기본 타입(int, double)

래퍼 클래스(Integer, Long)

String

 

int, double 같은 기본 타입은 절대 공유 안되고, 항상 값을 복사하므로 안전하다.

 

Integer같은 래퍼 클래스(참조 방식)나, String 같은 특수한 클래스는 공유 가능한 객체이지만 변경하면 안된다.

 

2. 임베디드 타입(embedded type, 복합 값 타입)

@Embedded : 값 타입을 사용하는 곳에 표시

@Embeddable : 값 타입을 정의하는 곳에 표시

기본 생성자는 필수이다.

 

사용자가 원하는 새로운 값 타입을 정할 수 있다. 주소, x,y 위치 좌표에 주로 사용한다. 기본값 타입을 모아서 만들어서 복합 값 타입이라고도 한다. int, String 과 같은 값 타입이다(변경시 이력 추적이 안된다)

 

 

기존에 공통적으로 사용하는 기간이나 주소를 어떻게 임베디드 값 타입으로 편리하게 만들 수 있는지 살펴본다.

 

임베디드 타입을 활용하여 엔티티 간소화하기

 

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private int age;

    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    private String city;
    private String street;
    private String zipcode;
}

Member는 기간 칼럼 2개와 주소칼럼 3개를 가지고 있다.

 

create table Member (
       id bigint not null,
        age integer not null,
        city varchar(255),
        endDate timestamp,
        name varchar(255),
        startDate timestamp,
        street varchar(255),
        zipcode varchar(255),
        primary key (id)
    )

 

기간칼럼 2개와 주소칼럼 3개를 하나로 묶고 각각 Period, Address 값 타입으로 생성한다.

@Embdded는 값 타입을 사용하는 곳에, @Embeddable은 값 타입을 정의하는 곳에 사용한다.

 

엔티티와 값타입을 구분분리

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    public Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }
}

 

@Embeddable
public class Period {
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    public Period() {}

    public Period(LocalDateTime startDate, LocalDateTime endDate) {
        this.startDate = startDate;
        this.endDate = endDate;
    }

    public LocalDateTime getStartDate() {
        return startDate;
    }

    public void setStartDate(LocalDateTime startDate) {
        this.startDate = startDate;
    }

    public LocalDateTime getEndDate() {
        return endDate;
    }

    public void setEndDate(LocalDateTime endDate) {
        this.endDate = endDate;
    }
}

 

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private int age;

    @Embedded
    private Period workPeriod;

    @Embedded
    private Address homeAddress;
}

Member 클래스가 가지고 있던 많은 칼럼들이 임베디드 값 타입으로 인해 굉장히 간단해졌다. 특징은, 임베디드 값 타입의 칼럼들은 새로운 엔티티가 아니라, 일반적인 int, String 타입처럼 기본 값 타입으로 Member 테이블에 그대로 칼럼으로 들어있다는 것이다. 

 

create table Member (
       id bigint not null,
        age integer not null,
        city varchar(255), //Address 그대로!
        endDate timestamp, //Period 그대로!
        name varchar(255),
        startDate timestamp, //Period 그대로!
        street varchar(255), //Address 그대로!
        zipcode varchar(255), //Address 그대로!
        primary key (id)
    )

 

Member member = new Member;
member.setHomeAddress(new Address("1000", "zip", "Street"));

 

따라서, 임베디드 값 타입을 Member에 setter로 추가하면, 해당 테이블 칼럼에 추가된다.

 

임베디드 값이 있는 엔티티와 테이블의 매핑

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

특징은, 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하다. (메서드를 활용한 응용이 유용하다)

 

- 같은 값 타입을 여러개 넣고 싶은 경우는 어떻게 해야 할까?

같은 값 타입이 2개 이상이면, 칼럼명이 중복되어 충돌이 날 것이다. 따라서, @AttribteOverrides를 사용한다. 특성을 Override하여 재정의한다고 이해하면 된다. name에는 기존 값 타입의 칼럼명을, column에는 내가 원하는 새로운 칼럼명을 적는다.

 

@Embedded
@AttributeOverrides({
        @AttributeOverride(name ="city", column = @Column(name = "work_city")),
        @AttributeOverride(name ="street", column = @Column(name = "work_street")),
        @AttributeOverride(name ="zipcode", column = @Column(name = "work_zipcode"))
})
private Address workAddress;

 

 Member member = new Member();
 member.setHomeAddress(new Address("1000", "zip", "Street"));
 member.setWorkAddress(new Address("work1000", "workzip", "workStreet"));
 em.persist(member);

 

workAddress 임베디드 값 타입 1개 더 추가

내가 설정한 새로운 칼럼 3개가 엔티티에 추가되었다. 임베디드 타입을 추가했으므로, 새로운 엔티티나 테이블이 만들어지는 것이 아니라, 부모 엔티티에 값 타입이 추가된다.

 

임베디드 타입의 장점

1. 재사용이 가능하며, 높은 응집도를 가진다.

2. Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있다

3. 임베디드 타입을 포함한 모든 값 타입은 값 타입을 소유한 엔티티에 생명 주기를 의존한다.

 

***임베디드 타입의 변경 유의점***

 값 타입은 복잡한 객체를 조금이라도 단순화하려고 만든 개념이므로, 안전하게 사용해야만 한다. 값 "복사"와 "참조"에 대해 잘 이해하고, "변경"에 유의해야 한다. 특히 임베디드 값 타입을 변경해야 한다면, getter로 호출 후 setter로 변경하면 안된다. 임베디드 값 객체 칼럼들을 하나하나 "복사"해서 새로운 객체를 생성해 값을 넣어주어야 한다.

 

*임베디드 값 타입을 getter, setter로 수정할 때 부작용

Address address = new Address("1000", "zip", "Street");
Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);

Member member2 = new Member();
member2.setName("member2");
member2.setHomeAddress(address);
em.persist(member2);

member1.getHomeAddress().setCity("newCity"); //여기에 주목!

tx.commit();

 

의도하지 않게 member2 변경된다.

임베디드 타입은 자바의 기본 타입이 아니라 "객체 타입"이다. 따라서, getter, setter로 값을 바꾸는 순간 해당 객체를 참조하고 있던 모든 칼럼들이 변경된다.

 

member1의 CITY만 newCity로 바꾸고 싶었는데, member2의 CITY까지도 같이 바뀌어버렸다. 임베디드 값 타입이 같은 값 타입은 여러 엔티티에서 공유하면 위험하다. 따라서, 대신 값을 "복사"해서 사용해야 한다.

 

*새로운 임베디드 값 객체를 만들어서 대입

Address address = new Address("1000", "zip", "Street");
Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);

Address newAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setName("member2");
member2.setHomeAddress(newAddress);
em.persist(member2);

member1.getHomeAddress().setCity("newCity");

 

member2의 CITY는 바뀌지 않는다!!!

이제, 새로운 객체를 생성해서 갈아끼면, 부작용은 방지할 수 있다.

 

 하지만, 누군가가 실수로 아래처럼 getter, setter를 사용한다면? 참조 값에 직접 값을 대입한다면? 사람은 누구나 실수하기 때문에, 직접 대입하는 행위 자체를 막을 수는 없는 한계가 생긴다.

member1.getHomeAddress().setCity("newCity");

 

임베디드 값 타입의 부작용을 원천 차단하고 안전하게 변경하는 방법은 무엇일까? 불변객체를 만드는 것이다. 불변 객체는 생성시점 이후 절대 값을 변경할 수 없는 객체이다. 방법은, 생성자로만 값을 설정하고 Setter를 만들지 않는다. (참고로, Integer, String은 자바가 제공하는 대표적인 불변객체이다.)

 

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    public Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }

    public String getCity() {
        return city;
    }

    //public void setCity(String city) {
    //    this.city = city;
    //}

    public String getStreet() {
        return street;
    }

    //public void setStreet(String street) {
    //    this.street = street;
    //}

    public String getZipcode() {
        return zipcode;
    }

    //public void setZipcode(String zipcode) {
    //    this.zipcode = zipcode;
    //}
}

임베디드 타입의 setter 함수를 없애버리거나, private으로 바꾸면 getter, setter를 이용한 변경을 원천 차단할 수 있다.

 

member1.getHomeAddress().setCity("newCity"); //컴파일 오류가 뜬다

임베디드 값 타입에서 setter 함수를 제거하면, getter, setter설정은 컴파일에서 오류가 나므로 완전하게 부작용을 차단한다.

 

Address address = new Address("1000", "zip", "Street");
Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);

//member1.getHomeAddress().setCity("newCity");

Address newAddress = new Address("newCity", address.getStreet(), address.getZipcode());
member1.setHomeAddress(newAddress);

이제, 같은 사람의 주소 수정마저도, setter로 하지 못하고 새로운 임베디드 타입 값 객체를 생성하고 Address 자체를 갈아끼워야만 하므로 안전하게 변경할 수 있다.

 

*값 타입의 비교

기본값 타입은  == 비교만으로도 값이 같으면 언제나 true이지만, 임베디드 타입은 다르다. 대신 equals로 비교해야 한다. 하지만, 임베디드 값 타입의 기본 equals 비교는 false가 나온다.

Address address1 = new Address("1000", "zip", "Street");
Address address2 = new Address("1000", "zip", "Street");
System.out.println("address1 == address2 : " + (address1 == address2)); // false
System.out.println("address1 == address2 : " + (address1.equals(address2))); //false

 

equals와 hashCode를 재정의한다.

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return getCity().equals(address.getCity()) 
                && getStreet().equals(address.getStreet()) 
                && getZipcode().equals(address.getZipcode());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCity(), getStreet(), getZipcode());
    }

equals 재정의를 통해 Address 임베디드 값 타입의 city, street, zipcode가 같으면, 같은 객체라는 정의와 함께 hasCode로 재정의한다.

 

Address address1 = new Address("1000", "zip", "Street");
Address address2 = new Address("1000", "zip", "Street");
System.out.println("address1 == address2 : " + (address1 == address2)); // false
System.out.println("address1 == address2 : " + (address1.equals(address2))); //true

equals, hashCode 재정의를 하고나서는 equals 비교가 true이다.

 

* 자바에서 비교의 차이

동일성(identity) : 인스턴스 참조값을 비교 , == 사용

동등성(equivalence) : 인스턴스 값을 비교, equals()

 

3. 컬렉션 값 타입(collection value type)

 컬렉션 값 타입은 Lists, Set 같은 타입으로, 값 타입을 하나 이상 저장할 때 사용한다. List, Set같은 컬렉션은 한 테이블 안에 저장할 수 있는 방법이 존재하지 않는다. 따라서, 1:N으로 풀어서 별도의 테이블을 만들어야 한다. 즉, 컬렉션 칼럼은 별도의 테이블로 분리해야 한다.

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private int age;

    @Embedded
    private Period workPeriod;

    @Embedded
    private Address homeAddress;

    @ElementCollection
    @CollectionTable(name = "favorite_food", joinColumns = @JoinColumn(name = "member_id"))
    private Set<String> favoriteFoods = new HashSet<>();

    @ElementCollection
    @CollectionTable(name = "address", joinColumns = @JoinColumn(name = "member_id"))
    private List<Address> addressHistory = new ArrayList<>();
}

 

컬렉션의 경우 모든 필드는 PK

Member를 @JoinColumn으로 지정하여 외래키를 가지며 1: N 관계처럼 생각하면 쉽다. 단, 각 컬렉션 타입의 칼럼들은 모두 PK이다. 따라서, 해당 테이블 칼럼의 전체를 비교해서 중복을 허용하지 않는다.

 

*값 타입 컬렉션 저장

값 타입 컬렉션은 크게 보면 값 타입의 일부이다. 값 타입은 무조건 자신이 속해있는 부모 객체와 생명주기를 같이하여 생성과 삭제가 부모에 의해 결정된다. 따라서, 값 타입 컬렉션을 따로 영속성 컨텍스트에 저장하지 않아도, 부모만 persist하면, 자동적으로 테이블에 값이 추가된다. 

Member member = new Member();
member.setName("member1");
member.setAge(20);

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

member.setHomeAddress(new Address("city", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));

em.persist(member);
create table Member (
       id bigint not null,
        age integer not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        name varchar(255),
        endDate timestamp,
        startDate timestamp,
        primary key (id)
    )
create table address (
       member_id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255)
    )
create table favorite_food (
       member_id bigint not null,
        favoriteFoods varchar(255)
    )

 

FAVORITE_FOOD 테이블 뿐만 아니라 ADDRESS테이블이 새로 만들어져서 헷갈릴 수 있다. 하지만, homeAddress는 임베디드 값 타입이므로 MEMBER 안에서 생성이 되고, addressHistory는 컬렉션 값 타입이므로 따로 테이블에 생성된다.

 

*값 타입 컬렉션 조회

값 타입 컬렉션은 지연 로딩 전략을 사용한다.

Member member = new Member();
member.setName("member1");
member.setAge(20);

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

member.setHomeAddress(new Address("city", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));

em.persist(member);

em.flush();
em.clear();

Member findMember = em.find(Member.class, member.getId());

 

Hibernate: 
    select
        member0_.id as id1_3_0_,
        member0_.age as age2_3_0_,
        member0_.city as city3_3_0_,
        member0_.street as street4_3_0_,
        member0_.zipcode as zipcode5_3_0_,
        member0_.name as name6_3_0_,
        member0_.TEAM_ID as TEAM_ID9_3_0_,
        member0_.endDate as endDate7_3_0_,
        member0_.startDate as startDat8_3_0_ 
    from
        Member member0_ 
    where
        member0_.id=?

지연로딩이므로 FAVORITE_FOOD 테이블과 ADDRESS 테이블의 내용은 조회되지 않는다. city, street, zipcode 칼럼은 임베디드 값 타입들이 조회된 것이다.

 

Member member = new Member();
member.setName("member1");
member.setAge(20);

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

member.setHomeAddress(new Address("city", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));

em.persist(member);

em.flush();
em.clear();

Member findMember = em.find(Member.class, member.getId());

List<Address> addressHistory = findMember.getAddressHistory();
System.out.println(addressHistory);

Set<String> favoriteFoods = findMember.getFavoriteFoods();
System.out.println(favoriteFoods);

 

Hibernate: 
    select
        addresshis0_.member_id as member_i1_0_0_,
        addresshis0_.city as city2_0_0_,
        addresshis0_.street as street3_0_0_,
        addresshis0_.zipcode as zipcode4_0_0_ 
    from
        address addresshis0_ 
    where
        addresshis0_.member_id=?
[domain.Address@8e98d5cb, domain.Address@8e98d5cb]
Hibernate: 
    select
        favoritefo0_.member_id as member_i1_2_0_,
        favoritefo0_.favoriteFoods as favorite2_2_0_ 
    from
        favorite_food favoritefo0_ 
    where
        favoritefo0_.member_id=?
[족발, 치킨, 피자]

 지연로딩이기 떄문에, 단순히 getHomeAddress()나 em.find()로 호출만해서는 DB에서 쿼리문이 나가지 않고, 칼럼을 조회하거나, 객체를 조회할 때, 비로소 DB조회 쿼리가 나간다.

 

*값 타입 컬렉션 수정

임베디드 타입이 새로운 객체를 만들어서 추가를 해주었다면, 컬렉션 타입은 컬렉션에서 값을 지우고 새롭게 추가해야 한다. 

Member member = new Member();
member.setName("member1");
member.setAge(20);

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

member.setHomeAddress(new Address("city", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));
member.getAddressHistory().add(new Address("old1", "zip", "street"));

em.persist(member);

em.flush();
em.clear();

Member findMember = em.find(Member.class, member.getId());

List<Address> addressHistory = findMember.getAddressHistory();
System.out.println(addressHistory);

Set<String> favoriteFoods = findMember.getFavoriteFoods();
System.out.println(favoriteFoods);

findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("요거트");

findMember.getAddressHistory().remove(new Address("old1", "zip", "street"));
findMember.getAddressHistory().add(new Address("newCity", "zip", "street"));

 

 

Hibernate: 
    /* delete collection domain.Member.addressHistory */ delete 
        from
            address 
        where
            member_id=?
Hibernate: 
    /* insert collection
        row domain.Member.addressHistory */ insert 
        into
            address
            (member_id, city, street, zipcode) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* insert collection
        row domain.Member.addressHistory */ insert 
        into
            address
            (member_id, city, street, zipcode) 
        values
            (?, ?, ?, ?)

addressHistory의 값을 제거하고 추가할 때, 단순히 delete와 insert를 1개씩 하는 것이 아닌, 해당 Member의 모든 행을 delete하고 다시 insert를 한다. delete문의 where 조건을 보면 member_id = ?로 해당 회원의 모든 값을 삭제한다.

 

Hibernate: 
    /* delete collection row domain.Member.favoriteFoods */ delete 
        from
            favorite_food 
        where
            member_id=? 
            and favoriteFoods=?
Hibernate: 
    /* insert collection
        row domain.Member.favoriteFoods */ insert 
        into
            favorite_food
            (member_id, favoriteFoods) 
        values
            (?, ?)

favorite_food의 경우 delete할 때 쿼리문에서 member_id = ? and favoriteFoods = ?로 해당 1개를 삭제하고, 1개를 추가한다.

 

 

정상적으로 결과가 나왔음을 알 수 있다.

 

값 타입 컬렉션의 제약사항은 값 타입은 엔티티와 다르게 식별자 개념이 없어서 값을 변경하면 추적하기 어렵다.

값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

 

값 타입 컬렉션을 매핑하는 테이블은 모든 칼럼을 묶어서 기본 키를 구성해야 한다. null입력은 안되고, 중복저장도 불가능하게 만들어야 한다. (이 부분은 위의 코드에서 JPA에서 안해주므로 사용 DB에서 따로 해준다.)

 

그렇다면, 제약사항이 많은 컬렉션 타입의 대안 방법은 무엇일까? 실무에서는 상황에 따라 값 타입 컬렉션 대신에 1:N 관계를 고려한다 1:N 관계를 위한 엔티티를 만들고 여기에 값 타입을 사용한다.(외래키 관리를 N쪽이 아닌 1쪽에서 사용하므로 update 쿼리가 나간다.) 영속성 전이, 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용한다.

 

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
List<AddressEntity> addressHistory = new ArrayList<>();

 

@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
    @Id
    @GeneratedValue
    private Long id;

    @Embedded
    private Address address;

    public AddressEntity() {}

    public AddressEntity(String city, String street, String zipcode) {
        this.address = new Address(city, street, zipcode);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

 

Member member = new Member();
member.setName("member1");
member.setAge(20);

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

member.setHomeAddress(new Address("city", "zip", "street"));
member.getAddressHistory().add(new AddressEntity("old1", "zip", "street"));
member.getAddressHistory().add(new AddressEntity("old2", "zip", "street"));

em.persist(member);

em.flush();
em.clear();

 

create table ADDRESS (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        MEMBER_ID bigint,
        primary key (id)
    )

기존 값 타입 컬렉션에서 모든칼럼이 PK이고, MEMBER_ID는 FK였지만, 1:N개선 이후에, MEMBER_ID만 FK로 가진다. 이제, ADDRESS의 모든 관리는 MEMBER에서 한다.

 

Hibernate: 
    /* insert domain.Member
        */ insert 
        into
            Member
            (age, city, street, zipcode, name, TEAM_ID, endDate, startDate, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert domain.AddressEntity
        */ insert 
        into
            ADDRESS
            (city, street, zipcode, id) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* insert domain.AddressEntity
        */ insert 
        into
            ADDRESS
            (city, street, zipcode, id) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* create one-to-many row domain.Member.addressHistory */ update
        ADDRESS 
    set
        MEMBER_ID=? 
    where
        id=?
Hibernate: 
    /* create one-to-many row domain.Member.addressHistory */ update
        ADDRESS 
    set
        MEMBER_ID=? 
    where
        id=?

 

엔티티 값타입
식별자 o 식별자 x
생명주기 관리 생명주기를 엔티티에 의존
공유가능 공유하지 않는 것이 안전

식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티이다.

 

반응형

'학습 > DB' 카테고리의 다른 글

Transaction 격리수준  (0) 2021.09.26
db Transaction(롤백처리)  (0) 2021.09.26
객체지향 쿼리 언어  (0) 2021.09.17
Oracle DML GRANT, REVOKE 사용하기  (0) 2021.09.01
Query Method  (0) 2021.08.29