프로젝트 세팅
persistence.xml
생성<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="javax.persistence.jdbc.user" value="springjpa" />
<property name="javax.persistence.jdbc.password" value="springjpa" />
<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
<!-- 테이블 생성 옵션 ( 필요할 때만 사용 ) -->
<property name="hibernate.hbm2ddl.auto" value="create"/>
<!-- 옵션 -->
<!-- 콘솔에 하이버네이트가 실행하는 SQL문 출력 -->
<property name="hibernate.show_sql" value="true" />
<!-- SQL 출력 시 보기 쉽게 정렬 -->
<property name="hibernate.format_sql" value="true" />
<!-- 쿼리 출력 시 주석(comments)도 함께 출력 -->
<property name="hibernate.use_sql_comments" value="true" />
</properties>
</persistence-unit>
</persistence>
ㅇ 이론
- 객체와 테이블 연관관계의 차이를 이해
- 객체의 참조와 테이블의 외래 키를 매핑
- 연관관계 주인 ( Owner )
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
시작
Member.java
생성@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamid;
}
Team.java
생성@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
JpaMain.java
수정...
try {
Team team = new Team();
team.setName("TeamA");
// 영속상태가 되면, PK의 값이 세팅이 된 후 영속상태가 된다.
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
// select
// 어느팀 소속인지 알고 싶을 때 JPA or DB에게 계속 물어봐야 한다.
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
System.out.println("findTeam : " + findTeam.getName());
tx.commit();
...
JpaMain.java
실행 시 )Member.java
수정@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
// @Column(name = "TEAM_ID")
// private Long teamId;
@ManyToOne // Team 이 하나
@JoinColumn(name = "TEAM_ID") // 관계 컬럼을 적어준다. TEAM_ID와 조인해야 한다.
private Team team;
}
JpaMain.java
수정...
try {
Team team = new Team();
team.setName("TeamA");
// 영속상태가 되면, PK의 값이 세팅이 된 후 영속상태가 된다.
em.persist(team);
Member member = new Member();
member.setName("member1");
// member.setTeamId(team.getId());
member.setTeam(team);
em.persist(member);
// 강제 db 쿼리를 보고 싶을때
em.flush();
em.clear();
// select
// find시에 1차캐시에서 가지고 와서 select 문이 없다.
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam : " + findTeam.getName());
tx.commit();
...
JpaMain.java
실행 시 )이론
- 양방향 매핑
- 현재 객체는 Member가 Team을 가졌으나, Team은 Member를 가지지 못한다.
- 객체 참조와 외래키의 가장 큰 차이점
- 테이블은 FK만 있으면 양쪽의 연관관계를 알 수 있다.
- Member : Team = : n : 1 => @ManyToOne
- Team : Member = 1 : n => @OneToMany
- 테이블 연관관계
- 관계 1개
- Member 테이블 입장에서 Team 테이블 조인 가능
- Team 테이블 입장에서 Member 테이블 조인 가능
- 객체 연관관계
- 관계 2개
- Member 객체에서 Team 객체로 연관관계 1개 ( 단방향 )
- Team 객체에서 Member 객체로 연관관계 1개 ( 단방향 )
- 관리의 딜레마
- 둘 중 하나로 외래키를 관리해야 한다.
- Member에서 Team으로 가는 Team 참조 값과, Team에서 Member로 가는 member 참조 값이 있다.
- Member에서 Team값이 수정 되었을 때 Member table의 TEAM_ID가 수정되야 하는지, Team에 있는 members를 수정했을 때 Member table의 TEAM_ID가 수정되야 하는지?
-> DB 입장에서는 Member table에 있는 TEAM_ID만 update하면 된다 -> 룰 ( 주인 ) 이 생긴다.
- 연관관계의 주인 ( Owner ) - 양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리 ( 등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용 x
-> mappedBy : 내가 누군가에 의해서 mapping 되었다는 뜻
- 주인이 아니면 mappedBy 속성으로 주인 지정
Team.java
수정@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> member = new ArrayList<Member>();
}
persistence.xml
<property name="hibernate.hbm2ddl.auto" value="create"/>
JpaMain.java
수정...
try {
Team team = new Team();
team.setName("TeamA");
// 영속상태가 되면, PK의 값이 세팅이 된 후 영속상태가 된다.
em.persist(team);
Member member = new Member();
member.setName("member1");
// member.setTeamId(team.getId());
member.setTeam(team);
em.persist(member);
// 이때까진 Member 객체에만 team이 할당되어있고
// Team 객체에는 Member가 할당되어있지않다. 따라서
// 두 데이터를 DB에서 다시 받아오게 하기위함
em.flush();
em.clear();
// select
// find시에 1차캐시에서 가지고 와서 select 문이 없다.
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam : " + findTeam.getName());
// 양방향 매핑
Member findSideMember = em.find(Member.class, member.getId());
List<Member> members = findSideMember.getTeam().getMember();
for( Member m : members) {
System.out.println("result : " + m.getName());
}
tx.commit();
...
- 양방향 연관관계에서 주의 할 점
- 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정해 주어야 한다.
- 연관관계 편의 메서드를 생성해 주어야 한다.
- 양방향 매핑시에 무한 루프를 조심해야 한다.
-> toString(), lomlok lib를 조심해야 한다.
JpaMain2.java
생성public class JpaMain2 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
/*
* 양방향 매핑시 가장 많이 하는 실수
*/
Member member = new Member();
member.setName("member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMember().add(member);
em.persist(team);
em.flush();
em.clear();
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
}
}
JpaMain2.java
수정...
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
System.out.println("========================================");
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMember();
for( Member m : members ) {
System.out.println("m : " + m.getName());
}
System.out.println("========================================");
...
JpaMain2.java
...
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
// em.flush();
// em.clear();
// 객체 지향적인 입장에서 양쪽에 모두 값을 넣어주어야 한다.
// 양방향 매핑시에는 양쪽에 값을 모두 입력해주어야 한다.
// DB를 다시 다녀오지 않고 객체 상태로만 사용 할 수 있다.
team.getMember().add(member);
System.out.println("========================================");
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMember();
for( Member m : members ) {
System.out.println("m : " + m.getName());
}
System.out.println("========================================");
...
Member.java
수정 )Member.java
...
@ManyToOne
@JoinColumn(name = "TEAM_ID")
@Setter(value = AccessLevel.NONE)
private Team team;
// 일반적으로 setter의 형태가 아니면 메서드 이름을 바꿔준다.
// 추후 소스코드를 봤을 때 단순 setter의 작업이 아닌 중요한 작업을 진행하는지를 파악 할 수 있다.
public void changeTeam(Team team) {
this.team = team;
team.getMember().add(this); // this : 나 자신의 인스턴스를 넣어준다.
}
JpaMain2.java
...
Member member = new Member();
member.setName("member1");
// member.setTeam(team);
member.changeTeam(team);
em.persist(member);
// em.flush();
// em.clear();
// team.getMember().add(member);
System.out.println("========================================");
...
Team.java
수정 )Team.java
수정...
public void addMember (Member member ) {
member.setTeam(this);
this.member.add(member);
}
...
JpaMain.java
수정...
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
// member.setTeam(team);
// member.changeTeam(team);
em.persist(member);
team.addMember(member);
...
무한루프
-> Member.java , Team.java에 각각 toString 만들어주기
@ToString 어노테이션이나 @DATA 어노테이션을 이용하게되면 고정된 toString값이 나오므로 서로가 서로를 끊임없이 바라보는 무한루프 발생 가능 lomlok lib를 조심해야 한다.
Member.java
수정...
@Override
public String toString() {
return "Member [id=" + id + ", name=" + name + ", team=" + team + "]";
}
...
Team.java
수정...
@Override
public String toString() {
return "Team [id=" + id + ", name=" + name + ", member=" + member + "]";
}
...
- 단방향 매핑만으로도 이미 연관관께 매핑은 완료
- 양방향 매핑은 반대 방향으로 조회기능이 추가 된 것 뿐.
- 양방향 사용 이유 : JPQL에서 역 방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요 할 때 추가해도 됨 ( 테이블에 영향을 주지 않음 )
- 결론: 객체 입장에서 양방향 매핑이 필수는 아님.
- 연관관계의 주인을 정하는 기준
- 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
- 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함
Member.java / Team 의 @Entity 주석처리