memo6759 님의 블로그
2025-11-21(JPA 연관관계, Spring Data JPA) 본문
5. 연관관계
- 연관 관계의 종류
- 방향(단방향, 양방향)
- 주테이블이 무엇인지 고민
- 연관관계에서 주인이 되는 테이블(주테이블) 외래키를 가지고 있어야한다.
- 보통 주테이블을 통해 외래키를 관리(등록, 수정, 삭제..)
- JPA에서는 외래키에 대한 정보가 컬럼값이 아니라 엔티티로 정의해야 한다.
- 연관관계를 정의하면서 cascade설정을 추가해야한다.
=> 연관관계에 있는 엔티티들에서 주테이블에서 작업을 수행할때 연관된 다른 테이블에서도 관련 작업이 수행되도록 처리
=> CascadeType.ALL : 모든 작업에서
CascadeType.PERSIST : 엔티티를 저장할때
CascadeType.REMOVE : 엔티티를 삭제할때
-@JoinColum 은 DBMS에서 테이블에 만들어지는 외래키의 이름은 사용자가 정의하고 싶은 경우사용
=> JPA내부에 정의되어 있는 명명규칙대로 생성된다
=> JoinColumn이 정의된 엔티티가 주테이블의 역할이 된다.
- 지연로딩과 즉시 로딩을 적절하게 적용
- 지연로딩은 바로 생성하지 않고 객체가 실제로 사용될때 로딩되는 것을 의미
- 즉시로딩은 모든 테이블을 한 번에 조회할 수 있도록 바로 로딩하는 것을 의미
-
1) 일대일
- 1:1관계는 양쪽 엔티티가 서로 하나의 관계를 가지므로 왜래키를 어떤 테이블에 두어도 상관이 없다
- 외래키는 주테이블에 둔다.
- @One to One 을 이용해서 작업
2) 일대다(단방향)
- pk엔티티에서 fk엔티티의 정보들을 가지고 있는 관계
- pk엔티티에서 fk엔티티 정보를 List에 갖고 있도록 정의
- pk와 fk관계가 있어야 작업이 가능
ex) 한 부서에서 근무하는 직원 목록
주문번호에 주문한 물건들의 명세와 주문에 대한 일반적인 내용
게시글에 연결된 첨부파일...
사원 한 명의 자격사항, 경력사항....
- @OneToMany을 이용해서 작업
- 일대다관계에서 @joinColumn은 외래키테이블에 외래키로 정의할 컬럼명
3) 다대일(단방향)
- JPA에서 가장 중요하고 가장 많이 사용되는 연관관계
- 다에서 해당하는 엔티티에 외래키를 설정하고 이를 통해서 일에 해당하는 엔티티
- @ManyToOne
- fk테이블을 나타내는 엔티티쪽에서 pk정보를 갖고 있는 것
-
4) 다대다
-> 일대다와 다대일로 표현 하는 것이 일반적
6. 양방향
-카테고리에서 상품을 카테고리를 양방향으로 모두 접근할 수 있도록 만드는 작업
- 두 객체가 서로의 존재를 인지하고 참조할 수 있도록 정의하는 방식
상품 => 카테고리(단방향) - 다대일
카테고리 = > 상품(단방향) - 일대다
- 양방향은 단방향의 연관관계를 두 개 정의하고 사용
- Category엔티티에서 Product의 정보를 갖고 있고 Product에서 Category정보를 갖고 있도록 작업
- 양방향으로 작업하는 경우 기준을 정해서 작업한다. 즉 주테이블을 정하고 작업하기
- ManyToOne 엔티티가 주 테이블이 된다.
- 주 테입블이 아닌 곳에서는 mappedBy속성을 이용해서 현재 엔티티가 주테이블의 엔티티가 아니고 주테이블의 컬럼을 참조한다는 것을 명시
----------------------------
mappedBy속성에 참조하는 컬럼명을 정확하게 정의
7. Spring data JPA
- Spring data jpa는 스프링프레임워크에서 JPA를 쉽게 사용할 수 있도록 제공되는 기능
- 개발자가 반복해서 사용하는 CLRUD의 기능을 비롯한 많은 기능을 구현해서 제공하므로 편하게 작업할 수 있다.
- 구현 클래스없이 인터체이스만 가지고 작업이 가능
- 기본 CLRUD작업을 위해서 Spring data JPA가 제공하는 JpaRepository만 상속받아 인페이스만 만들면
기본 기능을 spring data JPA가 만들어서 추상화해준다.
- spring boot application 이 start될때 JpaRepository를 상속하는 모든 객체의 구현체를 만들어서 제공한다.
======================\
객체를 상속해서 메소드를 오버라이딩
- 이렇게 만들어지는 구현체를 프록시 객체
- class jdk.proxy1.$Proxy131이런식으로 객체의 정보가 출력되면 프록시 객체
Spring Data JPA 완전 정리
2. Repository 작성
package com.example.jpatest.springdatajpa;
import com.example.jpatest.mappedBy.EmpEntity4;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmpRepository extends JpaRepository<EmpEntity4, String> {
}
EmpRepository
DTO (요청/응답)
package com.example.jpatest.springdatajpa;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpRequestDTO {
private String userId;
private String name;
private String addr;
}
package com.example.jpatest.springdatajpa;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpResponseDTO {
private String firstName;
private String lastName;
}
테스트 코드 (EmpRepositoryTest)
package com.example.jpatest.springdatajpa;
import com.example.jpatest.mappedBy.DeptEntity;
import com.example.jpatest.mappedBy.EmpEntity4;
import com.example.jpatest.mappedBy.PrivateInfoEntity4;
import jakarta.transaction.Transactional;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.annotation.Rollback;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@Rollback(value = false)
class EmpRepositoryTest {
@Autowired
DeptRepository deptRepository;
@Autowired
EmpRepository empRepository;
@Test
public void test1(){
//스프링이 만들어주는 프록시 객체 확인
System.out.println("++++++++++++실제실행되는 클래스 확인+++++++++++++++++++++++");
System.out.println(deptRepository.getClass());
System.out.println(empRepository.getClass());
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
//내림차순정렬
//Sort.by(정렬기준,정렬할 컬럼명)
print(empRepository.findAll(Sort.by(Sort.Direction.DESC,"addr")));
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
//기본키로 레코드 조회하기
EmpEntity4 emp = empRepository.findById("bts1").get();
System.out.println(emp);
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
//userId를 여러 개 주고 만족하는 데이터를 조회
// List<String> idlist = new ArrayList<>();
// idlist.add("bts1");
// idlist.add("bts2");
// idlist.add("bts3");
// print(empRepository.findAllById(idlist));
print(empRepository.findAllById(Lists.newArrayList("bts1","bts4","bts5")));
}
/*
* save메소드는 객체를 새럽게 만들어서 작업하는 경우에는 insert문이 만들어져서 실행되고
* 조회한 객체의 setter메소드를 이용해서 값을 변경하거나
* 1차 캐시에 있는 개체가 변경되는 경우 update문을 만들어서 작업한다.
*
*
* */
@Test
public void findtest(){
//데이터모두조회하기
List<EmpEntity4> emplist = empRepository.findAll();
print(emplist);
}
@Test
public void insert(){
//레코드 하나 저장하기
DeptEntity dept = deptRepository.findById(1L).get();
EmpEntity4 emp = new EmpEntity4("bts77", "뷔", "제주",
new PrivateInfoEntity4("bts77", "재즈", "귀여움"),dept);
empRepository.save(emp);
}
void print(List<EmpEntity4> emplist){
for(EmpEntity4 emp:emplist){
System.out.println(emp);
}
}
@Test
public void update(){
//update나 delete할 레코드를 조회
EmpEntity4 bts1 = empRepository.findById("bts1").get();
System.out.println("조회한레코드"+bts1);
bts1.setAddr("강원도");
empRepository.save(bts1);
}
@Test
public void readtest(){
//갯수
long count = empRepository.count();
System.out.println("레코드갯수=>"+count);
//레코드 존재유무
System.out.println("실행결과==>"+empRepository.existsById("bts1"));
System.out.println("실행결과==>"+empRepository.existsById("bts11111111111111"));
}
@Test
public void insertall(){
DeptEntity dept = deptRepository.findById(1L).get();
EmpEntity4 emp1 = new EmpEntity4("bts88","뷔","제주",
new PrivateInfoEntity4("bts88","재즈","귀여움"),dept);
EmpEntity4 emp2 = new EmpEntity4("bts99","뷔","제주",
new PrivateInfoEntity4("bts99","재즈","귀여움"),dept);
EmpEntity4 emp3 = new EmpEntity4("bts100","뷔","제주",
new PrivateInfoEntity4("bts100","재즈","귀여움"),dept);
empRepository.saveAll(Lists.newArrayList(emp1,emp2,emp3));
}
//spring data jpa에서 제공되는 페이징 처리
@Test
public void pagetest(){
//pageRequest객체를 만들어서 repository의 findall호출
Page<EmpEntity4> pagelist = empRepository.findAll(PageRequest.of(0, 5));
//==========================
// 현재페이지 번호 한 페이지에서 보여줄 레코드 갯수
// 0번 부터시작
System.out.println("pagelist:"+pagelist);
System.out.println("total(전체레코드수)"+pagelist.getTotalElements());
System.out.println("전페페이지"+pagelist.getTotalPages());
System.out.println("t현제조회한 레코드수"+pagelist.getNumberOfElements());
System.out.println("정렬"+pagelist.getSort());
System.out.println("total(전체레코드수)"+pagelist.getSize());
//실제 데이터 꺼내기
List<EmpEntity4> content = pagelist.getContent();
System.out.println("content:"+content);
}
}
pagetest() — 페이징 처리
Page<EmpEntity4> pagelist = empRepository.findAll(PageRequest.of(0, 5));
- 0번 페이지(첫 페이지)
- 페이지당 5개 레코드
출력:
pagelist.getTotalElements(); // 전체 레코드 수 pagelist.getTotalPages(); // 전체 페이지 수 pagelist.getNumberOfElements(); pagelist.getSize(); pagelist.getSort(); pagelist.getContent(); // 실제 데이터 리스트
➡ Spring Data JPA는 페이징 기능을 기본 제공한다.
Spring Data JPA 핵심 요약
JPARepository 상속만 하면
→ CRUD, 페이징, 정렬 기능을 자동으로 제공
save()
- 새 엔티티 → INSERT
- 영속상태 엔티티 수정 → UPDATE 자동
findAll(), findById(), existsById(), count()
→ 기본 제공 메소드
findAll(PageRequest)
→ 페이징 처리 자동화
findAll(Sort)
→ 정렬 자동화
'HDC 학습일지' 카테고리의 다른 글
| 2025-11-27(Open CV) - 현업자 특강 (0) | 2025.11.27 |
|---|---|
| 2025-11-26(컴퓨터 비전)- 현업자 특강 (0) | 2025.11.27 |
| 2025-11-20(JPA) (0) | 2025.11.20 |
| 2025-11-18(Spring) (0) | 2025.11.19 |
| 2025-11-17(Spring) (1) | 2025.11.17 |