JPA 에 대하여

나중에 찾아보고 사용하기 위해 정리합니다. ㅎㅎ

 

Entity

@Entity
public class user {
  @Id @GeneratedValue
  private Long id;
}

 

해당 클래스가 JPA 에서 관리하는 Entity Class 임을 나타냅니다.

해당 Class 는 DB 의 Table 로 Mapping 되며, 기본생성자(public or protected) 가 꼭 필요합니다.

또한 해당 Class 에는 @Id 가 붙은 필드가 하나 이상 있어야 합니다.

 

다른 코드들을 보면 기본 생성자를 안 넣고 @Entity 를 사용해도 오류가 발생하지 않는데 그 이유는 자바는 생성자를 하나도 안 만들면 컴파일러가 자동으로 기본 생성자를 만들어 주기 때문입니다.

 

Table

@Entity
@Table(name = "user_table", schema="my_schema")
public class User {
  @Id @GeneratedValue
  private Long id;
}

 

테이블명을 직접 지정해주고 싶거나, 스키마를 분리하고 싶을 때 사용합니다.

 

name : 테이블 이름 ( default 는 Class Name )

schema : 사용할 DB 스키마 이름

uniqueConstraints : 테이블에 유니크 제약조건 설정

uniqueConstraints 옵션은 한 DB 에 동일한 이름을 가진 Table 명이 있을 경우 사용하면 좋습니다.

 

@Id, @GeneratedValue

@Entity
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQIENCE, generator = "my_seq_gen")
  @SequenceGenerator(
      name = "my_seq_gen",
      sequenceName="my_seq",
      initialValue=1,
      allocationSize=30
  )
  private Long id;
    
  ...
}

 

@Id 는 Entity 의 기본 키(PK) 를 지정해줍니다.

@GeneratedValue 는 어떻게 값을 생성할 지 결정하는 옵션입니다.

 

대표적인 네가지는 다음과 같습니다.

 

AUTO : JPA 가 DB 에 맞춰 자동선택 - 거의 모든 DB

IDENTITY : DB 의 AUTO_INCREMENT 사용 - MySQL, MariaDB

SEQUENCE : DB 시퀀스 사용 - ORACLE, PostgreSQL

 

위에 코드를 보면 SEQUENCE 를 사용하는데, 이 SEQUENCE 를 사용하려면 DB 에 시퀀스가 존재해야 합니다.

CREATE SEQUENCE my_seq START WITH 1 INCREMENT BY 1;

 

initalValue 는 대부분 사용하지 않습니다. DB 에 직접 시퀀스를 세팅하기 때문입니다.

allocationSize 는 DB 에 미리 30개의 시퀀스 값을 메모리에 적재하고 사용하는 것을 의미합니다.

그럼 DB 에서 계속 통신을 하지 않아 성능면에서 우수합니다.

 

@Column

@Column(name = "user_name", unique = false, nullable = false)
private String name;

 

자바 필드와 DB Column 을 Mapping 해주는 기능입니다.

생략하시면 default 값으로 변수명이 Column Name 이 됩니다.

 

아래는 옵션입니다.

name : DB 컬럼명 지정

nullable : NOT NULL 여부 (true = NULL 허용)

unique : 유니크 제약조건 (true = 중복불가)

length : 문자길이(VARCHAR 의 크기)

insertable/updatable : INSERT/UPDATE SQL 에 포함할지에 대한 여부

precision, scale : 숫자 자릿수 지정

 

@Enumarated

public enum Role {
  USER, ADMIN
}

@Enumarated(EnumType.STRING)
private Role role;

user.setRole(Role.USER); // <-- USER 저장

 

enum 타입 필드를 DB Column 과 매핑할 때 어떤 방식으로 저장할 지 결정하게 됩니다.

 

@ManyToOne, @OneToMany, @JoinColumn

@Entity
public class Member {
	
  @Id @GeneratedValue
  private Long id;
  
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "team_id")
  private Team team;
}

@Entity
public class Team {
	
  @Id @GeneratedValue
  private Long id;
  
  @OneToMany(mappedBy = "team")
  List<Member> members = new ArrayList<>();
}

 

일대다, 다대일 Mapping 을 도와줍니다.

중요한 점은 외래키(PK) 를 누구한테 정의해주냐 입니다.

결론은 반드시 Many 쪽에서 정의해 주어야 합니다.

 

또한, 위에 코드를 보면 지연로딩(FetchType.LAZY) 를 적용한 모습을 볼수가 있는데, 되도록이면 모든 부분에 지연로딩을 설정해주셔야 합니다. Team 정보도 즉시 가져오기 때문에 불필요한 SQL 문이 여러번 실행 될 수 있습니다. (이러한 문제를 N+1 문제라고 합니다.)

 

@Inheritance, @DiscriminatorColumn, @DiscriminatorValue

@Entity
@Inheritance(strategy = InheritanceType.JOIND)
public class Item {

  @Id @GeneratedValue
  private Long id;
  
  private String name;
}

@Entity
public class Book extends Item {

  @Id @GeneratedValue
  private Long id;
  
  private String author;
}

@Entity
public class Movie extends Item {

  @Id @GeneratedValue
  private Long id;
  
  private String director;
}

 

JPA 에서 상속관계를 매핑할 때 사용하는 Annotation 입니다.

즉, Java 클래스가 상속 구조일 때 이를 테이블 구조로 어떻게 매핑 할 것인지 지정합니다.

 

옵션은 아래와 같습니다.

 

SINGLE_TABLE

하나의 테이블에서 모든 자식 엔티티를 저장합니다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype") // <-- 생략해도 JPA 에서 알아서 DTYPE 생성
public class Item {...}

 

