JPA 프로그래밍 4. 관계 맵핑 - KwangtaekJung/inflearn-spring-data-jpa-keesun GitHub Wiki

JPA 프로그래밍: 1대다 맵핑

  • 관계에는 항상 두 엔티티가 존재 합니다.

    • 둘 중 하나는 그 관계의 주인(owning)이고
    • 다른 쪽은 종속된(non-owning) 쪽입니다.
    • 해당 관계의 반대쪽 레퍼런스를 가지고 있는 쪽이 주인.
  • 단방향에서의 관계의 주인은 명확합니다.

    • 관계를 정의한 쪽이 그 관계의 주인입니다.

단방향 @ManyToOne

  • 단방향 @ManyToOne
    • 기본값은 FK 생성
@Entity
@Getter @Setter
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;
}
@Entity
@Getter @Setter
public class Study {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Account account;
}
@Component
@Transactional
public class AppRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("jung");
        account.setPassword("spring");

        Study study = new Study();
        study.setName("Spring Data JPA");
        study.setAccount(account);

        Session session = entityManager.unwrap(Session.class);
        session.save(account);
        session.save(study);
    }
}
springdata=# select * from account;
 id | password | username
----+----------+----------
  1 | spring   | jung
(1 row)

springdata=# select * from study;
 id |      name       | account_id
----+-----------------+------------
  2 | Spring Data JPA |          1
(1 row)

단방향 @OneToMany

  • 단방향 @OneToMany
    • 기본값은 조인 테이블 생성
@Entity
@Getter @Setter
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    @OneToMany
    private Set<Study> studies = new HashSet<>();
}
@Entity
@Getter @Setter
public class Study {

    @Id @GeneratedValue
    private Long id;

    private String name;
}
@Component
@Transactional
public class AppRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("jung");
        account.setPassword("spring");

        Study study = new Study();
        study.setName("Spring Data JPA");

        account.getStudies().add(study);

        Session session = entityManager.unwrap(Session.class);
        session.save(account);
        session.save(study);
    }
}
springdata=# select * from account;
 id | password | username
----+----------+----------
  1 | spring   | jung
(1 row)

springdata=# select * from study;
 id |      name
----+-----------------
  2 | Spring Data JPA
(1 row)

springdata=# select * from account_studies;
 account_id | studies_id
------------+------------
          1 |          2
(1 row)

여기서 생성된 account_studies 테이블은 수작업으로 drop table account_studies; 해줘야 한다.
spring.jpa.hibernate.ddl-auto=create로 설정하면 기존에 있던 테이블만 지우고 새로 만든다.

단방향 @OneToMany + @JoinColumn

  • 단방향 @OneToMany + @JoinColumn
    • Account가 아닌 Study 테이블에 join column이 생성됨.
    • 참고로, @JoinColumn에 name을 속성을 설정하지 않으면 studies_id 이름으로 컬럼이 생성됨.(naming 기본 전략?)
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    @OneToMany
    @JoinColumn(name = "account_id")
    private Set<Study> studies = new HashSet<>();
@Entity
@Getter @Setter
public class Study {

    @Id @GeneratedValue
    private Long id;

    private String name;
    create table account (
       id bigint not null,
        password varchar(255),
        username varchar(255) not null,
        primary key (id)
    )

    create table study (
       id bigint not null,
        name varchar(255),
        account_id bigint,
        primary key (id)
    )

양방향

  • FK 가지고 있는 쪽이 오너 따라서 기본값은 @ManyToOne 가지고 있는 쪽이 주인.

  • 주인이 아닌쪽(@OneToMany쪽)에서 mappedBy 사용해서 관계를 맺고 있는 필드를 설정해야 합니다.

    • mappedBy를 설정하지 않으면 서로 연관 관계가 없는 단방향 2개가 설정된 것이다.
  • 양방향

