-
Jpa Embedded백엔드 2023. 9. 6. 14:47728x90반응형SMALL
Jpa에서 converter를 이용해 DB에 저장하는 값과 객체에 저장하는 값을 다르게 하는 이유는 아무래도 가독성 높은 코드를 작성하기 위해서 일 것입니다.
이러한 것들을 가능하게 해주는 Entity의 Embedded 속성들을 살펴보도록 하겠습니다.
임베디드 타입으로 많이 선언하는 것은 Spring 프레임워크에서도 예시를 들어주고 있습니다.
Example 1: @Embeddable public class EmploymentPeriod { @Temporal(DATE) java.util.Date startDate; @Temporal(DATE) java.util.Date endDate; ... } Example 2: @Embeddable public class PhoneNumber { protected String areaCode; protected String localNumber; @ManyToOne PhoneServiceProvider provider; ... } @Entity public class PhoneServiceProvider { @Id protected String name; ... } Example 3: @Embeddable public class Address { protected String street; protected String city; protected String state; @Embedded protected Zipcode zipcode; } @Embeddable public class Zipcode { protected String zip; protected String plusFour; }
위의 예시는 한 클래스를 임베디드 타입으로 쓸 것이라고 선언하는 @Embeddable 어노테이션에서 소개하는 @Embeddable로 선언할만한 객체의 내용을 보여주는 것입니다.
기간, 전화번호, 주소 등이 임베디드 타입으로 다른 엔티티의 속성에 포함되는 경우가 많습니다.
위와 같이 여러 개의 Entity에서 사용될 혹은 따로 클래스로 모아서 사용할 속성들을 모아서 @Embeddable 어노테이션을 선언해주고 Embedded 타입으로 사용할 수 있습니다.
Embedded 타입으로 선언하지 않고 모든 컬럼들을 그냥 Entity에 선언해서 사용할 수 있습니다.
@Entity public class User { ... private String city; private String district; private String detail; private String zipCode; }
위와 같이 선언을 해주도록 하겠습니다.
만약, 여기서 User Entity와 같이 주소값을 사용하는 Entity가 있을 경우 city, district, detail, zipCode 값을 모두 복사해서 붙여넣고 사용하게 될 것입니다.
이러한 경우는 클린코드 법칙 중 하나인 DRY(Don't Repeat Yourself)를 위배됩니다. 코드를 복사 붙여넣기를 하기 보다는 객체화하여 사용하는 것을 권장하는 것입니다.
그러므로 Embedded 타입으로 주소를 선언해서 사용해보도록 하겠습니다.
@Embeddable
@Embeddable @Data @AllArgsConstructor @NoArgsConstructor public class Address{ private String city; private String district; private String detail; private String zipCode; }
@Embeddable 어노테이션을 선언해줌으로써 Embedded 타입으로 사용할 수 있도록 해줍니다.
@Embedded
@Entity public class User{ ... @Embedded private Address address; }
Address를 User안에 선언해서 사용하게 된다면, User table이 선언될 경우 Address 내의 속성들이 모두 포함되어 만들어지는 것을 알 수 있습니다.
create table user
Hibernate: create table user ( id bigint generated by default as identity, city varchar(255), detail varchar(255), district varchar(255), zip_code varchar(255), email varchar(255), name varchar(255), primary key (id) )
위와 같이 @Embeddable로 선언된 클래스의 경우 @Column 어노테이션을 통해 테이블 내 속성의 이름을 재정의하거나 nullable과 같은 옵션을 넣어줄 수도 있습니다.
@Embeddable @Data @AllArgsConstructor @NoArgsConstructor public class Address{ @Column(name = "address_city") private String city; ... }
city로 선언되었던 컬럼 명이 address_city로 바뀌는 것을 확인할 수 있습니다.
만약 User Entity에서 여러 개의 address를 가질 경우 어떻게 하는 것이 좋을까요?
예시)
@Entity public class User{ ... @Embedded private Address homeAddress; @Embedded private Address companyAddress; }
위처럼 Address를 두 번 선언해주었습니다. 위와 같이 했을 경우 오류가 발생하게 됩니다.
아무래도 Address를 두 번씩 선언해서 내부 속성 또한 두 번 선언되어서 중복으로 선언되었다는 오류가 발생한 것입니다.
그러므로 해당 속성들의 이름들을 모두 새로 선언해서 사용해주어야 합니다.
@Entity public class User{ ... @Embedded @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name = "home_city")), @AttributeOverride(name = "district", column = @Column(name = "home_district")), @AttributeOverride(name = "detail", column = @Column(name = "home_address_detail")), @AttributeOverride(name = "zipCode", column = @Column(name = "home_zipCode")), }) private Address homeAddress; @Embedded @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name = "company_city")), @AttributeOverride(name = "district", column = @Column(name = "company_district")), @AttributeOverride(name = "detail", column = @Column(name = "company_address_detail")), @AttributeOverride(name = "zipCode", column = @Column(name = "company_zipCode")), }) private Address companyAddress; }
중복적으로 선언되는 컬럼들의 이름을 모두 새롭게 정의해주어서 사용할 수 있습니다.
위의 설정 값들은 모두 DB에 적용될 때의 값이고 Jpa 내에서 사용할 경우 모두 <user Entity>.getCompanyAddress().getCity()와 같은 형식으로 사용이 가능합니다.
위와 같이 중복되는 컬럼이 많아질수록 다시 설정해야 하는 값이 많아져 지저분해 보일 수 있습니다. @AttributeOverride를 사용해 재정의해 사용할 것인지 아니면 클래스를 아예 재정의해서 사용할 것인지는 개개인이 판단해서 사용하는 것이 좋을 것 같습니다.
Embedded 타입이 null or 빈 객체일 경우
@Transactional @Test void test(){ User user1 = new User(); user1.setName("user1"); user1.setAddress(null); userRepository.save(user1); User user2 = new User(); user2.setName("user2"); user2.setAddress(new Address()); userRepository.save(user2); }
트랜잭션이 걸려있는 상태에서Embedded 객체의 속성 값들이 null 일 경우와 Embedded 객체 자체가 null 일 경우 어떻게 달라지는지 확인해보면,
User(id=1, name=user2, email=null, userHistories=[], address=Address(city=null, district=null, detail=null, zipCode=null)) User(id=2, name=null, email=null, userHistories=[], address=null)
전자는 빈 객체 그리고 후자는 null로 저장된 것을 알 수 있습니다.
하지만, DB에 저장된 값은 모든 속성이 null로 저장된 상태입니다.
[1, null, null, null, null, null, user1] [2, null, null, null, null, null, user2]
이는 영속성 컨텍스트가 제공하는 캐시 때문으로, 캐시에 남아있는 Address 객체를 그대로 반환하기 때문에 빈 Address객체를 반환하게 되는 것입니다.
즉, 빈 Embedded 객체가 있을 경우 DB에 Embedded 객체의 속성은 모두 null로 저장된다는 것을 알고 있으면 됩니다.
반응형LIST'백엔드' 카테고리의 다른 글
Spring filter, interceptor (0) 2023.09.14 Spring Validation (0) 2023.09.13 JPA @Query, Native Query, Converter (0) 2023.09.05 JPA Cascade, OrphanRemoval (0) 2023.09.04 Spring IOC, DI, AOP (0) 2023.08.26