@GetMapping("/prac1")
fun practice1() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
// transaction
val transaction = em.transaction
transaction.begin()
var team = Team()
team.teamName = "테스트 팀네임"
val member = Member()
member.memberName = "테스트 멤버네임"
member.teamId = team.id
em.persist(member)
transaction.commit()
em.close()
emf.close()
}
team id로 매핑은 되지만 DB와 DB 사이를 억지로 집어넣은 느낌이 든다.
동작도 가능하지만 객체를 집어넣어서 매핑하는게 객체지향적으로 좋다.
이렇게 나온 개념이 ManyToOne, OneToMany라고 한다.
@Entity
class Member {
@Id @GeneratedValue
var id: Long = 0
@Column(name = "title")
var memberName: String = ""
@Column(name = "team_id")
var teamId: Long = 0L
var team: Team
}
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
테이블: 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
객체 : 참조를 사용해서 연관된 객체를 찾는다.
==> 테이블과 객체 사이에는 이런 큰 간격이 있다.
@Entity
class Member {
@Id @GeneratedValue
var id: Long = 0
@Column(name = "title")
var memberName: String = ""
@Column(name = "team_id")
var teamId: Long = 0L
@ManyToOne
var team: Team? = null
}
team id 를 외래키(FK)로 지정한다.
이렇게 단방향만으로도 모든 매핑이 가능!
그래서 설계할 때 단방향으로만 테이블 연관관계를 맺어놓고 만약, MEMBER가 TEAM을 참조해야 하는 경우에만 양방향으로 바꾸는 것! 처음부터 양방향 막 쓰지 않도록 주의!!
@GetMapping("/prac1")
fun practice1() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
// transaction
val transaction = em.transaction
transaction.begin()
var team = Team()
team.teamName = "테스트 팀네임"
em.persist(team)
val member = Member()
member.memberName = "테스트 멤버네임"
member.team = team
em.persist(member)
transaction.commit()
em.close()
emf.close()
}
@Entity
class Member(
@Id
@GeneratedValue()
@Column(name = "MEMBER_ID")
val id: Long = 0,
@Column(name = "USERNAME")
var username: String,
@ManyToOne
@JoinColumn(name = "TEAM_ID")
val team: Team
)
일대다 중 멤버가 FK의 주인이므로 멤버에 팀 객체를 담는다.
=> FK의 주인에 One객체를 집어 넣는다.
객체지향적이고 DB문제도 해결(DB를 어거지로 묶는 위의 예시 해결)
=> 이렇게 하면 join의 성질도 갖게 된다.
만약 팀에 멤버 리스트가 존재한다면?
@Entity
class Team {
@Id @GenenratedValue
var id: Long = 0
@Column(name ="title")
var teamName: String = ""
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true)
val member: MutableList<Member> = mutableListOf()
}
한 팀에 여러멤버가 들어오니 멤버 매핑할 때 리스트로 설정
이렇게 해도 테이블 연관관계는 단방향과 변함이 없다.
양방향으로 하든 안하든 단방향으로 하면 DB 연결이 다 끝난다.
그래서 처음에 작업할 때 단방향으로 다 연결하고 나중에 도메인 로직을 작성할 때 댓글을 통해서 게시글을 찾게 된다면 양방향 연결하기(서비스 로직)
양방향은 객체지향 문제이기에 DB와는 아무관계가 없다.
따라서 연관관계의 주인은 연관 관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만, 주인이 아니면 조회만 가능하다.
주인은 외래키가 있는 곳으로 지정하면 된다.
Member엔티티가 외래 키를 가지고 있으믕로 연관관계의 주인이 된다.
연관관계의 주인이 아닌 객체는 mappedBy 속성을 사용한다.
<양방향 매핑 규칙>
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능(team에 해당)
@GetMapping("/prac1")
fun practice1() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
// transaction
val transaction = em.transaction
transaction.begin()
val member = Member()
member.memberName = "새로운 멤버네임"
em.persist(member)
val team = Team()
team.teamName = 새로운 팀네임"
team.member.add(member)
em.persist(team)
transaction.commit()
em.close()
emf.close()
}
만약 주인이 아닌 방향(역방향)만 연관관계 설정을 하면 Member 테이블의 TEAM_ID는 NULL 값이 들어간다.
올바르게 사용하려면,
@GetMapping("/prac1")
fun practice1() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
// transaction
val transaction = em.transaction
transaction.begin()
val team = Team()
team.teamName = "Z새로운 팀네임11"
em.persist(team)
val member = Member()
member.memberName = "새로운 멤버네임11"
member.team = team
em.persist(member)
transaction.commit()
em.close()
emf.close()
}
멤버에다가 팀을 넣어야 팀 아이디가 DB에 들어간다.
mappedBy Member List가 읽기 전용이라 멤버 추가를 JPA가 무시해버린다.
매번 양방향의 코드를 추가하는 것이 힘들기에 연관관계 편의 메서드 생성 권장
@Entity
class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
var id: Long
@Column(name = "USERNAME")
val username: String
@ManyToOne
@JoinColumn(name = "TEAM_ID")
val team: Team
// 연관관계 편의 메서드
fun addTeam(team: Team) {
this.team = team;
team.getMembers().add(this);
}
... 생략
}