결과

id name dtype author director
1 책1 Book 작가A NULL
2 영화 Movie NULL 감독B

 

위에 dtype 을 보면 각 클래스명이 기본값으로 들어가지만 만약 이러한 기본값을 변경하고 싶다면 @DiscriminatorValue 를 사용하시면 됩니다.

@Entity
@DiscriminatorValue("B") // <- 이렇게 설정하면 dtype 에 B 가 들어간다.
public class Book extends Item {...}

 

JOINED

각 자식마다 테이블을 만들고 부모와 조인합니다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {...}

 

결과

Item Table Book Table Movie Table
id, name id, author id, director

 

JOINED 랑 SINGLE_TABLE 이랑 크게 다른 점 중 하나는 SINGLE_TABLE 로 설정한 뒤 movie.setName("공룡") 이라고 입력을 하고 em.persist 로 영속성 컨텍스트에 저장을 하면 Movie Table 의 Name Column 에 공룡이 들어가는게 아니라(Column 이 아예 없기도 함) Item Table 의 Name Column 에 공룡이라는 데이터가 들어가게 됩니다.

 

@MappedSuperclass

@MappedSuperclass
public abstract class BaseEntity {
  private String name;
  private String age;
}

@Entity
public class User extends BaseEntity {...}

@Entity
public class Student extends BaseEntity {...}

 

공통으로 사용하는 변수들을 모아놓고 관리하고 싶을 때 사용합니다.

@MappedSuperclass 는 Entity 가 아니기 때문에 DB 에 적재될 수가 없습니다.

 

@Embeddable, @Embedded

@Embeddable
public class Address {

  private String city;
  private String street;
  private String zipcode;
  
  // 기본생성자 필수
}

@Entity
public class Member {
	
  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @Embedded
  private Address address;
}

 

Entity 안에 들어가는 값 객체입니다.

복잡한 필드를 하나의 클래스로 묶어서 재사용하고 싶을 때 사용합니다.

이 클래스를 테이블로 따로 분리하지 않고, 소속된 엔티티 테이블에 컬럼으로 포함합니다.

 

@Embedded 는 임베디드 타입을 사용하는 필드 라는 뜻입니다.

실제 DB 에는 address_city, address_street, address_zipcode 로 저장됩니다.

여기서 중요하게 볼 부분은 @Embedded 필드명이 컬럼 접두사로 사용된다는 것입니다.

 

컬럼명을 커스터마이징 하고 싶을 때는 아래와 같이 @AttributeOverrides 를 사용하시면 됩니다.

@Embedded
@AttributeOverrides ({ 
  @AttributeOverride(name = "city", column = @Column(name = "home_city"))
})

 

@Embeddable Type 을 사용할 경우 주의해야 할 점이 있습니다.

여러 Entity 에서 공유하고 사용하고 있다면 "값 타입" 특성상 Side Effect 가 발생하게 됩니다.

Embedded Type 은 불변 객체로 설계하는 것이 원칙이라는 규칙때문입니다.

 

만약 여러 Entity 에서 Address 를 사용하고 있는 경우 중간에 City 값을 변경하게 된다면 다른 Entity 들의 City 값도 함께 변경되게 됩니다. 따라서 혹시나 특정 Entity 의 City 값만 변경하고 싶다면 값 객체를 공유하지 말고, 복사해서 사용하시면 됩니다.

 

member.setAddress(new Address("Seoul", "강남대로", "1234"))
user.setAddress(new Address("Busan", "서면", "1234"))

 

JPA 는 값 타입 컬렉션이라는 기능을 지원해줍니다.

말 그대로 값 타입(@Embeddable 로 정의된 타입) 을 컬렉션(List, Set..) 형태로 여러 개 저장하는 것을 의미합니다.

@ElmentCollection 을 사용하는 방법입니다. 하지만 이러한 값 타입 컬렉션은 제약사항이 많습니다.

1. 지연 로딩만 지원됨 (LAZY)

2. 별도의 테이블이 만들어짐

3. 식별자가 없음 -> 개별 요소르 식별하거나 직접 수정하기 어려움

4. 수정하면 통으로 삭제 후 재삽입 해야함

 

따라서 값 타입 컬렉션을 사용하는 것 보다 Entity 로 분리하고, @OneToMany 로 Mapping 하고, @Id 를 추가해서 사용하면 개별 수정이 가능해지기 때문에 이러한 방법을 추천합니다.

 

CASCADE

Life Cycle 이 똑같을 때 사용합니다.

부모 -> 자식 관계에서 다른 부모도 해당 자식을 참조하는게 여러개가 존재한다면 사용하시면 안됩니다.

 

@Entity
public class Child {

  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "parent_id")
  private Parent parent;
}

@Entity
public class Parent {

  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
  private List<Child> childList = new ArrayList<>();
  
  public void addChild(Child child) {
    childList.add(child);
    child.setParent(this);
  }
}

// 다른곳에서
Child child1 = new Child();
Child child2 = new Child();

// em.persist(child1) <-- 원래 이렇게 영속성 컨텍스트에 저장해야함.
// em.persist(child2) <-- 원래 이렇게 영속성 컨텍스트에 저장해야함.

Parent parent = new parent();

parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

 

 

'Spring' 카테고리의 다른 글

Spring 에서 CORS 설정  (3) 2025.07.09
@Aspect - AOP  (0) 2025.05.02
@Aspect 쓰지 않고 순수 Proxy, Proxy Factory 사용법  (0) 2025.04.29
다양한 패턴  (0) 2025.04.25
트랜잭션 프록시와 예외  (0) 2025.04.08