• Home
  • About Me
    • Yoon's Blog photo

      Yoon's Blog

      융가의
      개발일지입니다 :)

    • Learn More
    • Email
    • Facebook
    • Instagram
    • Github
  • Posts
    • All Posts
    • All Tags
    • JAVA
      • Java
      • spring Boot
    • PHP
      • PHP 7
      • Codeigniter
      • Lalavel
    • Linux
      • Linux
      • Ubuntu
    • DataBase
      • Oracle
      • Mysql
    • Develope Enviroment
      • Eclipse
      • Docker
      • Git hub

[Spring Boot] Jpa Mapping 연관관계 2

24 Aug 2021

Reading time ~2 minutes

연관관계 (단방향과 양방향)

연관관계 매핑

  • 방향 : 단방향, 양방향
  • 다중성 : 일대일, 일대다, 다대일, 다대다
  • 연관관계 주인 : 객체 양방향 연관관계는 주인이 필요함!

1. 일대일 양방향

1. Entity

1. Host - 주 테이블

package com.example.demo.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@Data
public class Host2 {

	// 1:1 양방향
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;

	@OneToOne
	@JoinColumn(name = "target_id")
	@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
	private Target2 target;

	@Builder
	public Host2(String name, Target2 target) {
		this.name = name;
		this.target = target;
	}

}

2. Target

package com.example.demo.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@Data
public class Target2 {

	// 1:1 양방향
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;

	@OneToOne(mappedBy = "target")
	private Host2 host;

	@Builder
	public Target2(String name, Host2 host) {
		this.name = name;
		this.host = host;
	}
}

3. HostRepository

package com.example.demo.repository;

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

import com.example.demo.domain.Host2;

@Repository
public interface Host2Repository extends JpaRepository<Host2, Long> {
}

4. TargetRepository

package com.example.demo.repository;

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

import com.example.demo.domain.Target2;

@Repository
public interface Target2Repository extends JpaRepository<Target2, Long> {
}

5. HostTargetService

package com.example.demo.service;

import org.springframework.stereotype.Service;

import com.example.demo.domain.Host2;
import com.example.demo.domain.Target2;
import com.example.demo.repository.Host2Repository;
import com.example.demo.repository.Target2Repository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class HostTargetService2 {

	private final Target2Repository targetRepo;
	private final Host2Repository hostRepo;

	public void save() {
		Target2 target = Target2.builder().name("target2").build();
		Host2 host = Host2.builder().name("host2").target(target).build();

		targetRepo.save(target);
		hostRepo.save(host);
	}

	public Target2 read1() {

		return targetRepo.findById(1L).get();
	}

	public Host2 read2() {
		return hostRepo.findById(1L).get();
	}
}

6. HostTargetController

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.domain.Host1;
import com.example.demo.domain.Host2;
import com.example.demo.domain.Target1;
import com.example.demo.domain.Target2;
import com.example.demo.service.HostTargetService1;
import com.example.demo.service.HostTargetService2;

import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;

@Api
@RestController
@RequestMapping("/host-target")
@RequiredArgsConstructor
public class HostTargetController {
	private final HostTargetService1 hostTargetService1;
	private final HostTargetService2 hostTargetService2;

    @PostMapping("/save2") 
	public void save2() {
		hostTargetService2.save();
	}

	@GetMapping("/read-host2")
	public Host2 readparent2() {
		return hostTargetService2.read2();
	}

	@GetMapping("/read-target2")
	public Target2 readchild2() {
		return hostTargetService2.read1();
	}
}

7. Swagger & DB 확인하기

1. save2 실행 후

2. read-host2 조회

4. read-target2 조회


@JsonIdentityInfo

Host2에서 @JsonIdentityInfo를 지우면 어떤일이 일어날까 ?

	@OneToOne
	@JoinColumn(name = "target_id")
//	@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
	private Target2 target;

아래와같은 현상이 발생한다.

JPA ORM으로 개발하다보면 꽤나 많은 양방향 참조가 필요한 모델들이 있다.
문제는 이런 양방향관계를 가진 객체를 직렬화 하려고 할 때 무한참조가 되어 StackOverFlow를 발생시켜 무한루프에 빠지게된다.
그렇다고 해서 ORM이 가진 장점을 포기하고 단방향으로만 구성 할 수는 없는 노릇 ,,
때문에 @JsonIdentityInfo을 통해 순환참조될 대상의 식별키로 구분해 더이상 순환참조되지 않게 하는 방법이 있다.

ObjectIdGenerators에는

  • Property를 설정할 수 있는 PropertyGenrator ,
  • 숫자형 ID로 설정하는 IntSequenceGenerator,
  • 문자열 ID로 설정하는 StringIdGenerator UUID 형태의 ID로 설정하는 UUIDGenerator 등이 있다.

나는 현재 Long Type ID로 엔티티가 이루어져 있어, IntSequenceGenerator 사용.
추가로 property 명을 따로 명시하지 않으면 기본값 직렬화 시 객체의 id가 @id 로 설정된다.



SpringBootJavaREST APIJPAHibernateSwaggerEclipse Share Tweet +1