JPA에서의 값 타입은 데이터베이스의 단일 값이나 복합 값을 표현하는 타입으로, 엔티티와는 다른 특성을 가지고 있어. 값 타입은 주로 엔티티의 속성(필드)을 정의할 때 사용되며, 객체의 생명 주기를 엔티티에 종속시킨다는 특징이 있어. JPA에서 값 타입은 크게 기본값 타입과 임베디드(복합) 값 타입으로 나눌 수 있어.
1. 기본값 타입
기본값 타입은 자바의 기본 데이터 타입(int, double, String 등)과 JPA가 제공하는 타입을 말해. 이 값들은 데이터베이스 테이블에서 하나의 컬럼에 대응하고, 보통 복사(by value) 방식으로 처리돼. 기본 타입은 엔티티와 달리 생명 주기를 따로 관리하지 않고, 엔티티에 종속돼 있어.
예시:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name; // 기본값 타입
private int age; // 기본값 타입
}
• name과 age는 기본값 타입이며, 각각 데이터베이스의 VARCHAR, INTEGER 같은 컬럼에 매핑돼.
2. 임베디드(복합) 값 타입
임베디드 값 타입은 여러 필드를 하나의 객체로 묶어서 사용할 수 있는 값 타입이야. 주로 비즈니스 의미가 있는 값들을 그룹화해서 객체로 만들고, 엔티티의 속성으로 사용하는 방식이지. @Embeddable과 @Embedded 어노테이션을 통해 값 타입을 정의하고 사용할 수 있어.
예시:
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// Getters and Setters
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
private Address address; // 임베디드 값 타입
}
• Address는 임베디드 값 타입으로, User 엔티티에 포함된 객체야. 이 값 타입은 User 엔티티가 저장되거나 삭제될 때 같이 처리돼.
3. 컬렉션 값 타입
컬렉션 값 타입은 값 타입을 컬렉션 형태로 저장하는 방식이야. 엔티티에 속한 값 타입 컬렉션은 별도의 테이블에 저장돼 관리되며, JPA에서는 이를 지원하기 위해 @ElementCollection을 사용해.
예시:
@Embeddable
public class PhoneNumber {
private String countryCode;
private String number;
// Getters and Setters
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
private List<PhoneNumber> phoneNumbers; // 값 타입 컬렉션
}
• phoneNumbers 필드는 여러 개의 PhoneNumber 객체를 컬렉션으로 관리하는 값 타입이야.
특징:
• 독립성 부족: 값 타입은 엔티티에 종속적이기 때문에, 값 타입만 따로 저장하거나 조회할 수 없어.
• 불변성: 값 타입은 기본적으로 변경 불가능(immutable)하게 설계하는 것이 권장돼. 값이 변경되면 새롭게 값을 교체하는 방식으로 처리해야 해.
• 참조 무결성 없음: 값 타입은 별도의 식별자가 없고 엔티티와 함께 관리되므로, 값 타입 객체를 공유하거나 참조할 수 없어.
정리
• 기본값 타입: 단일 값 (예: String, int)을 저장하는 타입.
• 임베디드 값 타입: 여러 필드를 가진 객체를 엔티티의 일부로 사용하는 복합 타입.
• 컬렉션 값 타입: 값 타입을 컬렉션 형태로 관리.
JPA에서 값 타입은 객체 모델을 더 유연하게 설계하고, 데이터를 그룹화해서 사용할 수 있게 도와주는 중요한 기능이야.
@AttributeOverride는 JPA에서 임베디드 값 타입이나 상속 구조에서 기본적으로 매핑된 필드나 속성의 매핑 정보를 재정의할 때 사용하는 어노테이션이야. 특히, 같은 값 타입을 여러 엔티티에서 사용할 때 각 필드의 매핑 정보를 변경할 필요가 있을 때 유용해.
주로 사용하는 상황:
1. 임베디드 값 타입에서 속성 이름 변경: 여러 엔티티에서 동일한 값 타입을 사용하지만, 해당 필드가 다른 데이터베이스 컬럼에 매핑되어야 할 때 사용해.
2. 상속 관계에서 필드 재정의: 상속받은 엔티티에서 부모 클래스의 속성 매핑을 재정의할 때도 사용할 수 있어.
기본 사용법:
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "home_city")),
@AttributeOverride(name = "street", column = @Column(name = "home_street")),
@AttributeOverride(name = "zipcode", column = @Column(name = "home_zipcode"))
})
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "office_city")),
@AttributeOverride(name = "street", column = @Column(name = "office_street")),
@AttributeOverride(name = "zipcode", column = @Column(name = "office_zipcode"))
})
private Address officeAddress;
}
설명:
• @Embedded: 임베디드 타입을 선언할 때 사용.
• @AttributeOverrides: 여러 속성의 매핑 정보를 재정의할 때 사용하며, 그 안에 @AttributeOverride를 포함해.
• @AttributeOverride(name, column): 재정의할 속성의 이름(name)과 그 속성이 매핑될 새로운 컬럼 정보(column)를 지정.
위 예시에서 Address라는 임베디드 값 타입을 homeAddress와 officeAddress로 각각 다른 컬럼에 매핑하고 있어. homeAddress는 home_city, home_street, home_zipcode 컬럼에, officeAddress는 office_city, office_street, office_zipcode 컬럼에 매핑돼.
정리:
• @AttributeOverride는 같은 값 타입이나 상속된 필드의 기본 매핑을 재정의하고 싶을 때 사용하는 어노테이션이야.
• 주로 임베디드 값 타입을 사용할 때 각각의 필드를 서로 다른 컬럼에 매핑할 수 있도록 해줘.
Tip💡
**쉘로우 카피(Shallow Copy)**와 **딥 카피(Deep Copy)**는 객체 복사 시 복사의 깊이를 의미하는 개념이야. 두 방식은 객체 내부의 참조 자료형(예: 배열, 객체)이 어떻게 복사되는지에 따라 다르지.
1. 쉘로우 카피 (Shallow Copy)
• 얕은 복사라고도 해.
• 객체의 최상위 필드만 복사하고, 그 필드가 참조하고 있는 객체는 복사하지 않아. 즉, 복사된 객체와 원본 객체가 같은 참조를 가리키게 돼.
• 참조형 데이터(예: 배열, 객체)는 복사하지 않고 참조 주소만 복사하기 때문에, 복사본에서 참조형 데이터를 변경하면 원본에도 영향을 줄 수 있어.
예시:
public class Main {
public static void main(String[] args) {
int[] original = {1, 2, 3};
int[] shallowCopy = original;
shallowCopy[0] = 100; // 원본 배열도 영향을 받음
System.out.println(original[0]); // 100 출력
}
}
2. 딥 카피 (Deep Copy)
• 깊은 복사는 객체의 모든 필드와 그 필드가 참조하는 객체들까지 모두 복사하는 방식이야.
• 즉, 복사된 객체가 원본 객체와 완전히 독립적으로 동작해. 원본과 복사본이 각각의 참조를 가지기 때문에, 복사본에서 참조형 데이터를 변경해도 원본에 영향을 미치지 않아.
예시:
public class Main {
public static void main(String[] args) {
int[] original = {1, 2, 3};
int[] deepCopy = original.clone(); // 배열을 완전히 복사
deepCopy[0] = 100; // 원본 배열에는 영향 없음
System.out.println(original[0]); // 1 출력
}
}
차이점 정리:
• 쉘로우 카피: 참조만 복사, 변경 시 원본에 영향.
• 딥 카피: 모든 객체를 독립적으로 복사, 변경 시 원본에 영향 없음.
쉘로우 카피는 성능이 좋지만 참조 문제로 의도치 않은 부작용이 생길 수 있고, 딥 카피는 성능은 조금 더 떨어질 수 있지만 안전한 복사 방법이야.
Tip💡
Q. Integer, String 등은 자바가 제공하는 대표적인 불변 객체인가?
A. 네, **Integer**와 **String**은 자바에서 제공하는 대표적인 **불변 객체(immutable object)**야.
1. String 클래스
• 불변 객체: String은 생성된 후 값을 변경할 수 없는 불변 객체로 설계되었어. 문자열을 조작하려고 하면 새로운 String 객체가 생성되고, 기존 객체는 변하지 않아.
• 불변성 이유: 메모리 효율성, 보안, 스레드 안전성 등을 위해 설계되었어. 여러 곳에서 같은 문자열 리터럴을 공유할 수 있고, 그 값이 변경되지 않으므로 프로그램 안정성을 높여줘.
예시:
String str = "hello";
str = str + " world"; // 새로운 String 객체 생성
이 경우 str은 처음에 "hello"를 가리키고 있었지만, str + " world"로 새로운 문자열이 만들어지고 str은 이제 그 새로운 객체를 가리켜.
2. Integer 클래스
• 불변 객체: Integer는 자바의 래퍼 클래스 중 하나로, int와 같은 기본형 타입을 객체로 다루기 위해 사용돼. Integer 객체는 생성 후 변경되지 않아. 따라서, 숫자를 변경하려면 새로운 Integer 객체를 생성해야 해.
• 불변성 이유: 래퍼 클래스들은 기본형 타입의 값을 감싸는 단순한 역할을 하기 때문에 값이 변경될 필요가 없어. 이를 통해 메모리 관리와 성능이 더 최적화될 수 있어.
예시:
Integer a = 100;
Integer b = a;
a = 200;
이 코드에서 a가 처음에 100을 가리켰다가 200으로 변경되지만, 이는 새로운 Integer 객체를 할당하는 것이지 기존의 100 값을 변경하는 것은 아니야. b는 여전히 100을 가리키고 있어.
불변 객체의 장점:
• 안정성: 여러 스레드에서 동시에 접근해도 안전해.
• 캐싱 가능: 동일한 값에 대한 객체를 재사용함으로써 메모리 효율성을 높일 수 있어. (예: 자바의 Integer 캐싱)
결론:
• **String**과 **Integer**는 자바에서 제공하는 대표적인 불변 객체.
• 불변성 덕분에 성능 및 메모리 관리가 더 효율적이며, 스레드 안전성을 보장할 수 있어.
'Database > JPA' 카테고리의 다른 글
N+1 문제를 해결하는 JPA 사용법 (0) | 2024.09.24 |
---|---|
JPA Q&A 모음집 (1) | 2024.09.23 |
JPA 고급 상속 매핑 전략: 단일 테이블, 조인 테이블, 테이블당 클래스 비교와 활용 (1) | 2024.09.13 |
JPA 연관관계와 복합 키 (2) | 2024.09.13 |
JPA의 단방향, 양방향 연관관계와 Cascade 이해하기 (0) | 2024.09.13 |