본문 바로가기
JAVA/JPA

[Spring + Java] JPA 타입 정리

by 개미가되고싶은사람 2024. 11. 22.

목차

    JPA의 데이터 타입 종류

    엔티티 타입

    엔티티 타입은 @Entity 어노테이션으로 정의된 객체로, 각 엔티티는 고유한 식별자(기본 키)를 가지고 있습니다. 이로 인해 데이터가 변경되더라도 식별자로 지속적으로 추적할 수 있어 관리가 용이합니다.

     

    값 타입

    값 타입은 int, Integer, String과 같은 기본 데이터 타입이나 그에 해당하는 객체로, 식별자가 없고 값만 존재합니다. 따라서 값이 변경되면 완전히 다른 값으로 간주되어 추적이 불가능합니다. 예를 들어, 숫자 100을 200으로 변경하면 200은 새로운 값으로 인식됩니다.

     

     

    값 타입 종류

    1. 기본값 타입

    자바의 기본 타입인 int와 double은 값이 직접 저장되며, 항상 값을 복사하기 때문에 절대 공유되지 않고, 이들 타입의 생명주기는 엔티티에 의존하여, 예를 들어 Student라는 엔티티에 있는 int age와 String name은 해당 Student 엔티티가 삭제되면 함께 삭제됩니다. 반면, 래퍼 클래스인 Integer와 Long, 그리고 String과 같은 특수한 클래스는 객체로서 공유가 가능하지만, 이들은 값을 변경할 수 없어 생성된 후 값을 변경할 수 없다.

     

    2. 임베디드 타입(embedded type) - 복합 값 타입

    임베디드 타입은 내장 타입으로 불리며, 주로 기본 값 타입을 모아서 만들어진 복합 값 타입입니다. 복합 값 타입을 통해 새로운 값 타입을 직접 정의할 수 있으며, 해당 타입을 가지고 있는 엔티티에 생명주기를 의존합니다. 예를 들어 회원 정보에서 비슷한 정보끼리 묶어 관리하고 싶다면 이러한 묶음을 임베디드 타입으로 만들어 사용하면 됩니다.

     

    위에 사진 같이 회원 정보 중 비슷한 특징을 가지고 있는 시작 날짜(startDate)와 마지막 날짜(endData)를 하나로 묶어서 Period로 city, street, zipcode를 묶어 Address로 관리할 수 있습니다. 

    @Embeddable
    public class Address {
    
        private String city;
        private String street;
        private String zipcode;
    
        public Address() {  
        }
    }
    @Embeddable
    public class Period {
    
        private LocalDateTime startDate;
        private LocalDateTime endDate;
    
        public Period() { 
        }
    }
    public class Member {
    
    	@Id @GeneratedValue
        	@Column(name = "MEMBER_ID")
        	private Long id;
    
        	@Column(name = "USERNAME")
        	private String username;
            
        	@Embedded
        	private Period period;
    
        	@Embedded
        	private Address address;
            
     }

    Address와 Period 클래스에(값 타입을 정의하는 객체/타입) @Embeddable을 붙여주고, 값 타입을 사용하는 곳이 Member에는 @Embedded를 붙여주면 됩니다. 이 때 임베디드 타입들은 기본 생성자가 필수적으로 있어야 합니다.

     

     

    임베디드 타입의 문제점 - 참조 공유

    래퍼 클래스나 String은 참조 값을 복사하기 때문에 값을 변경할 때 문제가 없습니다. 하지만 임베디드 타입은 직접 정의한 객체 타입이기 때문에 문제가 발생합니다.

    예를들어 본인(회원1)과 엄마(회원2)의 주소가 같아서 하나의 Address객체로 값을 공유하게 되면 문제가 발생합니다.

    Address address = new Address("city","street","zipcode");
    
    Member member1 = new Member();
    member1.setName("본인");
    member1.setAddress(address);
    em.persist(member1);
    
    Member member2 = new Member();
    member2.setName("엄마");
    member2.setAddress(member1.getAddress());
    
    // member1의 city에 값만 변경하고 싶어서 이렇게 작성
    member1.getAddress().setCity("new_city");
    em.persist(member2);
    
    tx.commit();

    만약 본인이 이사를 가서 주소를 변경해야 되어서 코드를 위에 같이 작성하면 member1의 주소만 변경 되는게 아니라  member1, member2에 주소가 변경됩니다. 왜냐하면 Address 객체가 동일한 참조를 공유하기 때문입니다.

     

    해결 방법

    1. equals() 메서드 사용

    객체의 동등성을 비교할 때 equals() 메서드를 오버라이드하여, 두 객체가 같은 값을 가지는지를 비교할 수 있습니다. 이를 통해 객체의 동일성을 명확하게 판단할 수 있습니다.

     

    2. 불변 객체 사용

    불변 객체(Immutable Object)를 사용하면 객체의 상태를 변경할 수 없으므로, 참조 공유로 인한 문제를 방지할 수 있습니다. 예를 들어, Address 객체를 불변으로 설계하면, 주소를 변경할 때 새로운 Address 객체를 생성해야 하므로, 기존의 객체는 영향을 받지 않습니다. 불변 객체를 만드는 방법은 setter() 메소드를 생성하지 않거나 private로 정의하고, 생성자를 통해서만 값을 주입하는 것입니다.

     

     

    값 타입 컬렉션

    참고로 자바 컬렉션으로 값 타입 컬렉션을 사용할 수 있지만, 데이터 수정 시 추적이 어렵다는 단점이 있습니다. 예를 들어, List<> 컬렉션을 사용할 경우, 데이터를 수정할 때 전체가 삭제되고 남은 데이터가 다시 추가되는 방식으로 처리됩니다. 즉, 기존에 3개의 데이터가 있을 때 1개를 수정하면, 전체 삭제 쿼리와 함께 3개의 데이터를 다시 삽입하는 쿼리가 실행됩니다. 이러한 이유로 값 타입 컬렉션을 사용하고자 할 때는 새로운 엔티티를 만들어 일대다 관계로 설정하는 경우가 많습니다.

     

     

     

    참고

    https://sudo-minz.tistory.com/145

    https://velog.io/@shlee327/JPA-%EA%B0%92-%ED%83%80%EC%9E%85-%EC%A0%95%EB%A6%AC

    https://terianp.tistory.com/174