JPA @OneToMany, @ManyToOne으로 연관관계 관리하기 ----------------------------------------------------------
https://velog.io/@goniieee/JPA-OneToMany-ManyToOne%EC%9C%BC%EB%A1%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0
-------------------------------
https://velog.io/@conatuseus/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-%EA%B8%B0%EC%B4%88-2-%EC%96%91%EB%B0%A9%ED%96%A5-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84%EC%99%80-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84%EC%9D%98-%EC%A3%BC%EC%9D%B8
---------------------------------------------------------------------------------------------------------------------------
스프링 데이터 JPA에서 두 개의 테이블을 조인하고 다루는 방법을 살펴 보겠습니다.
(즉시 로딩(Eager Loading)과 지연 로딩(Lazy Loading)에 대한 내용도 포함)
데이터베이스 테이블 구조 : 먼저, 두 개의 테이블 jikwon과 gogek의 구조 확인.
jikwon 테이블
Field | Type | Null | Key | Default |
jikwon_no | int(11) | NO | PRI | NULL |
jikwon_name | varchar(10) | NO | | NULL |
buser_num | int(11) | NO | | NULL |
jikwon_jik | varchar(10) | YES | | 사원 |
jikwon_pay | int(11) | YES | | NULL |
gogek 테이블
Field | Type | Null | Key | Default |
gogek_no | int(11) | NO | PRI | NULL |
gogek_name | varchar(10) | NO | | NULL |
gogek_tel | varchar(20) | YES | | NULL |
gogek_damsano | int(11) | YES | MUL | NULL |
1. 엔티티 클래스 작성
Jikwon 엔티티
import lombok.*;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "jikwon")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Jikwon {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "jikwon_no")
private int jikwonNo;
@Column(name = "jikwon_name", nullable = false)
private String jikwonName;
@Column(name = "buser_num", nullable = false)
private int buserNum;
@Column(name = "jikwon_jik", nullable = true)
private String jikwonJik;
@Column(name = "jikwon_pay", nullable = true)
private int jikwonPay;
@OneToMany(mappedBy = "jikwon", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Gogek> gogeks;
}
Gogek 엔티티
import lombok.*;
import javax.persistence.*;
@Entity
@Table(name = "gogek")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Gogek {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "gogek_no")
private int gogekNo;
@Column(name = "gogek_name", nullable = false)
private String gogekName;
@Column(name = "gogek_tel", nullable = true)
private String gogekTel;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "gogek_damsano", referencedColumnName = "jikwon_no", nullable = true)
private Jikwon jikwon;
}
2. 리포지토리 인터페이스 작성
Jikwon 리포지토리
import org.springframework.data.jpa.repository.JpaRepository;
public interface JikwonRepository extends JpaRepository<Jikwon, Integer> {
}
Gogek 리포지토리
import org.springframework.data.jpa.repository.JpaRepository;
public interface GogekRepository extends JpaRepository<Gogek, Integer> {
}
3. 서비스 클래스 작성
JikwonService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class JikwonService {
@Autowired
private JikwonRepository jikwonRepository;
@Autowired
private GogekRepository gogekRepository;
public List<Jikwon> getAllJikwon() {
return jikwonRepository.findAll();
}
public List<Gogek> getGogeksByJikwonNo(int jikwonNo) {
return gogekRepository.findAll().stream()
.filter(gogek -> gogek.getJikwon() != null && gogek.getJikwon().getJikwonNo() == jikwonNo)
.collect(Collectors.toList());
}
}
4. 컨트롤러 작성
JikwonController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/jikwon")
public class JikwonController {
@Autowired
private JikwonService jikwonService;
@GetMapping
public List<Jikwon> getAllJikwon() {
return jikwonService.getAllJikwon();
}
@GetMapping("/{jikwonNo}/gogeks")
public List<Gogek> getGogeksByJikwonNo(@PathVariable int jikwonNo) {
return jikwonService.getGogeksByJikwonNo(jikwonNo);
}
}
* 로딩 전략 *
1) 즉시 로딩 (Eager Loading) : 즉시 로딩은 연관된 엔티티를 즉시 로드하는 방법입니다. fetch = FetchType.EAGER를 사용하여 즉시 로딩을 설정할 수 있습니다. 즉시 로딩은 성능에 영향을 미칠 수 있으며, 필요하지 않은 데이터를 로드하는 경우 오버헤드가 발생할 수 있습니다.
@OneToMany(mappedBy = "jikwon", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Gogek> gogeks;
2) 지연 로딩 (Lazy Loading)
지연 로딩은 실제로 필요한 시점에 데이터를 로드하는 방법입니다. fetch = FetchType.LAZY를 사용하여 지연 로딩을 설정할 수 있습니다. 지연 로딩은 성능 최적화에 유리하며, 필요한 데이터만 로드합니다.
@OneToMany(mappedBy = "jikwon", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Gogek> gogeks;
** 조인 예제 **
-- Fetch Join : Fetch Join을 사용하여 조인된 데이터를 한 번에 가져올 수 있습니다.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface JikwonRepository extends JpaRepository<Jikwon, Integer> {
@Query("SELECT j FROM Jikwon j JOIN FETCH j.gogeks WHERE j.jikwonNo = :jikwonNo")
Jikwon findJikwonWithGogeks(@Param("jikwonNo") int jikwonNo);
}
-- Native Query 예제 : 네이티브 쿼리를 사용하여 직접 SQL을 작성할 수 있습니다.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface GogekRepository extends JpaRepository<Gogek, Integer> {
@Query(value = "SELECT * FROM gogek WHERE gogek_damsano = :jikwonNo", nativeQuery = true)
List<Gogek> findGogeksByJikwonNoNative(@Param("jikwonNo") int jikwonNo);
}
요약
- 엔티티 클래스 작성: @Entity 및 @Table 어노테이션을 사용하여 엔티티 클래스를 작성합니다.
- 리포지토리 인터페이스 작성: 기본 CRUD 및 커스텀 쿼리를 정의하는 리포지토리를 작성합니다.
- 서비스 클래스 작성: 비즈니스 로직을 구현하고 리포지토리의 데이터를 조작합니다.
- 컨트롤러 작성: HTTP 요청을 처리하고 서비스 계층의 메서드를 호출합니다.
- 로딩 전략: 즉시 로딩과 지연 로딩을 설정하여 성능을 최적화합니다.
- 조인 예제: Fetch Join 및 Native Query를 사용하여 조인된 데이터를 가져옵니다.
이렇게 하면 스프링 데이터 JPA를 사용하여 두 테이블 간의 조인을 효과적으로 수행할 수 있습니다. 로딩 전략을 적절히 사용하여 성능을 최적화하고 필요한 데이터를 효율적으로 가져올 수 있습니다.
----------------- ----------------- ----------------- ----------------- -----------------
*** 일대일, 일대다, 다대다에 대한 설명 ***
스프링 데이터 JPA에서 엔티티 간의 관계는 주로 세 가지로 나뉩니다.
일대일(One-to-One), 일대다(One-to-Many), 다대다(Many-to-Many)
각 관계를 정의하고 예제를 통해 확인하겠습니다.
1. 일대일 관계 (One-to-One) : 일대일 관계는 두 엔티티 간의 관계에서 각 엔티티가 하나의 상대 엔티티와만 연결되는 경우입니다.
예제: 직원과 직원 상세 정보 간의 일대일 관계
Employee 엔티티
import javax.persistence.*;
import lombok.*;
@Entity
@Table(name = "employee")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private EmployeeDetail employeeDetail;
}
두 엔티티가 서로 하나의 인스턴스와만 연결됩니다.
주로 @OneToOne 어노테이션을 사용합니다.
지연 로딩(LAZY)과 즉시 로딩(EAGER) 모두 사용 가능하지만, 지연 로딩이 일반적입니다.
한 엔티티가 여러 개의 다른 엔티티와 연결될 경우, @OneToMany와 @ManyToOne 어노테이션을 사용합니다. 여러 엔티티가 다른 여러 엔티티와 연결될 경우에는 @ManyToMany 어노테이션을 사용하며, 조인 테이블을 통해 관계를 관리합니다. 관계를 관리하는 조인 테이블을 정의해야 합니다