김영한님의 자바 ORM 표준 JPA 프로그래밍 강의와 책을 바탕으로 진행되는 스터디입니다
@Entity
JPA를 사용해서 테이블과 매핑할 클래스에 붙이는 어노테이션
@Table
엔티티와 매핑할 테이블을 지정하는 어노테이션
JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원
properties
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.show-sql=true
yaml
spring:
jpa:
hibernate:
ddl-auto: create
show-sql: true
ddl-auto: 자동으로 테이블을 생성할 지 결정하는 속성
JPA 자체적으로도 스키마 자동 생성 기능을 지원(update, validate 지원 x)
@Column(name="role_type")
과 같은 식으로 컬럼명을 지정하지 않으면 필드명을 그대로 컬럼명으로 사용hibernate.naming.implicit-strategy
속성을 통해 전략을 변경해 줄 수 있음hibernate.naming.physical-strategy
속성을 통해 물리적 이름도 변경할 수 있음length
속성을 통해 문자 크기 지정(varchar(10)
), nullable=false
속성을 통해 not null 제약 조건 적용
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME", nullable = false, length = 10)
private String username;
...
}
@Id
어노테이션을 통해서 기본 키 매핑 가능
기본 키 조건
기본키로는 자연 키와 대리 키 사용 가능
@GeneratedValue
어노테이션을 추가하여기본 키 직접 생성 대신 데이터베이스에서 제공하는 기본 키 자동 생성 기능 사용 가능
AUTO_INCREMENT
)em.persist
와 동시에 INSERT를 해줘야 함@SequenceGenerator
를 사용해서 시퀀스 생성기 등록em.persist
를 호출할 때 데이터베이스 시퀀스를 사용해서 식별자 조회 → 엔티티에 식별자 할당 → 영속성 컨텍스트에 저장 → 트랜잭션 플러시할 때 데이터베이스에 저장@TableGenerator
를 사용해서 테이블 키 생성기를 등록SELECT
쿼리를 사용하고 값을 증가시키기 위해 UPDATE
쿼리 사용IDENTITY
, SEQUENCE
, TABLE
중 하나를 선택@Column
: 객체 필드를 테이블 컬럼에 매핑@Column
을 생략하면 대부분 @Column
속성의 기본값이 적용@Enumerated
: enum 타입을 매핑할 때 사용@Temporal
: 날짜 타입(Date
, Calendar
)을 매핑할 때 사용Date
와 가장 유사한 datetime(MySQL) 또는 timestamp(H2, 오라클, PostgreSQL) 타입으로 매핑@Lob
: BLOB, CLOB 타입과 매핑@Transient
: 해당 필드를 매핑하지 않음@Access
: JPA가 엔티티 데이터에 접근하는 방식 지정@Id
어노테이션이 붙은 위치를 기준으로 결정Member
가 Team
을 필드로 가지는 연관관계
meber.getTeam
으로 가능가장 기초적인 다대일[N:1] 연관관계(Member
(N)가 Team
(1)을 필드로 가지는 연관관계)
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void setTeam(Team team) {
this.team = team;
}
...
}
@Entity
@Getter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
...
}
Member
→ Team
단방향 연관관계(Member → Team만 조회 가능)
@ManyToOne
@ManyToOne
의 기본값은 FetchType.EAGER@OneToMany
의 기본값은 FetchType.LAZY@JoinColumn(name=”TEAM_ID”)
TEAM_ID
를 FK로 하여 연관관계를 맺음@Column
의 속성과 같음Team team1 = new Team("team1", "팀1");
em.persist(tema1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
Member
→ Team
참조이므로 연관관계를 맺어줄 때 Team
은 반드시 영속상태여야 함MEMBER
테이블의 TEAM_ID
값으로 team1의 id값이 들어감연관관계를 조회하는 방법은 2가지
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
String jpql = "select m from Member m join m.team t where t.name = :teamName";
List<Member> members = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
member.setTeam
으로 참조하는 대상만 바꿔주면 JPA가 트랜잭션이 커밋되는 시점에 변경 사항을 데이터베이스에 반영Member membe1 = em.find(Member.class, "member1");
member.setTeam(null);
Team
→ Member
로 접근하는 관계를 추가
@Entity
@Getter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
@OneToMany
@ManyToOne
의 반대엄밀히 말해 객체에는 양방향 연관관계란 존재하지 않음. 단방향 연관관계 2개가 존재.
반면 데이터베이스 테이블은 외래 키 하나로 양방향 조인이 가능.
Member.team
이 연관관계의 주인이므로 Team.members
에는 mappedBy 속성을 지정해주어야 함team
으로 매핑되므로 mappedBy 값으로 team
을 주면 됨@ManyToOne
에는 mappedBy 속성이 존재하지 않음Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
// 연관관계의 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력됨
// team1.getMembers().add(member1);
주의
Member member1 = new Member("member1", "회원1");
em.persist(member1);
Team team1 = new Team("team1", "팀1");
team1.getMembers().add(member1);
em.persist(team1);
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
team1.getMembers().add(member1);
member.setTeam(team)
과 team.getMembers().add(member)
를 모두 호출할 때 실수로 누락 가능성 있음
→ 두 코드가 하나의 코드인 것 처럼 사용해야 안전
public class Member {
...
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
이렇게 Member.setTeam 하나로 양방향의 연관관계를 모두 입력해주도록 하면 연관관계를 맺어주는 메서드를 한 번만 호출해도 됨 (연관관계 편의 메서드)
member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMembers.get(0); // member1 조회됨
team
객체의 members
에는 아직 member
이 남아있음public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
Member.toString
에서 getTeam
을 호출하고, Team.toString
에서 getMember
를 호출하면 어느쪽에서 toString
을 호출하든 무한루프에 빠짐(엔티티를 JSON으로 변환할 때 많이 생기는 문제)
@ToString
사용 시에 해당 문제가 자주 발생