임베디드 타입, 컬렉션 값 타입은 jpa에서 정의해서 써야 함.
Integer : 값을 복사하는게 아니라 참조가 넘어가므로 공유가능한 객체이다
ex)
Integer a = new Integer(10);
Integer b = a;
Member.java
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//기간 Period
private LocalDateTime startDate;
private LocalDateTime endDate;
//주소
private String city;
private String street;
private String zipcode;
}
기간과 주소를 따로 빼보자
Member.java
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//기간 Period
@Embedded
private Period workPeriod;
//주소
@Embedded
private Address homeAddress;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Period getWorkPeriod() {
return workPeriod;
}
public void setWorkPeriod(Period workPeriod) {
this.workPeriod = workPeriod;
}
public Address getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(Address homeAddress) {
this.homeAddress = homeAddress;
}
}
Address.java
@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;
}
}
Period.java
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime 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;
}
}
한 엔티티에서 같은 값 타입을 사용하면?
하나면 @AttributeOverride, 여러개 속성이면 @AttributeOverrides 사용
Member.java
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//기간 Period
@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;
JpaMain.java
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 {
Address address = new Address("city", "streer", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
em.persist(member2);
member.getHomeAddress().setCity("newCity");
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
두 멤버의 city가 모두 newCity로 바뀌었다.
값을 복사해서 넣어보자
JpaMain.java
try {
Address address = new Address("city", "streer", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);
member.getHomeAddress().setCity("newCity"); //1번째에만 영향을 줌
tx.commit();
}
1번째 member에만 영향을 미치는 것을 확인 가능 .
: a와 b 둘 다 같은 address 인스턴스를 바라보고 있음.
객체 타입을 수정할 수 없게 만들면 된다.
생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨
or setter 를 private으로 만들면 됨.
그럼 값을 변경할려면 어떻게 하지?
완전히 다시 세팅한다. 값을 통으로 다 바꿔야한다.
JpaMain.java
try {
Address address = new Address("city", "streer", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Address newAddress = new Address("WowCity", address.getStreet(), address.getZipcode());
member.setHomeAddress(newAddress);
tx.commit();
}
객체 타입은 == 비교하면 false가 나옴
ValueMain.java
public class ValueMain {
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println("a == b : " + (a == b));
Address address1 = new Address("city", "streer", "10000");
Address address2 = new Address("city", "streer", "10000");
System.out.println("address1 == address2 : "+(address1 == address2));
}
}
constructor 로 equals() 메소드를 생성한다.
Address.java
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(city, address.city) && Objects.equals(street, address.street) && Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode() {
return Objects.hash(city, street, zipcode);
}
equals() 구현하면 hashCode()도 그에 맞춰서 구현해야한다.
ValueMain.java
public class ValueMain {
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println("a == b : " + (a == b));
Address address1 = new Address("city", "streer", "10000");
Address address2 = new Address("city", "streer", "10000");
System.out.println("address1 equals address2 : "+(address1.equals(address2)));
}
}
객체의 값을 비교할 때는 equals를 쓰자
값 타입은 값 속성 들을 모아서 pk로 지정한다.
Member.java
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//주소
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns =
@JoinColumn(name = "MEMBER_ID")) //member_id를 외래키로 잡게됨
@Column(name = "FOOD_NAME") //이렇게 속성 값이 하나면 컬럼명 지정 가능
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns =
@JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
값 타입 컬렉션도 지연 로딩 전략 사용
JpaMain.java
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 {
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity","street","1000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1","street","1000"));
member.getAddressHistory().add(new Address("old2","street","1000"));
em.persist(member);
em.flush();
em.clear(); //db에 넣고, Db에는 데이터가 있는 상태에서 깔끔한 상태로 조회 가능
System.out.println("=======================");
Member findMember = em.find(Member.class, member.getId());
List<Address> addressHistory = findMember.getAddressHistory();
for (Address address : addressHistory) {
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood);
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
=====표시선 아래로 addressHistory와 favoriteFood가 조회됨을 확인할 수 있다.
Address 인스턴스 자체를 갈아끼워야한다.
JpaMain.java
try {
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity","street","1000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1","street","1000"));
member.getAddressHistory().add(new Address("old2","street","1000"));
em.persist(member);
em.flush();
em.clear(); //db에 넣고, Db에는 데이터가 있는 상태에서 깔끔한 상태로 조회 가능
System.out.println("=======================");
Member findMember = em.find(Member.class, member.getId());
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity",a.getStreet(), a.getZipcode()));
tx.commit();
}
select 쿼리 이후에 update 쿼리가 나감.
String은 값 타입이다. 통째로 갈아 끼워야한다. update 자체가 없음
//치킨 -> 한식
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
이제 주소를 변경해보자
findMember.getAddressHistory().remove(new Address("old1", "street", "10000")); //이렇게 해줘야 Object를 정확하게 찾아서 지워줄 수 있음
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
delete 쿼리 1개, insert쿼리는 2개 나갔다. 왜일까?
->값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장하기 때문!
JpaMain.java
try {
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity","street","1000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new AddressEntity("old1","street","10000"));
member.getAddressHistory().add(new AddressEntity("old2","street","10000"));
em.persist(member);
em.flush();
em.clear(); //db에 넣고, Db에는 데이터가 있는 상태에서 깔끔한 상태로 조회 가능
System.out.println("=======================");
Member findMember = em.find(Member.class, member.getId());
// Address a = findMember.getHomeAddress();
// findMember.setHomeAddress(new Address("newCity",a.getStreet(), a.getZipcode()));
//치킨 -> 한식
// findMember.getFavoriteFoods().remove("치킨");
// findMember.getFavoriteFoods().add("한식");
//주소 변경
// findMember.getAddressHistory().remove(new AddressEntity("old1", "street", "10000")); //이렇게 해줘야 Object를 정확하게 찾아서 지워줄 수 있음
// findMember.getAddressHistory().add(new AddressEntity("newCity1", "street", "10000"));
tx.commit();
}
자체적인 ID가 있다는 것은 값 타입이 아니라 엔티티라는 것.