회원1 - 프로필1
@Builder
@Data
@Entity
@NoArgsConstructor @AllArgsConstructor
public class Member extends BaseEntity {
@Id @GeneratedValue
private Long seq;
@Column(length=60, nullable = false, unique = true)
private String email;
@Column(length=65, nullable = false)
private String password;
@Column(length=40, nullable = false, name="name")
private String userName;
// @Lob
@Transient
private String introduction;
@Column(length=10)
@Enumerated(EnumType.STRING)
private Authority authority;
@OneToOne(mappedBy = "profile_seq")
private MemberProfile profile;
}
@Data
@Builder
@Entity
@NoArgsConstructor @AllArgsConstructor
public class MemberProfile {
@Id @GeneratedValue
private Long seq;
private String profileImage;
private String status;
@OneToOne(mappedBy="profile")
@ToString.Exclude
private Member member;
}
@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class Ex10 {
@Autowired
private MemberRepository memberRepository;
@Autowired
private MemberProfileRepository profileRepository;
@PersistenceContext
private EntityManager em;
@BeforeEach
void init(){
MemberProfile profile = MemberProfile.builder()
.profileImage("이미지")
.status("상태")
.build();
profileRepository.saveAndFlush(profile);
Member member = Member.builder()
.email("user01@test.org")
.password("123456678")
.userName("사용자91")
.authority(Authority.USER)
.profile(profile)
.build();
memberRepository.saveAndFlush(member);
em.clear();
}
@Test
public void test(){
Member member = memberRepository.findById(1L).orElse(null);
MemberProfile profile = member.getProfile();
System.out.println(profile);
}
@Test
public void test2(){
MemberProfile profile = profileRepository.findById(1L).orElse(null);
Member member = profile.getMember();
System.out.println(member);
}
}
test1, member에서 profile조회

test2, profile에서 member조회

Meber - BoardData 관계
ManyToOne이 존재해야 사용가능
mappedBy 연관관계 주인 설정 - 관계의 주인의 외래키쪽
lombok의 toString() 함께 사용시 순환참조 문제 발생 가능성
List<BoardData> itmes 👉 toString() 👉 getMember() ... 해결방법
@Builder
@Data
@Entity
@NoArgsConstructor @AllArgsConstructor
public class Member extends BaseEntity {
@Id @GeneratedValue
private Long seq;
@Column(length=60, nullable = false, unique = true)
private String email;
@Column(length=65, nullable = false)
private String password;
@Column(length=40, nullable = false, name="name")
private String userName;
// @Lob
@Transient
private String introduction;
@Column(length=10)
@Enumerated(EnumType.STRING)
private Authority authority;
@ToString.Exclude //ToString 추가 배제
@OneToMany(mappedBy = "member") //BoardData 쪽에 있는 member를 가르킴 (관계의주인)
private List<BoardData> items;
}
@Test
void test2(){
Member member = memberRepository.findById(1L).orElse(null);
List<BoardData> items = member.getItems();
items.forEach(System.out::println);
}

Many쪽에 정의해야한다.
가장 많이 사용
여러개의 게시글데이터가 한명의 회원으로 연결, 게시글 - 회원
부모 : 회원(One) , 자식 : 게시글(Many)
Many - 외래키를 가지고 있다. 자식테이블, 연관관계의 주인One - 부모테이블 관계를 바꿀수 있는 주체는 자식임 👉 외래키가 자식에게 있으니까
BoardData - member 관계
@Data
@Builder
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class BoardData extends BaseEntity {
@Id @GeneratedValue //시퀀스객체 auto
private Long seq;
@ManyToOne //Member쪽이 one, member_seq(엔티티명_기본키 속성명)
private Member member;
@Column(nullable = false)
private String subject;
@Column(nullable = false)
@Lob
private String content;
}
자동으로 외래키가 생성된다. (엔티티명_기본키 속성명)
@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class Ex09 {
@Autowired
private MemberRepository memberRepository;
@Autowired
private BoardDataRepository boardDataRepository;
@PersistenceContext
private EntityManager em;
@BeforeEach
void init(){
Member member = Member.builder()
.email("user01@test.org")
.password("12345678")
.userName("사용자01")
.authority(Authority.USER)
.build();
memberRepository.saveAndFlush(member);
List<BoardData> items = IntStream.rangeClosed(1,10)
.mapToObj(i -> BoardData.builder()
.subject("제목"+i)
.content("내용"+i)
.member(member)
.build()).toList();
boardDataRepository.saveAllAndFlush(items);
em.clear();
}
@Test
void test1(){
BoardData item = boardDataRepository.findById(1L).orElse(null);
Member member = item.getMember();
System.out.println(item);
}
}
결과
Left Join 확인
left join
member m1_0
on m1_0.seq=bd1_0.m_seq
중간테이블 하나 생성
BoardData - HashTag
게시글1 태그1 태그4
게시글2 태그1 태그2
게시글3 태그3 태그4
태그1 - 게시글1, 게시글2
@Data
@Builder
@Entity
@NoArgsConstructor @AllArgsConstructor
public class BoardData extends BaseEntity {
@Id @GeneratedValue
private Long seq;
@ManyToOne // member_seq - 엔티티명_기본키 속성명
@JoinColumn(name="mSeq")
private Member member;
@Column(nullable = false)
private String subject;
@Lob
private String content;
@ManyToMany
@ToString.Exclude
private List<HashTag> tags;
}
@Data
@Entity
@Builder
@NoArgsConstructor @AllArgsConstructor
public class HashTag {
@Id
private String tag;
@ManyToMany(mappedBy = "tags")
private List<BoardData> items;
}
중간테이블 자동생성

