[Spring JPA #4] JPA 관계 매핑

2021. 3. 25. 03:06 Spring Data/Spring Data JPA

 

| @ManyToOne

 

프로젝트 구조

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── tutorial
│   │   │           └── springbootjpa
│   │   │               ├── Account.java
│   │   │               ├── Address.java
│   │   │               ├── JpaRunner.java
│   │   │               ├── SpringBootJpaApplication.java
│   │   │               └── Study.java
│   │   └── resources
│   │       ├── application.properties
│   │       ├── static
│   │       └── templates

 

의존성 관리

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/springboot
spring.datasource.username=saelobi
spring.datasource.password=pass

spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

 

소스 코드

@SpringBootApplication
public class SpringBootJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJpaApplication.class, args);
    }

}
@Entity(name = "myAccount")
@Table(name = "Account")
public class Account {

    @Id
    @GeneratedValue
    private Long id;

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

    private String password;


    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;
    }
}
@Embeddable
public class Address {

    private String street;

    private String city;

    private String state;

    private String zipCode;
}
@Entity
public class Study {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Account 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;
    }

    public Account getOwner() {
        return owner;
    }

    public void setOwner(Account owner) {
        this.owner = owner;
    }
}
  • @ManyToOne은 현재 Study 엔티티가 Account 엔티티와 N:1 관계를 맺는 것이라는 정보를 제공하는 어노테이션입니다. 따라서 이 어노테이션을 설정할 경우 테이블 상에서는 study 테이블에 account 테이블의 컬럼을 참조하는 외래키를 생성하게 됩니다.

 

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

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

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

        entityManager.persist(account);
        entityManager.persist(study);
    }
}
  • Account 객체를 생성한 후 이 Account 객체를 Study 객체의 owner 컬럼의 레퍼런스로서 설정하게 되면 다음과 같이 RDB의 테이블 상에서 이 정보가 account의 한 id로 변경되며 해당 row를 참조하게 됩니다.

 

결과화면

Hibernate:

create table account (
id int8 not null,
password varchar(255),
username varchar(255) not null,
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
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
insert
into
account
(password, username, id)
values
(?, ?, ?)
Hibernate:
insert
into
study
(name, owner_id, id)
values
(?, ?, ?)
springboot=# select * from study;
id |      name       | owner_id
----+-----------------+----------
2 | Spring Data JPA |        1
springboot=# select * from account;
id | password | username
----+----------+----------
1 | jpa      | saelobi

 

| @OneToMany

 

소스 코드

@SpringBootApplication
public class SpringBootJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJpaApplication.class, args);
    }

}
@Entity(name = "myAccount")
@Table(name = "Account")
public class Account {

    @Id
    @GeneratedValue
    private Long id;

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

    private String password;

    @OneToMany
    private Set<Study> study = new HashSet<>();

    public Set<Study> getStudy() {
        return study;
    }

    public void setStudy(Set<Study> study) {
        this.study = study;
    }

    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;
    }
}
  • Account 엔티티에 위와 같이 Account : Study = 1: N 의 관계를 맞추기 위해 @OneToMany 어노테이션과 다수의 Study 엔티티에 대한 정보를 저장하기 위한 Set 자료구조를 도입하였습니다. 이때 JPA에서는 @OneToMany에서의 관계를 보고 account와 study 테이블의 관계를 정의한 account_study 테이블을 생성하여 관계 정보를 저장합니다.

 

@Entity
public class Study {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    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;
    }
}

 

결과화면

Hibernate:

create table account (
id int8 not null,
password varchar(255),
username varchar(255) not null,
primary key (id)
)
Hibernate:

create table account_study (
my_account_id int8 not null,
study_id int8 not null,
primary key (my_account_id, study_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_study
add constraint UK_2wmnv2rfmyl3w24rwy6qo47ss unique (study_id)
Hibernate:

alter table if exists account_study
add constraint FKaj8d7ald8auk6it7koyokkkkj
foreign key (study_id)
references study
Hibernate:

alter table if exists account_study
add constraint FKntr6qqv6r6tb5wng0tuc0w54c
foreign key (my_account_id)
references account
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
insert
into
account
(password, username, id)
values
(?, ?, ?)
Hibernate:
insert
into
study
(name, id)
values
(?, ?)
Hibernate:
insert
into
account_study
(my_account_id, study_id)
values
(?, ?)
springboot=# select * from study;
id |      name
----+-----------------
2 | Spring Data JPA
(1 row)

springboot=# select * from account;
id | password | username
----+----------+----------
1 | jpa      | saelobi
(1 row)

springboot=# select * from account_study;
my_account_id | study_id
---------------+----------
1 |        2
(1 row)

 

| 양방향 매핑

 

소스 코드

@Entity(name = "myAccount")
@Table(name = "Account")
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<>();

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

    public void setStudies(Set<Study> studies) {
        this.studies = 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.studies.add(study);
        study.setOwner(this);
    }

    public void removeStudy(Study study) {
        this.studies.remove(study);
        study.setOwner(null);
    }
}
  • 양방향 매핑을 이루기 위해 @OneToMany 어노테이션에 mappedBy = "owner" 라는 인수를 추가했습니다. 이 인수의 목적은 Study 엔티티의 어떤 값과 매핑되어 양방향 매핑이 될 것인지에 대한 정보를 추가하는 용도입니다.
  • addStudy와 removeStudy는 Account 엔티티에서 Study 엔티티 목록을 추가하거나 삭제했을 때 어떻게 양방향 매핑정보를 처리하는 지 알아볼 수 있습니다.

 

@Entity
public class Study {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Account owner;

    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;
    }
}
  • Study 엔티티에 @ManyToOne을 추가해서 양방향 매핑에 대한 정보를 설정해야 합니다. Account 엔티티에 어떤 컬럼과 매핑할 것인지에 대한 정보가 설정되어 있으므로 그와 관련된 정보는 여기서 부가하지 않았습니다.

 

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

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

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

        account.addStudy(study);

        entityManager.persist(account);
        entityManager.persist(study);
    }
}

 

결과 화면

Hibernate:

create table account (
id int8 not null,
password varchar(255),
username varchar(255) not null,
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
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
select
nextval ('hibernate_sequence')
Hibernate:
insert
into
account
(password, username, id)
values
(?, ?, ?)
Hibernate:
insert
into
study
(name, owner_id, id)
values
(?, ?, ?)
springboot=# select * from account;
id | password | username
----+----------+----------
1 | jpa      | saelobi
(1 row)

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

 

https://www.inflearn.com/course/스프링-데이터-jpa



출처: https://engkimbs.tistory.com/815?category=772527 [새로비]