springframework/Spring Data JPA

관계 맵핑, 1 : N

Jungsoomin :) 2020. 11. 13. 19:21

엔티티들의 관계

  • 하나는 Owner 이며 하나는 Non-owning 이다.
  • 관계의 레퍼런스를 가지고 있는 쪽이 Owner 다.
  • 1 = @OneToMany
  • N = @ManyToOne

 N 테이블이 Owner 일 경우

  • 1 에 대한 FK 가 생성 된다.
@Entity
public class Study {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // 1 : N >>  N
    @ManyToOne
    private Account owner;
//    //

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Study study = (Study) o;
        return id.equals(study.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Account getOwner() {
        return owner;
    }

    public void setOwner(Account owner) {
        this.owner = owner;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

DDL = 자식 테이블인 Study 에 FK 를 건 모습.

Hibernate: 
    
    create table account (
       id int8 not null,
        city varchar(255),
        state varchar(255),
        home_street varchar(255),
        zip_code varchar(255),
        created time,
        password varchar(255) not null,
        username varchar(255) not null,
        yes varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table study (
       id int8 not null,
        name varchar(255),
        owner_id int8,
        primary key (id)
    )
Hibernate: 
    
    alter table if exists account 
       add constraint UK_gex1lmaqpg0ir5g1f5eftyaa1 unique (username)
Hibernate: 
    
    alter table if exists study 
       add constraint FK210g5r7wftvloq2ics531e6e4 
       foreign key (owner_id) 
       references account

1 테이블이 Owner 일 경우

  • Join Table이 생성 된다.
@Entity
public class Account {

    @Id
    @GeneratedValue
    private Long id;

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

    @Column(columnDefinition = "varchar(255) not null")
    private String password;

    // 1 : N >> 1
    @OneToMany
    private Set<Study> studies = new HashSet<>();
    //


    public void setStudies(Set<Study> studies) {
        this.studies = studies;
    }

    @Temporal(TemporalType.TIME)
    private Date created = new Date();


    private String yes;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "street" , column = @Column(name = "home_street"))
    })
    private Address address;

    @Transient
    private String no;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return id.equals(account.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Set<Study> getStudies() {
        return studies;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    }

DDL = 복합키를 가진 중간 테이블 생성된 모습

Hibernate: 
    
    create table account (
       id int8 not null,
        city varchar(255),
        state varchar(255),
        home_street varchar(255),
        zip_code varchar(255),
        created time,
        password varchar(255) not null,
        username varchar(255) not null,
        yes varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table account_studies (
       account_id int8 not null,
        studies_id int8 not null,
        primary key (account_id, studies_id)
    )
Hibernate: 
    
    create table study (
       id int8 not null,
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    
    alter table if exists account 
       add constraint UK_gex1lmaqpg0ir5g1f5eftyaa1 unique (username)
Hibernate: 
    
    alter table if exists account_studies 
       add constraint UK_tevcop76y9etp9vx5vce7gns6 unique (studies_id)
Hibernate: 
    
    alter table if exists account_studies 
       add constraint FKem9ae62rreqwn7sv2efcphluk 
       foreign key (studies_id) 
       references study
Hibernate: 
    
    alter table if exists account_studies 
       add constraint FK4h3r1x3qcsugrps8vc6dgnn25 
       foreign key (account_id) 
       references account

단방향 관계의 주인

  •  Entity 의 레퍼런스를 가진 쪽이 Owner 가 된다.
  • 단방향 맵핑의 경우 Owner 에서 Non-owning Entity 를 가져와 영속화 시킨다.

Study 가 Owner Entity 일 경우의 영속화

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("Jungsoomin2");
        account.setPassword("jpa");

        Study study = new Study();

        study.setName("spring data jpa");
        
        study.setOwner(account); // Owner 인 Study 에 객체참조 값을 지정하는 모습

        Session session = entityManager.unwrap(Session.class);

        session.save(account);
        session.save(study);


//       entityManager.persist(account);
    }
}

양방향 관계

 

Entity 사이의 양방향 레퍼런스를 만들고 싶을 경우 설정한다.

  • mappedBy 속성을 사용하며 Non-owning 에서 설정한다.
  • Owner Entity 에서 지정한 맵핑될 멤버변수의 이름을 선언해야한다.

Non-owning Entity = Account = mappedBy

@Entity
public class Account {

    @Id
    @GeneratedValue
    private Long id;

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

    @Column(columnDefinition = "varchar(255) not null")
    private String password;

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


    public void setStudies(Set<Study> studies) {
        this.studies = studies;
    }

    @Temporal(TemporalType.TIME)
    private Date created = new Date();


    private String yes;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "street" , column = @Column(name = "home_street"))
    })
    private Address address;