@SpringBootTest
//@Transactional
public class Ex11 {
@Autowired
private BoardDataRepository boardDataRepository;
@Autowired
private HashTagRepository hashTagRepository;
//@PersistenceContext
//private EntityManager em;
@BeforeEach
void init(){
List<HashTag> tags = IntStream.rangeClosed(1,5)
.mapToObj(i -> HashTag.builder()
.tag("태그" + i).build()).toList();
hashTagRepository.saveAllAndFlush(tags);
List<BoardData> itmes = IntStream.rangeClosed(1,5)
.mapToObj(i -> BoardData.builder()
.subject("제목" + i)
.content("내용" + i)
.tags(tags)
.build()).toList();
boardDataRepository.saveAllAndFlush(itmes);
}
//게시판으로 태그조회
@Test
void test1(){
BoardData item = boardDataRepository.findById(1L).orElse(null);
List<HashTag> tags = item.getTags();
tags.forEach(System.out::println);
}
//태그로 게시판조회
@Test
void test2(){
HashTag tag = hashTagRepository.findById("태그2").orElse(null);
List<BoardData> items = tag.getItems();
items.forEach(System.out::println);
}
}
중간테이블
둘은 같은 관계를 서로 다른 관점에서 설명한 것입니다. 다대일은 여러 개의 엔티티가 하나의 엔티티에 연결된다는 점을 강조하고, 일대다는 하나의 엔티티가 여러 개의 엔티티에 연결된다는 점을 강조합니다. 같은 관계를 다대일 관점과 일대다 관점 중 어느 쪽에서 보는지에 따라 표현이 달라질 뿐, 본질적으로 동일한 관계를 설명하는 것입니다.
조인되는 컬럼의 이름을 변경
@ManyToOne //Member쪽이 one, member_seq(엔티티명_기본키 속성명) 외부키 생성
@JoinColumn(name="mSeq") //외부키 이름 변경
private Member member;

지연 로딩(Lazy Loading)은 데이터베이스나 객체 관계 매핑(ORM) 시스템에서 자주 사용되는 패턴으로, 실제로 필요한 시점까지 데이터를 로드하지 않는 방법입니다. 이는 성능 최적화와 자원 관리를 위해 매우 유용합니다. 이 패턴을 사용하면 초기 로드 시점에 모든 데이터를 가져오지 않고, 필요한 데이터만 그때그때 로드하게 됩니다. 👉 성능향상
글로벌 전략으로 지연로딩, 필요할 때만 즉시 로딩 전략 사용
but, 목록을 조회할때는 목록 갯수만큼 쿼리조회를 수행하게됨 👉 해결책 : Fetch조인
필요한 엔티티만 즉시 로딩 전략을 사용
public interface BoardDataRepository extends JpaRepository<BoardData, Long>, QuerydslPredicateExecutor<BoardData> {
@Query("SELECT b FROM BoardData b LEFT JOIN FETCH b.member")
List<BoardData> getAllList();
}
그냥 JOIN할경우

FETCH JOIN할경우 LEFT JOIN

쿼리메서드 사용시 정의 가능
public interface BoardDataRepository extends JpaRepository<BoardData, Long>, QuerydslPredicateExecutor<BoardData> {
@EntityGraph(attributePaths = "member")
List<BoardData> findBySubjectContaining(String key);
}

