SpringBoot JPA 예제(1:N, 양방향)
양방향 1:N JPA 관계를 맺어봅시다.
준비
이 포스트는 다음과 같은 포스트에서 파생 되었습니다.
양방향(bidirectional) 1:N 관계
1:N 양방향 관계는 이전에 봤던 @OneToMany, @ManyToOne 어노테이션을 사용합니다. 이번에는 살짝 코드를 분리시켰기 때문에 코드량이 많아졌습니다.
양방향 1:N 예제
양방향 1:N 예제를 진행합니다.
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;
Entity
회원 Entity 및 핸드폰 Entity를 만듭니다.
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입니다.
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입니다.
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> {}
Application
실제로 위의 코드를 돌려보는 프로그램을 작성합니다. 이번 포스트에서는 바로 Repository를 최상위 애플리케이션 클래스에서 실행하지 않고 서비스 클래스를 별도로 만들어 분리하고 실행합니다.
RespositoryService
서비스 메소드를 정의할 인터페이스를 만듭니다.
package jpa3; public interface RepositoryService { public void saveMember(); public void print(); public void lazyPrint(); public void lazyPrint2(); public void deletAll(); }
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(); } }
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
마무리
1:N 양방향 예제에 대해 알아봤습니다. JPA 잘 쓰면 정말 빠르고 유연한 어플리케이션 개발이 될것 같네요!
이 외에도 @OneToOne, @ManyToMany 어노테이션 등도 존재합니다. 추후에 이것들도 포스팅할 기회가 있으면 해볼게요. :)