관리 메뉴

nalaolla

SpringBoot JPA 예제(1:N, 양방향) 본문

JPA

SpringBoot JPA 예제(1:N, 양방향)

날아올라↗↗ 2020. 2. 12. 15:15
728x90

양방향 1:N JPA 관계를 맺어봅시다.

  1. 준비
    이 포스트는 다음과 같은 포스트에서 파생 되었습니다.

SpringBoot JPA 예제(@OneToMany, 단방향)
SpringBoot JPA 예제(@ManyToOne, 단방향)
2. 양방향(bidirectional) 1:N 관계
1:N 양방향 관계는 이전에 봤던 @OneToMany, @ManyToOne 어노테이션을 사용합니다. 이번에는 살짝 코드를 분리시켰기 때문에 코드량이 많아졌습니다.

2.1. 양방향 1:N 예제
양방향 1:N 예제를 진행합니다.

2.1.1. Table
테이블은 아래처럼 구성 되어있습니다. MariaDB입니다.

CREATE TABLE member (
seq INT(10) NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (seq)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
CREATE TABLE phone (
seq INT(10) NOT NULL AUTO_INCREMENT,
member_id INT(10) NULL DEFAULT NULL,
no VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (seq)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
2.1.2. Entity
회원 Entity 및 핸드폰 Entity를 만듭니다.

2.1.2.1. Member Entity
package jpa3;

import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.transaction.Transactional;

@Entity
public class Member {

@Id
@Column(name="seq")
@GeneratedValue(strategy=GenerationType.AUTO)
private int seq;

@Column(name="name")
private String name;

@OneToMany(cascade=CascadeType.ALL, mappedBy="member")
private Collection<Phone> phone;

public Member(){}

public Member(String name){
    this.name = name;
}

public int getSeq() {
    return seq;
}

public void setSeq(int id) {
    this.seq = id;
}

public String getName() {
    return name;
}

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

public Collection<Phone> getPhone() {
    if( phone == null ){
        phone = new ArrayList<Phone>();
    }
    return phone;
}

public void addPhone(Phone p){
    Collection<Phone> phone = getPhone();
    phone.add(p);
}

public void setPhone(Collection<Phone> phone) {
    this.phone = phone;
}    

@Override
public String toString() {
    String result = "[member_"+seq+"] " + name;
    return result;
}

}
회원 Entity에 @OneToMany 어노테이션이 사용되었습니다. 그리고 mappedBy 라는 속성이 사용 되었습니다. 여기서 mappedBy의 값으로 사용된 "member"는 Phone Entity에서 Member 클래스 변수명입니다. 기본적으로 @OneToMany는 fetch 타입이 LAZY입니다.

2.1.2.2. Phone Entity
package jpa3;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Phone {

@Id
@Column(name="seq")
@GeneratedValue(strategy=GenerationType.AUTO)
private int seq;

@Column(name="no")
private String no;

@ManyToOne(optional=false)
@JoinColumn(name="member_id")
private Member member;

public Phone() {}
public Phone(Member member, String no){
    this.no = no;
    this.member = member;
}

public int getSeq() {
    return seq;
}

public void setSeq(int seq) {
    this.seq = seq;
}

public String getNo() {
    return no;
}

public void setNo(String no) {
    this.no = no;
}

public Member getMember() {
    return member;
}

public void setMember(Member member) {
    this.member = member;
}    

@Override
public String toString() {
    String result = "[phone_"+seq+"] " + no;
    return result;
}    

}
Phone Entity에서는 @ManyToOne 어노테이션이 사용되었습니다. 여기에서 Member 클래스를 가진 멤버 변수 이름이 "member" 입니다. 아까 Member Entity에서 봤던 mappedBy에서 "member"의 값을 뜻합니다. 그리고 @ManyToOne 어노테이션의 기본 fetch 전략은 EAGER입니다.

2.1.3. Repository
Repository는 간단하게 코드만 보고 넘어가겠습니다.

package jpa3;

import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Integer>{}
package jpa3;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PhoneRepository extends JpaRepository<Phone, Integer> {}
2.2. Application
실제로 위의 코드를 돌려보는 프로그램을 작성합니다. 이번 포스트에서는 바로 Repository를 최상위 애플리케이션 클래스에서 실행하지 않고 서비스 클래스를 별도로 만들어 분리하고 실행합니다.

2.2.1. RespositoryService
서비스 메소드를 정의할 인터페이스를 만듭니다.

package jpa3;

public interface RepositoryService {
public void saveMember();
public void print();
public void lazyPrint();
public void lazyPrint2();
public void deletAll();
}
2.2.2. RespositoryServiceImpl
실제 서비스할 비즈니스 로직을 구현합니다. 자세한 내용은 주석으로 처리했습니다.

package jpa3;

import java.util.List;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RepositoryServiceImpl implements RepositoryService {

@Autowired
private MemberRepository mr;

@Autowired
private PhoneRepository pr;

public void saveMember(){
    Member first = new Member("Jung");
    Member second = new Member("Dong");
    Member third = new Member("Min");

    Phone p = new Phone(first, "010-XXXX-YYYY");

    mr.save(first);
    mr.save(second);
    mr.save(third);

    pr.save(p);
}

public void print(){
    // @ManyToOne의 fetch 기본전략은 EAGER이다.
    // 따라서 @Transactional 어노테이션이 없더라도
    // 기본적으로 전부 데이터를 적재한다.
    List<Phone> phone = pr.findAll();
    for( Phone p : phone ){
        System.out.println(p.toString()+ " " + p.getMember().toString());
    }
}

@Transactional
public void lazyPrint(){
    // @OneToMany의 fetch 기본전략은 LAZY이다.        
    // 따라서 Member Entity 내부의 Phone 콜렉션은 
    // LAZY 전략이기 때문에 @Transactional 어노테이션이 있어야 한다.
    List<Member> member = mr.findAll();
    for( Member m : member ) {
        System.out.println(m.toString());
        for( Phone e : m.getPhone() ){
            System.out.println(e.toString());
        }
    }
}

public void lazyPrint2(){
    // Entity가 LAZY 전략일지라도 
    // LAZY 전략을 쓰는 객체를 사용하지 않는다면
    // @Transactional 어노테이션이 없어도 된다.
    List<Member> member = mr.findAll();
    for( Member m : member ) {
        System.out.println(m.toString());
    }
}

public void deletAll() {
    mr.deleteAll();
    pr.deleteAll();
}    

}
2.2.3. Application
RepositoryService의 메소드들을 실행합니다. 트랜잭션이 걸린것도 있고 아닌것도 있지만 콘솔 화면에 출력은 문제가 없습니다.

package jpa3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Jpa3Application implements CommandLineRunner{

@Autowired
private RepositoryService rs;

public static void main(String[] args) {
SpringApplication.run(Jpa3Application.class, args);
}
@Override
public void run(String... args) throws Exception {
rs.deletAll();
rs.saveMember();
rs.print();
rs.lazyPrint();
rs.lazyPrint2();
}
}

[phone_161] 010-XXXX-YYYY [member_336] Jung
[member_336] Jung
[phone_161] 010-XXXX-YYYY
[member_337] Dong
[member_338] Min
[member_336] Jung
[member_337] Dong
[member_338] Min
3. 마무리
1:N 양방향 예제에 대해 알아봤습니다. JPA 잘 쓰면 정말 빠르고 유연한 어플리케이션 개발이 될것 같네요!

이 외에도 @OneToOne, @ManyToMany 어노테이션 등도 존재합니다.

http://jdm.kr/blog/143

728x90

'JPA' 카테고리의 다른 글

JPA 사용법 (JpaRepository)  (0) 2020.02.12
SpringBoot JPA Pagination  (0) 2020.02.12
SpringBoot JPA 예제(@ManyToOne, 단방향)  (0) 2020.02.12
SpringBoot JPA 예제(@OneToMany, 단방향)  (0) 2020.02.12
SpringBoot JPA 예제  (0) 2020.02.12