JPAQueryFactory : 생성자 매개변수 - EntityManger
JPAQuery
@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class Ex12 {
@Autowired
private MemberRepository memberRepository;
@Autowired
private BoardDataRepository boardDataRepository;
@PersistenceContext
private EntityManager em;
@Test
void test4(){
QBoardData boardData = QBoardData.boardData;
JPAQueryFactory factory = new JPAQueryFactory(em);
JPAQuery<BoardData> query = factory
.selectFrom(boardData)
.leftJoin(boardData.member)
.fetchJoin();
List<BoardData> items = query.fetch();
}
}

@BatchSize(size = 10)
적용 전
SELECT ... FROM BoardData
SELECT ... FROM Member WHERE seq = 1L
SELECT ... FROM Member WHERE seq = 2L
SELECT ... FROM Member WHERE seq = 3L
...
적용 후
SELECT ... FROM BoardData
SELECT ... FROM Member WHERE seq IN(1L, 2L, 3L)
즉시 로딩 - 처음부터 JOIN
지연 로딩, 처음에는 현재 엔티티만 조회, 다른 매핑된 엔티티는 사용할때만 2차 쿼리 실행
@Transcational 애노테이션과 함계 많이 사용 : 2차 쿼리 실행때 데이터가 영속이 없을 경우 조회가 안되는 상황 발생하기 때문에.
@Data
@Builder
@Entity
@NoArgsConstructor @AllArgsConstructor
public class BoardData extends BaseEntity {
@Id @GeneratedValue
private Long seq;
@ManyToOne(fetch = FetchType.LAZY) // member_seq - 엔티티명_기본키 속성명
@JoinColumn(name="mSeq")
private Member member;
@Column(nullable = false)
private String subject;
@Lob
private String content;
@ManyToMany
@ToString.Exclude
private List<HashTag> tags;
}
public class Member extends BaseEntity {
@Id /* @GeneratedValue(strategy = GenerationType.AUTO) */ @GeneratedValue
private Long seq;
@Column(length=60, nullable = false, unique = true)
private String email;
@Column(length=65, nullable = false)
private String password;
@Column(length=40, nullable = false, name="name")
private String userName;
// @Lob
@Transient
private String introduction;
@Column(length=10)
@Enumerated(EnumType.STRING)
private Authority authority;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="profile_seq")
@ToString.Exclude
private MemberProfile profile;
@ToString.Exclude // ToString 추가 배제
@OneToMany(mappedBy = "member")
private List<BoardData> items;
}
@OneToMany
부모 엔티티의 영속성 변화 상태 👉 자식 엔티티에 전달
| CASCADE 종류 | 설명 |
|---|---|
| PERSIST | 부모 엔티티가 영속화될 때 자식 엔티티도 영속화 |
| MERGE | 부모 엔티티가 병합될 때 자식 엔티티도 병합 |
| REMOVE | 부모 엔티티가 삭제될 때 연된된 자식 엔티티도 삭제 |
| REFRESH | 부모 엔티티가 refresh되면 연관된 자식 엔티티도 refresh |
| DETACH | 부모 엔티티가 detach 되면 연관된 자식 엔티티도 detach 상태로 변경 |
| ALL | 부모 엔티티의 영속성 상태 변화를 자식 엔티티에 모두 전이 |
REMOVE
제약조건 CASCADE ON DELETE과는 다름.
자식 레코드 삭제 이후 부모 레코드 삭제.
@Builder
@Data
@Entity
@NoArgsConstructor @AllArgsConstructor
public class Member extends BaseEntity {
@Id /* @GeneratedValue(strategy = GenerationType.AUTO) */ @GeneratedValue
private Long seq;
@Column(length=60, nullable = false, unique = true)
private String email;
@Column(length=65, nullable = false)
private String password;
@Column(length=40, nullable = false, name="name")
private String userName;
// @Lob
@Transient
private String introduction;
@Column(length=10)
@Enumerated(EnumType.STRING)
private Authority authority;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="profile_seq")
@ToString.Exclude
private MemberProfile profile;
@ToString.Exclude // ToString 추가 배제
@OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
private List<BoardData> items;
}
고아 : 영속성 컨택스트와의 참조가 끊겨진 객체
@OneToMany 애노테이션에 orphanRemoval=true 옵션을 추가
PERSIST도 추가해야함.
@ToString.Exclude // ToString 추가 배제
@OneToMany(mappedBy = "member", cascade = {CascadeType.REMOVE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
private List<BoardData> items;
사용 시점에 객체 생성
수동 등록한 빈객체 위에다 정의 or 클래스에 적용 가능.
DBConfig
@Configuration
@RequiredArgsConstructor
public class DBConfig {
@PersistenceContext
private EntityManager em;
@Lazy
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}