이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 듣고 정리한 글입니다.
‘객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.’ -조영호(객체지향의 사실과 오해)
회원과 팀이 있다.
회원은 하나의 팀에만 소속될 수 있다.
회원과 팀은 다대일 관계이다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Column(name = "TEAM_ID") // 💡 참조 대신 외래 키 그대로 사용
private Long teamId;
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// code ..
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId()); // 💡 외래 키 식별자를 직접 다룸
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
}
}
SELECT * FROM MEMBER;
SELECT * FROM TEAM;
SELECT * FROM MEMBER m
JOIN TEAM t
ON m.team_id=t.team_id;
Member findMember = em.find(Member.class, member.getId()); // 조회
Team findTeam = em.find(Team.class, team.getId()); // 연관관계 없음
System.out.println("findTeam = " + findTeam.getName());
→ 서로 연관관계가 없기때문에 식별자로 다시 조회한다. 객체 지향적인 방법은 아니다.
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
왜? : 패러다임의 차이
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// 💡 객체의 참조와 테이블의 외래 키를 매핑
@ManyToOne // Member 입장에서 many, Team 입장에서 one
@JoinColumn(name = "TEAM_ID") // MEMBER 테이블의 'TEAM_ID' 칼럼과 매핑
private Team team;
}
try {
// 연관관계 저장
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 💡 단방향 연관관계 설정, 참조 저장
em.persist(member);
// 값을 깔끔하게 조회하기 위해 추가
em.flush();
em.clear();
// 조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); // 💡 참조를 사용해 연관관계 조회
System.out.println("findTeam = " + findTeam.getName());
tx.commit();
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
// 💡 컬렉션 추가
@OneToMany(mappedBy = "team") // 무엇과 연결되어 있는지 (Team 변수명)
private List<Member> members = new ArrayList<>();
// OneToMany이기 때문에, List 컬렉션이 필요
// null 포인트 안뜨게 하기 위해 `new ArrayList<>()`로 초기화하는 것이 관례
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
try {
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
em.persist(member);
em.flush();
em.clear();
// 조회
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
// 조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); // 💡 역방향 조회
DB 입장에서는 MEMBER 테이블의 TEAM_ID(FK)만 업데이트되면 된다.
→ 둘 중 하나로 외래 키를 관리해야 한다.
⇒ 연관관계의 주인(Owner)
@JoinColumn(name = "FK 칼럼 이름")
)@OneToMany(mappedBy = "주인 이름")
) @ManyToOne
@JoinColumn(name = "TEAM_ID") // 테이블과 매핑
private Team team;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
@ManyToOne
연관관계의 주인이 아닌 것에 값을 입력할 경우
JpaMain.java
try { Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setUsername("member1"); // 역방향(주인이 아닌 방향)만 연관관계 설정 team.getMembers().add(member); em.persist(member); // ... }
Team.java
@OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>();
JPA에서 DB를 변경할 때 가짜 맵핑(mappedBy 객체)을 보지 않음.
⇒ team.getMembers().add(member);
가 아닌, member.setTeam(team);
양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.
JpaMain.java
try { Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setUsername("member1"); member.setTeam(team); // 💡 em.persist(member); em.flush(); em.clear(); // 1차 캐시가 깨끗해진 상태 Team findTeam = em.find(Team.class, team.getId()); // 1) DB에서 새로 데이터를 가져와 1차 캐시에 저장 // members 리스트에 들어간 것이 없음 List<Member> members = findTeam.getMembers(); // 2) 데이터들을 리스트 컬렉션에 넣음 // 리스트에 세팅한 것이 없음에도 값이 출력됨 for (Member m : members) { System.out.println("m = " + m.getUsername()); } // ... }
em.flush(); em.clear();
를 실행하지 않았을 경우JpaMain.java
try {
Team team = new Team();
team.setName("TeamA");
em.persist(team); // 순수한 team 객체 상태
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // member 객체에 team 객체 설정
em.persist(member);
// members 리스트에 들어간 것이 없음
team.getMembers().add(member); // team 객체에 member 객체 추가
// em.flush();
// em.clear();
Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시
List<Member> members = findTeam.getMembers();
// 1차 캐시에 있는 것들이 출력됨
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
// ...
}
em.persist(team);
실행으로 영속성 컨텍스트에 있는 것들이 출력됨team.getMembers().add(member);
가 없을 경우em.persist();
)로는 해당 명령어가 없을 경우 못 읽어들인다.team.getMembers().add(member);
을 지우고,changeTeam()
메소드 추가JpaMain.java
try {
Team team = new Team();
team.setName("TeamA");
em.persist(team); // 순수한 team 객체 상태
Member member = new Member();
member.setUsername("member1");
member.changeTeam(team);
em.persist(member);
// em.flush();
// em.clear();
Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시
List<Member> members = findTeam.getMembers();
// 1차 캐시에 있는 것들이 출력됨
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
// ...
}
Member.java
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this); // this = Member
}
Team을 기준으로 Member 집어넣기
- 단, 양방향으로 편의 메소드를 작성할 경우에 문제를 일으킬 수 있기 때문에,
Member.java의changeTeam()
메소드를 지워주어야 함. (한 쪽에서만 연관관계 편의 메소드 작성)
JpaMain.java
team.addMember(member);
Team.java
public void addMember(Member2 member) { member.setTeam(this); members.add(member); }