    @Transient
    private String no;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return id.equals(account.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Set<Study> getStudies() {
        return studies;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    }

Owner Entity = Study

@Entity
public class Study {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    // 1 : N >>  N
    @ManyToOne
    private Account owner;
//    //

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Study study = (Study) o;
        return id.equals(study.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Account getOwner() {
        return owner;
    }

    public void setOwner(Account owner) {
        this.owner = owner;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

DDL = Join Table이 생성되지않고, 자식테이블에서 FK 를 지정하는 모습을 가지고 있음.

Hibernate: 
    
    create table account (
       id int8 not null,
        city varchar(255),
        state varchar(255),
        home_street varchar(255),
        zip_code varchar(255),
        created time,
        password varchar(255) not null,
        username varchar(255) not null,
        yes varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table study (
       id int8 not null,
        name varchar(255),
        owner_id int8,
        primary key (id)
    )
Hibernate: 
    
    alter table if exists account 
       add constraint UK_gex1lmaqpg0ir5g1f5eftyaa1 unique (username)
Hibernate: 
    
    alter table if exists study 
       add constraint FK210g5r7wftvloq2ics531e6e4 
       foreign key (owner_id) 
       references account

양방향 관계의 주인

  • 양방향 관계의 Owner Entity 는 FK를 가지고 있는 쪽이다.
  • 즉, @ManyToOneOwner Entity.
  • 결국, Non-owning Entity 에서 mappedBy 속성을 사용하여 Owner Entity 의 필드를 설정해줘야하는 것.

양방향 관계 맵핑은 Owner Entity(FK) 에서 해야만 한다.

  • 하지만, 객체지향 관점으로 보면, 서로의 레퍼런스를 참조할 시에 각 인스턴스의 참조 값에서는 값이 존재해야한다.
  • 그러므로, Owner Side 에서 메서드를 정의하여, 각 인스턴스의 참조 값에 데이터를 저장할 필요가 있다.

즉, 서로의 인스턴스에 참조 값을 주는 과정과 지우는 과정은 , 반드시 서로 묶여다녀야만한다.

 

 

해당 코드는 Non-owning Entity 인 Account 에서 addStudy(), removeStudy() 로 참조 값을 다루고 있으니 참고.

@Entity
public class Account {

    @Id
    @GeneratedValue
    private Long id;

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

    @Column(columnDefinition = "varchar(255) not null")
    private String password;

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


    public void setStudies(Set<Study> studies) {
        this.studies = studies;
    }

    @Temporal(TemporalType.TIME)
    private Date created = new Date();


    private String yes;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "street" , column = @Column(name = "home_street"))
    })
    private Address address;

    @Transient
    private String no;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return id.equals(account.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Set<Study> getStudies() {
        return studies;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void addStudy(Study study) {
        this.getStudies().add(study); // 양방향 관계의 주인은 Study , 객체지향 관점으로 보면 account 에도 데이터가 들어가야한다.
        study.setOwner(this);
    }

    public void removeStudy(Study study){
        this.getStudies().remove(study);
        study.setOwner(null);
    }
}

Account 에 정의된 레퍼런스 정의 메서드 사용 후 영속화 시키고 있다.

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Account account = new Account();
        account.setUsername("Jungsoomin2");
        account.setPassword("jpa");

        Study study = new Study();

        study.setName("spring data jpa");

        account.addStudy(study);


        Session session = entityManager.unwrap(Session.class);

        session.save(account);
        session.save(study);


//       entityManager.persist(account);
    }
}

'springframework > Spring Data JPA' 카테고리의 다른 글

Fetch 전략  (0) 2020.11.13
Cascade, Entity 상태  (0) 2020.11.13
Value 타입 맵핑  (0) 2020.11.13
엔티티타입 맵핑  (0) 2020.11.13
JPA 실행하고 적용하기  (0) 2020.11.09