스프링부트 너 뭐 돼?🤷‍♀️(10) - 임베디드

joyfulwave·2022년 12월 9일
0

피할 수 없다면 즐기자! 스프링부트 너.. 뭐 돼?




📚 임베디드

📌 임베디드 타입(복합 값 타입)

  • 임베디드 타입은 '복합 값 타입' 이라는 내장 타입을 뜻해요.
  • 새로운 값 타입을 직접 정의할 수 있어요.
  • JPA는 임베디드 타입(emdedded type)이라고 해요.
  • 주로 기본값 타입을 모아 만들어서 복합값 타입이라고도하고 int, String 과 같은 값 타입이에요.

📌 JPA에서 임베디드 타입 사용법

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

📌 임베디드 타입의 장점

  • 재사용
  • 높은 응집도
  • 객체지향적인 설계 가능

📌 예제

⚫ Member.java

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;
	
}

⚫ Period.java

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() {}
	
	
	
}

⚫ Address.java

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() {}
	
}

⚫ JpaMain5.java

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();
		}
		
	}

}




📌 @AttributeOverride

⚫ Member.java

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 결과 까지 변경되는 부작용을 보게 돼요.

⚫ 해결(1)

주소값이 아닌 값 자체를 가져오는

Address copyAddr = new Address(addr.getCity(), addr.getStreet(), addr.getZipcode()); 

을 추가하여 member2에 세팅해주면

member2.setAddress(copyAddr);

위의 부작용을 해결할 수 있어요.

  • JpaMain5.java
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();
		}
		
	}

}

⚫ 해결(2)

  • 불변객체를 만들어줘요.

    불변객체

    • 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단해요.
    • 값 타입은 불변 객체로 설계하는 것을 권장해요.
    • 생성 시점 이후 절대 값을 변경할 수 없는 객체
    • 생성자로만 값을 설정하고 수정자(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

0개의 댓글