Spring Boot/Spring Boot JPA-기본편 강의 정리(김영한)

[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 9. 값 타입

조찬국 2024. 2. 26. 16:51
728x90

강의 출처:
https://www.inflearn.com/course/ORM-JPA-Basic#curriculum

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런

저는 야생형이 아니라 학자형인가봐요^^ 활용편 넘어갔다 30% 정도 듣고 도저히 답답해서 기본편을 들어버렸네요^^. 한주 한주 김영한님 강의 들으니 렙업되는 모습을 스스로 느낍니다. 특히 실

www.inflearn.com

해당 강의는 Inflearn에 등록된 김영한님의 Spring Boot 강의입니다.

jpa는 크게 2가지 타입으로 나뉜다. 엔티티 타입 vs 값 타입
엔티티 타입은 지금까지 배운 타입이고, 이번 시간에는 값 타입을 공부하자.

1. 기본값 타입

값 타입 분류

임베디드 타입과 테이블 매핑'

 

 

다음과 같이 패키지를 하나 만들어서 각 클래스를 추가하자.

여기서 중요하게 봐야할 것은 임베디드 타입은 반드시 기본 생성자를 만들어야 한다.

 

 

MemberEmbeded 객체

@Entity
public class MemberEmbeded {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String name;


    //임베디드 타입으로 매핑 하는 이유
    @Embedded
    //기간
    private Period workPeriod;

    //★★★한개의 엔티티를 두개로 사용하는법:@AttributeOverride★★★
    @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;

다음과 같이 임베디드 타입으로 각 객체 workPeriodhomeAddress를 생성하였다.

이때 workAddress는 속성을 일일이 정의하였다. 이로 인해 회원의 집주소와 회사 주소를 명시할 수 있게 되었다. 이로 인해 같은 값 타입을 불러와서 써도 컬럼명의 중복을 방지 할 수 있게 되었다.

Period 객체

//내장형이다.
@Embeddable
public class Period {

    //기간 Period
    private LocalDateTime startDate;
    private LocalDateTime endDate;

    public Period() {
    }
     //getter,setter
 }

Address 객체

//내장형이다.
@Embeddable
public class Address {
    //w주소
    private String city;
    private String street;
    private String zipcode;
    //기본 생성자
    public Address() {
    }
    //생성자를 통해 setting (생성자 주입)
    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }     
    //getter,setter
 }

임베디드 타입의 장점은 다음과 같다.

상속하는게 아니라 다른 엔티티에 삽입하기가 좋다. 재사용성을 높인다.

main함수에서 MemberEmbeded 값 setting

MemberEmbeded memberEmbeded= new MemberEmbeded();
memberEmbeded.setName("chochanguk");
memberEmbeded.setHomeAddress(new Address("서울","낙성대역 8길","10"));
memberEmbeded.setWorkPeriod(new Period());

em.persist(memberEmbeded);

결과

 

 

 

 

 

2. 값 타입 컬렉션

값 타입 컬렉션

Adress를 객체로 사용하는 AddressEntity 생성

@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
    @Id
    @GeneratedValue
    private Long id;
    private Address address;

    public AddressEntity() {

    }

    public AddressEntity(String city, String street, String zipcode) {
        this.address = new Address(city, street, zipcode);
    }
     //getter,setter   
 }

MemberEmbeded에 값타입 컬렉션 추가( getter,setter 재설정)

   //★★★값 타입 컬렉션★★★
    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD", joinColumns =
    @JoinColumn(name = "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<>();

    //★★★★일대다로 매핑하는게 쿼리 최적화에 더 좋고, 할 수 있는게 많아진다.
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "MEMBER_ID")
    private List<AddressEntity> addressList = new ArrayList<>();

    //getter,setter 재설정

main 함수에서 값 타입 조회 및 값 타입 컬렉션 수정 예제

MemberEmbeded memberEmbeded1 = new MemberEmbeded();
//생성자로 입력해서 세팅하기
memberEmbeded1.setHomeAddress(new Address("homeCity", "낙성대역 8길", "10"));

//좋아하는 음식의 set을 가져와서 추가하기(세팅)
memberEmbeded1.getFavoriteFoods().add("치킨");
memberEmbeded1.getFavoriteFoods().add("치킨");
memberEmbeded1.getFavoriteFoods().add("피자");

memberEmbeded1.getAddressHistory().add(new AddressEntity("old1", "낙성대역 8길", "10000"));
memberEmbeded1.getAddressHistory().add(new AddressEntity("old2", "낙성대역 8길", "10000"));

em.persist(memberEmbeded1); //member만 persist하기
em.flush();
em.clear();

//★★ 값 타입 조회 하기 에제★★
System.out.println("=====================");
MemberEmbeded findMember = em.find(MemberEmbeded.class, memberEmbeded1.getId()); //
//addressHistoryList 조회
List<AddressEntity> addressHistoryList = findMember.getAddressHistory();
for (AddressEntity address : addressHistoryList) {
    System.out.println("address = " + address.getAddress().getCity());
}
//favoriteFoods 조회 하기
Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
    System.out.println(" favorite foods = " + favoriteFood);
}

//★★★★ 값 타입 수정하기 예제 ★★★★
System.out.println("=========START========");
MemberEmbeded findMemberForReset = em.find(MemberEmbeded.class, memberEmbeded1.getId());

//이건 잘못된 방식
//findMemberForReset.getHomeAddress().setCity("new CITY");

//값타입을 이게 제대로 된 방식(UPDATE 쿼리가 나감)
Address old = findMemberForReset.getHomeAddress();
findMemberForReset.setHomeAddress(new Address("new City", old.getStreet(), old.getZipcode()));

//★★★값 타입 컬렉션을 수정하는 법★★★
findMemberForReset.getFavoriteFoods().remove("치킨");
findMemberForReset.getFavoriteFoods().add("한식");

//★★★값 타입 컬렉션을 수정하는 법2★★★
findMemberForReset.getAddressHistory().remove(new AddressEntity("old1", "낙성대역 8길", "10"));
findMemberForReset.getAddressHistory().add(new AddressEntity("new City", "낙성대역 8길", "10"));

결과

728x90