HomeBlogGuestbookLab 

JDM's Blog

온갖 테스트 결과가 기록되는 이곳은 JDM's Blog입니다. :3

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 어노테이션 등도 존재합니다. 추후에 이것들도 포스팅할 기회가 있으면 해볼게요. :)