@OneToOne@JoinColumn으로 FK 관리@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
private Profile profile;
// ...
}
@Getter
@Entity
@Table(name = "profiles")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String imageUrl;
// ...
}
@ManyToOne@ManyToOne만으로 정의할 수 있다!@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// ...
}
@Getter
@Entity
@Table(name = "teams")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ...
}
@OneToMany@ManyToOne이 반드시 정의되어 있어야 함@ManyToOne// members 쪽은 위와 동일
@Getter
@Entity
@Table(name = "teams")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// ...
}
@ManyToMany@Getter
@Entity
@Table(name = "books")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// Book이 관계의 주인 (조인 테이블 생성)
@ManyToMany
@JoinTable(
name = "book_author", // 자동으로 생성되는 조인 테이블
joinColumns = @JoinColumn(name = "book_id"), // 현재 엔티티의 FK
inverseJoinColumns = @JoinColumn(name = "author_id") // 반대쪽 엔티티의 FK
)
private List<Author> authors = new ArrayList<>();
}
@Getter
@Entity
@Table(name = "authors")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "authors")
private List<Book> books = new ArrayList<>();
}

다대다 관계의 ERD를 보면, 만들지 않은 테이블이 생성되고, 작성하지 않은 SQL이 실행된다.
유지보수성이 떨어진다!
개선한 코드
@Getter
@Entity
@Table(name = "authors")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Getter
@Entity
@Table(name = "books")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
}
@Getter
@Entity
@Table(name = "book_author")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class BookAuthor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "book_id")
private Book book;
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
}
FetchType.EAGER: 즉시 로딩FetchType.LAZY: 지연 로딩@ManyToOne 옵션@ManyToOne
fetch = FetchType.LAZY: 연관 엔티티를 프록시로 지연 로딩 (EAGER는 안쓴다고 생각하기)optional: 이 연관관계가 필수인지 여부, nullable처럼 생각해도 됨 (기본값: true)@JoinColumn
name: FK 컬럼명unique: 유일 제약 여부 (기본값: false)true로 설정하면 여러 Member가 같은 Team 가리킬 수 없음 -> 사실상 일대일 제약@OneToOne도 이걸로 대체할 수 있음nullable: FK 컬럼의 null 값 허용 (기본값: true)foreignKey: FK 제약 메타데이터@ForeignKey(name="str", value=ContraintMode.XXX) 형식name: DB에 생성될 FK 제약 이름ConstraintMode.CONSTRAINT(기본값): 실제 FK 제약 생성ConstraintMode.NO_CONSTRAINT: DB에는 실제 제약 미생성, 무결성 보장은 애플리케이션에서 책임져야 함사용 예시
@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "team_id", // FK 컬럼명
unique = true, // 유니크 제약 부여 (1:1 관계와 동일한 효과)
nullable = false, // FK 컬럼의 NULL 불허
foreignKey = @ForeignKey(
name = "fk_member_team", // DB에 생성될 FK 제약 이름
value = ConstraintMode.CONSTRAINT // 실제 FK 제약 생성
)
)
private Team team;
// 생성자, 기타 메서드 등...
}
FK는 연관관계 설정 뿐 아니라, DB 무결성을 위한 제약 조건으로 생각할 수 있다.