@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID);
Long id;
String username;
@ManyToOne
@JoinColumn(name ="TEAM_ID")
Team team;
// g,setter
@Entity
public clas Team {
@Id
@Column(name= "TEAM_ID")
Long id;
String name;
// g,setter
@JoinColumn(name ="TEAM_ID") 를 이용해서 Member.team 필드를 TEAM_ID 컬럼과 매핑했다.
따라서 Member.team 으로 외래키를 관리한다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID);
Long id;
String username;
@ManyToOne
@JoinColumn(name ="TEAM_ID")
Team team;
무한 로프에 빠지지 않도록 체크..
public void setTeam(Team team) {
if(!team.getMembers().contains(this)){
team.getMembers().add(this);
}
this.team = team;
}
// g,setter
@Entity
public clas Team {
@Id
@Column(name= "TEAM_ID")
Long id;
String name;
@OneToMany(mappedBy ="team")
List<Member> members = new ArrayList<>();
void addMember(Member member){
this.member = member;
if(member.getTeam() != this)){
member.setTeam(this);
}
// g,setter
연관관계 주인이 아닌 Team.members 필드는 단지 조회를 위한 JPQL 또는 객체 그래프 탐색을 위해 존재하는 것이다.
양방향 연관관계는 항상 서로를 참조해야한다.
편의 메소드는 양쪽에 다 작성할 수 있는데, 양쪽에 다 작성하면 무한루프에 빠질 가능성이 있음으로 조심해서 사용해야한다.
팀 -> 회원
팀과 회원의 일대다 단방향 관계에서 (팀에 연간관계 주인) Team.members 로 회원 테이블의 TEAM_ID 를 관리한다.
이 매핑은 반대쪽 테이블에 있는 외래키를 관리한다. (테이블에서 외래키는 항상 "다" 쪽에 있기 때문에 )
@Entity
public clas Team {
@Id
@Column(name= "TEAM_ID")
Long id;
String name;
@OneToMany
@JoinColumn(name ="TEAM_ID") // MEMBER 테이블의 TEAM_ID (FK)
List<Member> members = new ArrayList<>();
// g,setter
그러므로 일대다 양방향을 보다는 다대일 양방향을 사용하는 것이 바람직한다.
이것도 비효율적이다 다대일 양방향을 사용하는 것이 바람직하다.
주 객체가 대상 객체를 참조하는 것처럼, 주 테이블에 외래키를 두고 대상 테이블을 참조한다.
객체지향 개발자들이 주 테이블에 외래키 방식ㅇㄹ 선호한다.
전통적 데베 개발자들은 대상 테이블에 외래 키를 두는 것을 선호한다.
또 테이블 관계를 일대일엣 일대다로 변경할 때 테이블 구조를 유지할 수 있다
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@OneToOne
@JoinTable(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id
@Column("LOCKER_ID")
private Long id;
private String name;
}
위의 관계에서 데베는 LOCKER_ID 외래키에 유니크 제약 조건을 추가한다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id
@Column("LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
Member member ;
}
단방향 방법 없다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@OneToOne(mappedBy= "member")
private Locker locker;
}
@Entity
public class Locker {
@Id
@Column("LOCKER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
Member member ;
}
보통 데베는 정규화된 테이블 2개로 다대다 를 표현할 수 없다. 일대다 다대일 관계로 풀어내는 연결 테이블 사용한다. (중간에 테이블 하나 더 낀다)
하지만 객체는 객체 2개를 다대다관계로 만들 수 잇다.
@Entity
public class Member {
@Id
@Column(name ="MEMBER_ID")
private String id;
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT",
joinColumns = @JoinColumn(name = "MEMBER_ID"),
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")
)
List<Product> products = new ArrayList<>();
}
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private String id;
}
위의 코드는 @매니투매니와 @조인테이블을 사용해서 연결 테이블을 바로 매핑했다.
(자동적으로 MEMBER_ID, PRODUCT_ID 두개의 컬럼을 갖고 있는 MEMBER_PRODUCT 엔티티를 만들어준다)
다대다 관계 저장 코드
public void save(){
Product productA = new Product("productA" ,"상품A");
em.persist(productA);
Member member1 =new Member("member1","회원1");
member1.getProducts().add(productA); // 연관관계 설정.
em.persist(member1);
회원1과 상품A의 연관관계를 설정했음으로 회원1을 저장할때 연결테이블에도 값이 저장된다.
INSERT INTO PRODUCT ...
INSERT INTO MEMBER ...
INSERT INTO MEMBER_PRODCUT...
public void find(){
Member member = em.find(Member.class ,"member1");
List<Product> products = member.getProducts();
for(Product product : products ){
System.out.printlne(product);
}
SELECT * FROM MEMBER_PRODUCT MP
INNER JOIN PRODUCT P ON P.PRODUCT_ID = MEMBER_PRODUCT.PRODUCT_ID
WHERE MP.MEMBER_ID = ?
역방향도 @매니투매니를 사용한다.
연관관계 주인 역시 설정 해 줘야한다.
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private String id;
@ManyToMany(mappedBy = "products")
List<Member> members = new ArrayList<>();
}
양방향 편의 메서드
public void addProduct(Product product){
if(!product.getMembers().contains(this)){
product.getMembers().add(this);
}
if(!products.contains(product)){
products.add(product)l
}
}
@다대다 매핑을 실무에서 사용하기엔 한계가 있다.
연결테이블에 각 연결된 테이블의 외래키 컬럼 외 다른 컬럼이 필요하다면 @다대다 애노테이션은 사용 불가능하다.
@다대다 말구 연결테이블 엔티티를 따로 정의해서 회원과 상품의 다대다 관계를 맺은 코드가 아래의 코드다
@Entity
public class Member {
@Id
@Column(name ="MEMBER_ID")
private String id;
private String username;
@OneToMany(mappedBy ="member")
List<MemberProduct> memberProducts;
}
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private String id;
private String name ;
// 상품엔티티에서 회원상품 엔티티로 객체 그래프 탐색이 필요하지 않아서 연관관계를 만들지 않았
}
@Entity
@IdClass(MemberProductId.class)
public class MemberProduct{
@Id
@ManyToOne
@JoinColummn(name ="MEMBER_ID")
Member member ; // MemberProductId.member 과 연결
@Id
@ManyToOne
@JoinColummn(name ="PRODUCT_ID")
Product product ; // MemberProductId.product 과 연결
private int orderAmount;
}
public class MemberProductId implements Serializable {
private String member;
private String product;
// hash code and equals ..
}
MemberProduct 를 보면 기본키와 외래키를 사용해서 기본키 + 외래키를 함께 매핑했다. 그리고 @IdClass 를 사용해 복합키를 매핑했다.
JPA는 복합키를 사용하려면 별도의 식별자 클래스를 만들어야한다.
식별자 클래스의 특징
1. 복합키는 별도의 식별자 클래스로 만들어야함.
2. Serializable 를 구현해야함
3. 이퀄 , 헤쉬코드 메소드 구현해야함
4. 기본 생성자 필수
5. 퍼블릭 이여야함
회원 상품은 회원과 상품의 기본키를 받아 자신의 기본키를 사용한다,
데베에서 부모테이블의 기본키를 받아 자신의 기본키 + 외래키로 사용하는 것을 식별관계 라고 부른다.
public void save(){
Member member1 = new Member();
member1.setId("member1");
member1.setUsername("회원1");
em.persist(member1);
Product productA = new Product();
productA.setId("productA");
productA.setName("상품A");
em.persist(productA);
MemberProduct memberProduct = new MemberProduct();
memberProduct.setMember(member1);
memberProduct.setProduct(productA);
memberProduct.setOrderAmount(2);
em.persist(memberProduct);
}
회원 상품 엔티티를 데베에 저장할 때 연관된 회원의 식별자와 상품의 식별자를 가져와 자신의 기본키값으로 사용한다.
public void find(){
MemberProductId memberProductId = new MemberProductId();
memberProductId.setMember("member1"); // member's primary key
memberProductId.setProduct("productA");
MemberProduct memberProduct = em.find(MemberProduct.class, memberProductId);
Member member = memberProduct.getMember();
Product product = memberProduct.getProduct();
}
복합키를 사용하면 매우 복잡하다.
복합키를 사용하지 않고 간단한 다대다 구현.
연결테이블의 기본키를 지정하는 방식 중 데베에서 자동으로 생성해주는 대리키를 사용하는 거이다.
대리키를 연결테이블의 기본키로 사용하고, 회원과 상품의 관계는 외래키로 설정한다.
@Entity
public class MemberProduct {
@Id
@GeneratedValue
@Column(name ="MEMBER_PRODUCT_ID")
Long id; // 데베에서 생성해주는 기본키
@ManyToOne
@JoinColumn(name="MEMBER_ID")
List<Member> members;
@ManyToOne
JoinColumn(name ="PRODUCT_ID")
List<Product> products;
int productAmount;
}
저장 코드
Member member =new Member("member1","회원1");
em.persist(member);
Product product = new Product("productA", "상품1");
em.persist(product);
MemberProduct memberProduct = new MemberProduct();
memberProduct.setMember(member);
memberProduct.setProduct(product);
memberProduct.setOrderAmount(2);
em.persist(memberProduct);
조회 코드
Long memberProductId = 1L;
MemberProduct memberProduct1 = em.find(MemberProduct.class, memberProductId);
Member member = memberProduct1.getMember();
Product product = memberProduct.getProduct();
즉 다대다 연관관계를 정의할때는 연결테이블의 식별키를 어떻게 구성할지 결정해야한다.
1. 부모로부터 받아온 기본키를 기본키 + 외래키로 사용하는 식별관계
2. 받아온 식별자는 외래키로만 사용하고 새로운 식별자를 추가한다 비식별관계