✅ @Embeddable ✅ @Embedded ✅ @AttributeOverride ✅ a.equals(b)
@Entity로 정의하는 객체
int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
1) 자바 기본 타입(int, double)
2) 래퍼 클래스(Integer, Long)
3) String
👉 따라서 우리는 java에서 side effect를 고려하지 않고 편하게 사용할 수 있었던 것!
(embedded type, 복합 값 타입)
1) 컬렉션 값 타입 (collection value type)
새로운 값 타입을 직접 정의할 수 있음
다음에서 비슷한 것끼리 모아 새로운 값 타입을 정의해보자.
@Embeddable: 값 타입을 정의하는 곳에 표시
@Embedded : 값 타입을 사용하는 곳에 표시
(기본 생성자 필수)
임베디드 타입을 사용하기 전과 후에 db상의 매핑하는 테이블은 같다.
하지만 객체는 데이터 뿐만 아니라 메서드(기능)까지 가지고 있기 때문에
임베디드 타입으로 묶어주면 재사용등의 장점들이 생기는 것!
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// 기간 preiod
private LocalDateTime startDate;
private LocalDateTime endDate;
// 주소
private String city;
private String street;
private String zipcode;
}
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// 기간 preiod
@Embedded
private Period workPeriod;
// 주소
@Embedded
private Address homeAddress;
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
}
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
한 엔티티에서 같은 값 타입(ex : workAddress, homeAddress)을 사용하면
컬럼 명이 중복되므로 에러가 난다.
이때, 여러개 일 때 @AttributeOverrides, 하나 일 때 @AttributeOverride를 사용해서 컬러 명 속성을 재정의 해줘야 한다.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// 기간 preiod
@Embedded
private Period workPeriod;
// 주소
@Embedded
private Address homeAddress;
@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;
}
create table Member (
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255),
USERNAME varchar(255),
WORK_CITY varchar(255), // 1
WORK_STREET varchar(255), // 2
WORK_ZIPCODE varchar(255), // 3
endDate timestamp,
startDate timestamp,
TEAM_ID bigint,
primary key (MEMBER_ID)
)
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 부작용(side effect)가 발생한다.
따라서 값을 복사해서 사용해야 한다.
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
em.persist(member2);
member1.getHomeAddress().setCity("newCity");
👉 member1의 city 뿐만아니라 member2의 city도 "newCity" 변경되는 문제가 발생함
Address address = new Address("city", "street", "10000");
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
em.persist(member1);
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);
member1.getHomeAddress().setCity("newCity");
👉 member1의 city만 변경됨
위처럼 항상 값을 복사하여 사용하면 공유 참조 문제를 해결할 수 있지만,
객체 타입(임베디드 타입 등)은 기본 타입(int 등)과 다르게 값참조를 전달하기 때문에
특정 팀원이 값을 복사해서 사용하지 않고 기본 타입처럼 사용한다면 문제가 생긴다.
// 값을 복사하여 사용 -> member2 값만 변경 -> 문제 없음
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
// 기본 타입처럼 사용 -> member1, member2 값 모두 변경 -> 문제 발생
Address copyAddress = address;
따라서 객체 타입을 수정할 수 없게 불변 객체를 만들어 부작용을 차단할 수 있다.
생성 시점 이후 절대 값을 변경할 수 없는 객체
생성자로만 값을 설정하고, 수정자(Setter)은 만들지 않으면 된다.
참고로 Integer, String은 자바가 제공하는 대표적인 불변 객체
Setter가 없으니 특정 값만 수정하는 것이 불가능해지므로 객체 자체를 다시 새로 만들어 넣어줘야 한다.
member.getAddress.setCity("") : X
member.setAddress(new Address("","","")) : O
값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 한다.
인스턴스의 참조 값을 비교, == 사용
인스턴스의 값을 비교, equals() 사용
e.printStackTrace();
추가하여 에러 발생시 에러코드 출력하게 함
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
tx.commit();
} catch (Exception e){
e.printStackTrace();
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
no default constructor for entity: : hellojpa.address
address에 기본 생성자를 추가하여 해결
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
}