    • @ManyToOne (이쪽이 주인)
      • 기본값은 FK 생성
    • @OneToMany(mappedBy)
    • 주인한테 관계를 설정해야 DB에 반영이 됩니다.
      • 주인이 아닌 쪽에 설정하면 DB에 반영되지 않는다.
@Entity
@Getter @Setter
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    @OneToMany(mappedBy = "owner")
    private Set<Study> studies = new HashSet<>();
}
@Entity
@Getter @Setter
public class Study {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Account owner;
}
@Component
@Transactional
public class AppRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("jung");
        account.setPassword("spring");

        Study study = new Study();
        study.setName("Spring Data JPA");

        account.getStudies().add(study); // 없어도 되지만 객체 관계를 생각했을 때 같이 설정함.
        study.setOwner(account);  // 주인쪽에 설정해줘야 함.

        Session session = entityManager.unwrap(Session.class);
        session.save(account);
        session.save(study);
    }
}
springdata=# select * from account;
 id | password | username
----+----------+----------
  1 | spring   | jung
(1 row)

springdata=# select * from study;
 id |      name       | owner_id
----+-----------------+----------
  2 | Spring Data JPA |        1
(1 row)

아래와 같이 연관 관계에 대한 설정(convenient method)을 Entity안에 넣는 것이 일번적임.

@Entity
@Getter @Setter
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    @OneToMany(mappedBy = "owner")
    private Set<Study> studies = new HashSet<>();

    //convenient method
    public void addStudy(Study study) {
        this.getStudies().add(study);
        study.setOwner(this);
    }

    public void removeStudy(Study study) {
        this.getStudies().remove(study);
        study.setOwner(null);
    }
}
@Component
@Transactional
public class AppRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("jung");
        account.setPassword("spring");

        Study study = new Study();
        study.setName("Spring Data JPA");

//        account.getStudies().add(study); // 없어도 되지만 객체 관계를 생각했을 때 같이 설정함.
//        study.setOwner(account);  // 주인쪽에 설정해줘야 함.
        account.addStudy(study);

        Session session = entityManager.unwrap(Session.class);
        session.save(account);
        session.save(study);
    }
}
질문
단방향 관계와 양방향 관계 관련되어 문의 드립니다.
slr과르
2018.10.22 AM 00:29
2
@ManyToOne 단방향으로 관계 설정하는 경우와

@ManyToOne+@OneToMany를 통해 양방향 관계로 설정하는 경우,

DB 상으로 생성된 컬럼이나 데이터가 동일한데 이 두가지에 대한 차이점은 무엇인가요?

DB 상에 데이터가 동일한데 굳이 양방향 관계를 사용하는 이유가 궁금합니다.

감사합니다

2
0
백기선
백기선
2018.10.22 AM 00:40
DB 상으론 단방향이든 양방향이든 동일하지만, 객체 관점에서 보면 다릅니다.

JPA를 사용하는 이유 중 하나가 객체지향적으로 프로그래밍을 하기 위함인데, 객체 순회를 어떻게 하느냐에 따라 단방향으로 만들지, 양방향으로 만들지 정해집니다.

가령, Event와 Location이라는 엔티티가 있다고 가정해보죠. Event에서 Location은 당연히 참조해야 할테니 Event -> Location이라는 방향은 보통 레퍼런스로 정의하게 될 겁니다. 그런데 만들 다 보니 어떤 장소에서 열렸던 이벤트 목록도 자주 보여주게 된다면? 그럼 아에 둘의 관계를 양방향으로 만들고, Location을 읽어온 뒤에, Location에서 열렸던 List 목록을 객체로 순회하며 보여줄 수도 있겠죠.

물론, 그렇게 하지않고, 쿼리를 이용해서 조회해올 수도 있겠지만, 가능한한 객체 중심으로 생각하고 코딩하기 편하게 해주는게 JPA의 목적이니 저라면 양방향 관계를 사용해서 그런 문제를 필긴 할 겁니다.

즉, 애플리케이션의 요구 사항이나 기능에 따라 양방향, 단방향이 결정되는 것이지 항상 어느 한쪽이 더 좋다라고 이야기할 수는 없습니다.

좋은 질문 감사합니다.
⚠️ **GitHub.com Fallback** ⚠️