자바 ORM 표준 JPA 프로그래밍 : 교보문고
자바 ORM 표준 JPA 프로그래밍 - 기본편 : 인프런
package org.example.javamain.Entity;
import javax.persistence.*;
@Entity
public class Member3
{
@Id
@GeneratedValue
private Long id;
@Column(name = "USER_NAME")
private String userName;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
@Entity
public class Team
{
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String teamName;
현재 Team 에서는 Member 를 조회할 방법이 없다
데이터베이스
의 경우 외래키 를 JOIN 하여
연관관계에 있는 두개의 테이블을 동시에 조회할 수 있다.select * from Member m JOIN Team t on m.TEAM_ID = t.TEAM_ID
select * from Team t JOIN Member m on m.TEAM_ID = t.TEAM_ID
그러기 위해서 외래키가 없는 Team 객체에 다음과 같은 필드를 추가한다.
@OneToMany(mappedBy = "team")
private List<Member3> members = new ArrayList<>();
Team 과 Member 의 연관관계는 1 : N 임으로
OneToMany
mappedby
속성을 사용하여 Member3 객체의 어느 필드와 연결이 되어 있는지 나타낸다.
@OneToMany(mappedBy = "team1")
private List<Member3> members = new ArrayList<>();![]
Exception in thread "main" org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: org.example.javamain.Entity.Member3.team1 in org.example.javamain.Entity.Team.members
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:785)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:736)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1696)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1664)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:287)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
mappedby 로 타겟이 된 필드를 찾지못하여 예외가 발생하는 것을 볼 수 있다.
main : 연관관계 주인이 아닌 Team이 할당하는 경우
public static void main(String[] args)
{
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try
{
Member3 member3 = new Member3();
member3.setUserName("member3");
em.persist(member3);
Team team = new Team();
team.setTeamName("A TEAM");
team.getMembers().add(member3);
em.persist(team);
et.commit();
}
catch (Exception e)
{ et.rollback(); }
finally
{ em.close(); }
emf.close();
}
지금의 경우 양방향 Mapping 이 설정되어 있지만 연관관계의 주인인
Member에서 값을 조작하는 것이 아닌
Team 에서 Member 값을 할당했다.
연관관계에서 연관관계 주인이 아니라면 읽기 전용 상태이기 때문에
할당되지 않는다.
main : 연관관계 주인인 Member 가 값을 할당하는 경우
public static void main(String[] args)
{
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try
{
Team team = new Team();
team.setTeamName("A TEAM");
em.persist(team);
Member3 member3 = new Member3();
member3.setUserName("member3");
member3.setTeam(team);
em.persist(member3);
et.commit();
}
catch (Exception e)
{ et.rollback(); }
finally
{ em.close(); }
emf.close();
}
양쪽에 값을 할당해주는 것이 좋다.
main : 연관관계 주인인 Member 만 Team 을 할당하는 경우
try
{
Team team = new Team();
team.setTeamName("A TEAM");
em.persist(team);
Member3 member3 = new Member3();
member3.setUserName("member3");
member3.setTeam(team);
em.persist(member3);
Team findTeam = em.find(Team.class, team.getId());
List<Member3> members = findTeam.getMembers();
System.out.println(":::::::::::::::::::::::");
for (Member3 member : members) {
System.out.println("member : "+member.getUserName());
}
System.out.println(":::::::::::::::::::::::");
et.commit();
}
Transaction 이 DB 에 적용되기 이전에 Team에서 Member를 조회하는 경우
find
메소드로 조회가 되는team
객체는 1차 cache에서 조회되는 객체로 member 가 할당되지 않은 상태이기 때문에
member 가 조회되지 않는다.
main : Member 와 Team 둘다 값을 할당하는 경우
try
{
Team team = new Team();
team.setTeamName("A TEAM");
em.persist(team);
Member3 member3 = new Member3();
member3.setUserName("member3");
member3.setTeam(team);
em.persist(member3);
team.getMembers().add(member3);
Team findTeam = em.find(Team.class, team.getId());
List<Member3> members = findTeam.getMembers();
System.out.println(":::::::::::::::::::::::");
for (Member3 member : members) {
System.out.println("member : "+member.getUserName());
}
System.out.println(":::::::::::::::::::::::");
et.commit();
}
다음 코드 처럼 선언하는 경우 코드가 복잡해지고 빼먹는 경우가 많기 때문에
@Entity
public class Member3
{
...
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
다음과 같이 setter 에 한쪽에서 연관관계가 Mapping 될 때 동시에
진행될 수 있게 따로 정의한다.
toString 이나 ToString 을 정의해주는 Lombok, JSON 라이브러리 등을 사용할 때 양방향 관계의 Entity 를 사용하는 경우 무한루프가 발생하지 않도록 조심해야한다.
ToString
으로 예를 들어보자
@Override
public String toString() {
return "Team{" +
"id=" + id +
", teamName='" + teamName + '\'' +
", members=" + members +
'}';
}
@Override
public String toString() {
return "Member3{" +
"id=" + id +
", userName='" + userName + '\'' +
", team=" + team +
'}';
}
try
{
Team team = new Team();
team.setTeamName("A TEAM");
em.persist(team);
Member3 member3 = new Member3();
member3.setUserName("member3");
member3.setTeam(team);
em.persist(member3);
team.getMembers().add(member3);
Team findTeam = em.find(Team.class, team.getId());
System.out.println(findTeam);
et.commit();
}
Team에서 toString 을 선언할때 member 안의 toString Method 가 선언이 되고 거기 안에서 다시 Team 필드에 대한 toString 메소드가 실행되는 무한루프가 발생할 수 있다 .
양방향 Mapping 은 Table 구조에 영향을 주지 않기 때문에
단방향으로 객체 설계를 한다음 필요 시 양방향으로 변환하는 방법이 좋다고 한다.