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

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

1. 준비

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

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를 최상위 애플리케이션 클래스에서 실행하지 않고 서비스 클래스를 별도로 만들어 분리하고 실행합니다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

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

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED