피할 수 없다면 즐기자! 스프링부트 너.. 뭐 돼?
package com.koreait.jpaitem.embedded;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
import lombok.Getter;
import lombok.Setter;
@Entity
@TableGenerator(name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ,",
allocationSize = 1)
@Getter @Setter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name", nullable = false)
private String username;
// 기간 period
// @Embedded와 @Embeddable 둘 중에 하나만 넣어도 되나 둘 다 넣어줄 것을 권장
// private LocalDateTime startDate;
// private LocalDateTime endDate;
@Embedded
private Period period;
// 주소 address
// private String city;
// private String street;
// private String zipcode;
@Embedded
private Address address;
}
package com.koreait.jpaitem.embedded;
import java.time.LocalDateTime;
import javax.persistence.Embeddable;
import lombok.Getter;
import lombok.Setter;
@Embeddable
@Getter @Setter
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
// 테스트를 위한 파라미터가 있는 생성자
public Period(LocalDateTime startDate, LocalDateTime endDate) {
super();
this.startDate = startDate;
this.endDate = endDate;
}
// 기본생성자는 반드시 있어야한다.
public Period() {}
}
package com.koreait.jpaitem.embedded;
import javax.persistence.Embeddable;
import lombok.Getter;
import lombok.Setter;
@Embeddable
@Getter @Setter
public class Address {
private String city;
private String street;
private String zipcode;
// 파라미터가 있는 생성자
public Address(String city, String street, String zipcode) {
super();
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
// 기본생성자
public Address() {}
}
package com.koreait.jpaitem;
import java.time.LocalDateTime;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.koreait.jpaitem.embedded.Address;
import com.koreait.jpaitem.embedded.Member;
import com.koreait.jpaitem.embedded.Period;
public class JpaMain5 {
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("user");
member.setAddress( new Address("서울", "역삼", "123123") );
member.setPeriod( new Period(LocalDateTime.now(), LocalDateTime.now()) );
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
emf.close();
}
}
}
package com.koreait.jpaitem.embedded;
import java.time.LocalDateTime;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
import lombok.Getter;
import lombok.Setter;
@Entity
@TableGenerator(name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ,",
allocationSize = 1)
@Getter @Setter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name", nullable = false)
private String username;
// 기간 period
// @Embedded와 @Embeddable 둘 중에 하나만 넣어도 되나 둘 다 넣어줄 것을 권장
// private LocalDateTime startDate;
// private LocalDateTime endDate;
@Embedded
private Period period;
// 주소 address
// private String city;
// private String street;
// private String zipcode;
// 주소
@Embedded
private Address address;
// 회사 주소
@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;
}
package com.koreait.jpaitem;
import java.time.LocalDateTime;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.koreait.jpaitem.embedded.Address;
import com.koreait.jpaitem.embedded.Member;
import com.koreait.jpaitem.embedded.Period;
public class JpaMain5 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address addr = new Address("서울", "역삼", "123123");
Member member = new Member();
member.setUsername("user1");
member.setAddress(addr);
em.persist(member);
Member member2 = new Member();
member2.setUsername("user2");
// user1과 user2가 같은 addr를 가지고 있다.
member2.setAddress(addr);
em.persist(member2);
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
emf.close();
}
}
}
user1의 주소만 newCity로 변경하고 싶다면 아래와 같이 작성하게 되는데
package com.koreait.jpaitem;
import java.time.LocalDateTime;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.koreait.jpaitem.embedded.Address;
import com.koreait.jpaitem.embedded.Member;
import com.koreait.jpaitem.embedded.Period;
public class JpaMain5 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address addr = new Address("서울", "역삼", "123123");
Member member = new Member();
member.setUsername("user1");
member.setAddress(addr);
em.persist(member);
Member member2 = new Member();
member2.setUsername("user2");
// user1과 user2가 같은 addr를 가지고 있다.
member2.setAddress(addr);
em.persist(member2);
// user1의 주소만 newCity로 변경하고 싶다.
member.getAddress().setCity("newCity");
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
emf.close();
}
}
}
member와 member2가 객체로서 같은 객체 addr의 주소값을 바라보기 때문에 member2의 city 결과 까지 변경되는 부작용을 보게 돼요.
주소값이 아닌 값 자체를 가져오는
Address copyAddr = new Address(addr.getCity(), addr.getStreet(), addr.getZipcode());
을 추가하여 member2에 세팅해주면
member2.setAddress(copyAddr);
위의 부작용을 해결할 수 있어요.
package com.koreait.jpaitem;
import java.time.LocalDateTime;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.koreait.jpaitem.embedded.Address;
import com.koreait.jpaitem.embedded.Member;
import com.koreait.jpaitem.embedded.Period;
public class JpaMain5 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address addr = new Address("서울", "역삼", "123123");
Member member = new Member();
member.setUsername("user1");
member.setAddress(addr);
em.persist(member);
Address copyAddr = new Address(addr.getCity(), addr.getStreet(), addr.getZipcode());
Member member2 = new Member();
member2.setUsername("user2");
member2.setAddress(copyAddr);
em.persist(member2);
// user1의 주소만 newCity로 변경하고 싶다.
member.getAddress().setCity("newCity");
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
emf.close();
}
}
}
불변객체
- 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단해요.
- 값 타입은 불변 객체로 설계하는 것을 권장해요.
- 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하고 수정자(Setter)을 만들지 않으면 돼요.
package com.koreait.jpaitem;
import java.time.LocalDateTime;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.koreait.jpaitem.embedded.Address;
import com.koreait.jpaitem.embedded.Member;
import com.koreait.jpaitem.embedded.Period;
public class JpaMain6 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address addr = new Address("서울", "역삼", "123123");
Member member = new Member();
member.setUsername("user1");
member.setAddress(addr);
em.persist(member);
Member member2 = new Member();
member2.setUsername("user2");
// user1과 user2가 같은 addr를 가지고 있다.
member2.setAddress(addr);
em.persist(member2);
// user1의 주소만 newCity로 변경하고 싶다.
Address newAddr = new Address("newCity", "역삼", "123123");
member.setAddress(newAddr);
// 부작용 방지를 위해 Address 클래스의 setter 를 제거한다.
// member.getAddress().setCity("newCity");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
emf.close();
}
}
}
출처
https://media.giphy.com/media/kyUIknbbDNvID5XzU4/giphy.gif
https://media.giphy.com/media/A6aHBCFqlE0Rq/giphy.gif