본문 바로가기
Spring

2024_08_06_화~08_07_수

by 알케니브 2024. 8. 6.

오늘의 코딩순서

(폴더: oBootBoardjpa02)

0806 일지

  • application.yml + index.html

ORACLE에서 TBL, Sequence, Foreign Key 만들기:

  • Member.class + Team.class

0. 기본 설정

  • MemberRepository.interface + JpaMemberRepository.class + MemberService.class + MemberController.class

1. 신규생성

  • createMemberForm.html + MemberController.class + MemberService.class + JpaMemberRepository.class  + Member.class
    + application.yml, createMemberForm.html 수정

2. 데이터 조회

  • memberList.html + MemberController.class + MemberService.class + JpaMemberRepository.class + MemberRepository.class

3. 데이터 수정 

  • memberModifyForm.html + MemberController.class + MemberService.class + JpaMemberRepository.class + MemberRepository.class

0806 일지

3. 데이터 수정 후 업데이트

  • Member.class + MemberController.class + MemberService.class + JpaMemberRepository.class + MemberRepository.class

4. 데이터 조회2 (현장 HW 1 )

  • memberList.html + MemberController.class + MemberService.class + MemberRepository.class +  JpaMemberRepository.class 

5. 데이터 조회3 (현장 HW 2)

  • memberList.html + MemberController.class + MemberService.class + MemberRepository.class +  JpaMemberRepository.class 

(src/test/java 폴더)

  • MemberServiceTest.class

오늘의 코딩 포인트

0806 일지

(폴더: oBootBoardjpa02)

  • application.yml
server:
  port: 8385
# Oracle Connect
spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521/xe
    username: scottJpa
    password: tiger
    
  #JPA Setting
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create
  • index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a href="/members/new">Member 신규 생성</a><p>
	<a href="/members">Member List 조회</a>

</body>
</html>

ORACLE에서 TBL, Sequence, Foreign Key 만들기

  • Member.class
    Tip)
    • @Id
      • 기본키(PK)를 지정하는 어노테이션
      • id 필드를 지정된 JPA 엔티티의 기본 키로 설정함
      • 기본타입, 기본 래퍼 유형, String, java.util.Date, java.sql.Date, java.math.BigDecimal, java.math.BigInteger 중 하나여야 함
      • @Column 어노테이션을 지정하지 않으면 열 이름은 기본 키 속성 또는 필드의 이름으로 가정함
      • @GeneratedValue 없이 @Id 어노테이션만 사용한다면 직접 할당이 됨
        즉, persist() 메서드를 호출하기 전에 애플리케이션에서 직접 실별자 값을 할당해야 하며, 식별자 값이 없을 경우 에러를 발생시킴
    • @GeneratedValue
      • 기본키(PK) 값에 대한 생성 전략을 제공함
      • 기본값은 Auto이며, persistence가 제공하는 ID 생성기임
      • @Id와 함께 엔티티 또는 매핑된 슈퍼클래스의 기본 키 속성 또는 필드에 적용할 수 있음
      • SequenceGenerator, TableGenerator에 지정된 대로 사용할 기본 키 생성기 이름을 지정하는 곳
    • @Column
      • 객체 필드와 DB 테이블 컬럼을 맵핑함
      • 속성
        1. name : 맵핑할 테이블의 컬럼 이름을 지정
        2. insertable : 엔티티 저장시 선언된 필드도 같이 저장
        3. updateable : 엔티티 수정시 이 필드를 함께 수정
        4. table : 지정한 필드를 다른 테이블에 맵핑
        5. nullable : NULL을 허용할지, 허용하지 않을지 결정
        6. unique : 제약조건을 걸 때 사용
        7. columnDefinition : DB 컬럼 정보를 직접적으로 지정할 때 사용
        8. length : varchar의 길이를 조정합니다. 기본값으로 255가 입력
        9. precsion, scale : BigInteger, BigDecimal 타입에서 사용, 각각 소수점 포함 자리수, 소수의 자리수를 의미
    • @SequenceGenerator
      • 테이블 마다 시퀀스 오브젝트를 따로 관리하고 싶을때 사용하는 어노테이션
      • sequence를 지원하는 프로그램의 데이터베이스에서만 사용할 수 있음
      • 속성
        1. name : 식별자 생성기 이름이며 필수로 작성
        2. sequnceName : 데이터베이스에 등록되어 있는 시퀀스 이름으로 기본값은 hibernate_sequence 입니다.
        3. initialValue : DDL 생성 시에만 사용되며 DDL을 생성할 때 처음 시작하는 수로 기본값은 1입니다.
        4. allocationSize : 시퀀스 한 번 호출에 증가하는 수로 기본값은 50입니다.
        5. catalog, schema: 데이터베이스 catelog, schema 이름
      • SEQUENCE?: 데이터베이스의 특별한 오브젝트 시퀀스를 사용하여 기본키를 생성함,
        DB Sequence란 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
    • @ManyToOne
      • Foreign Key를 걸어주는 TBL이 주체⭐
      • @JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가짐
      • N:1의 관계, 단방향의 연관관계
      • 가장 많이 쓰이는 연관관계임
        엔티티의 관계를 표현하고 FK 관리에 있어서 가장 자연스럽기 때문
    • @JoinColumn: 엔티티 테이블에 FK 칼럼을 정의해줌
    • 속성
      1. name: 매핑할 외래 키의 이름을 지정하는 속성
    • precision: 데이터베이스 열의 전체 자릿수를 지정하는 값을 가져오거나 설정함

 

package com.oracle.oBootJpa02.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Entity
@Table(name = "member2")	// TBL 이름
//@Getter
//@Setter
//@ToString
	//@Data가 위의 세 어노테이션의 기능을 함
@Data
@SequenceGenerator(
					name			= "member_seq_gen",	// seq 객체 이름
					sequenceName	= "member_seq_generator", // DB의 SEQUENCE 이름
					initialValue	= 1,
					allocationSize	= 1
					)

public class Member {
			// 객체 이름
	
	@Id
	@GeneratedValue(
						strategy = GenerationType.SEQUENCE,	
						generator = "member_seq_gen"
					)
	
	@Column(name = "member_id" , precision = 10)
	private Long	id;
	@Column(name = "username" , length = 50)
	private String	name;
	private Long	sal;
	
	
	// 관계 설정
	@ManyToOne
	@JoinColumn(name = "team_id") // Team TBL의 team_id와 Join
	private Team team;	// 관련지을 TBL 선언
	
	private String teamname;	//Team 이름 선언
	
}

 

외래키 설정완료되면 Console에 나오는 문구 ↓↓↓

 

외래키 설정완료되면 Oracle TBL 설정에서 확인 가능

  • Team.class
package com.oracle.oBootJpa02.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import lombok.Data;

@Entity
@Data
@SequenceGenerator(
					name			= "team_seq_gen",	// seq 객체 이름
					sequenceName	= "team_seq_generator", // DB의 SEQUENCE 이름
					initialValue 	= 1,
					allocationSize	= 1
					)

public class Team {
	@Id
	@GeneratedValue(
					strategy	= GenerationType.SEQUENCE,
					generator	= "team_seq_gen"
					)
	private Long team_id;
	
	@Column(name = "teamname")
	private String name;
	
	
	
}


0) 기본 설정

  • MemberRepository.interface
package com.oracle.oBootJpa02.repository;

import java.util.List;

import com.oracle.oBootJpa02.domain.Member;

public interface MemberRepository {
	
	Member			memberSave(Member member);
	List<Member>	findAll();

}
  • JpaMemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;

import jakarta.persistence.EntityManager;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	@Override
	public Member memberSave(Member member) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public List<Member> findAll() {
		List<Member> memberList = em.createQuery("select m from Member m", Member.class)
									.getResultList();
		
		return memberList;
	}
	
	
	
}

 

  • MemberService.class
package com.oracle.oBootJpa02.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
}
  • MemberController.class => 가장 기본 Controller 틀
package com.oracle.oBootJpa02.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping(value="/members/new")
	public String createForm() {
		System.out.println("MemberController /members/new start...");
		return "members/createMemberForm";
	}

}

1) 신규 생성

  • createMemberForm.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA 회원 등록2</h1>
	<div class="container">
		<form action="/memberSave" method="post">
			<!-- ID :		<input type="text" id="id"		name="id" required="required"> -->
				회원이름 :	<input type="text" name="name"		placeholder="회원 이름을 입력하세요"><p>
				팀 이름 :	<input type="text" name="teamname" 	placeholder="팀 이름을 입력하세요">
			<button type="submit">등록</button>
		</form>
 	</div> <!-- container -->
</body>
</html>
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	
	// 1. 새로운 데이터 입력 => createMemberForm.html과 연결됨
	@GetMapping(value="/members/new")
	public String createForm() {
		System.out.println("MemberController /members/new start...");
		return "members/createMemberForm";
	}
	
	@PostMapping(value = "/memberSave")
	public String memberSave(Member member) {
		System.out.println("MemberController create start...");
		System.out.println("member->"+member);
		System.out.println("member.getName()->"+member.getName());
		memberService.memberSave(member);
		return "redirect:/";
	}
	

}
  • MemberService.class
package com.oracle.oBootJpa02.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	
	// 1. 새로운 데이터 입력 Service => createMemberForm.html과 연결됨
	public Member memberSave(Member member) {
		System.out.println("MemberService join member->"+member);
		memberRepository.memberSave(member);
		return member;
	}
	
	
}
  • JpaMemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	
	// 1. 새로운 데이터 입력 => createMemberForm.html과 연결됨
	@Override
	public Member memberSave(Member member) {
		// 1. 팀 저장
		Team team = new Team(); // 인스턴스 선언
		team.setName(member.getTeamname());
			// @Transient덕분에 Teamname이 TBL에 임시생성이 가능함
		em.persist(team);
		
		// 2. 회원 저장
		member.setTeam(team);
		em.persist(member);
		
		return member;
	}
	
}
  • Member.class
    Tip)
    • @Transient
      • 임시선언
package com.oracle.oBootJpa02.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Entity
@Table(name = "member2")
//@Getter
//@Setter
//@ToString
@Data
@SequenceGenerator(
					name			= "member_seq_gen",	// seq 객체 이름
					sequenceName	= "member_seq_generator", // DB의 SEQUENCE 이름
					initialValue	= 1,
					allocationSize	= 1
					)

public class Member {
	
	@Id
	@GeneratedValue(
						strategy = GenerationType.SEQUENCE,	
						generator = "member_seq_gen"
					)
	
	@Column(name = "member_id" , precision = 10)
	private Long	id;
	@Column(name = "username" , length = 50)
	private String	name;
	private Long	sal;
	
	
	// 관계 설정
	@ManyToOne
	@JoinColumn(name = "team_id") // Team TBL의 team_id와 Join
	private Team team;	// 관련지을 TBL 선언
		// JpaMemberRepository의 member.setTeam(team);과 함께 연결됨
	
	@Transient
	private String teamname;	//Team 이름 선언
	
}

 

+++

  • application.yml ==> 이제 만든 테이블과 정보에 update할 예정이므로 yml 파일 create에서 update로 수정하기
server:
  port: 8385
# Oracle Connect
spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521/xe
    username: scottJpa
    password: tiger
    
  #JPA Setting
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
  • createMemberForm.html ==> 회원급여 정보 추가
    Tip)
    이 정보는 member 객체에 들어가기 때문에,  controller이나 Service 등에 추가할 로직은 없음
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA 회원 등록2</h1>
	<div class="container">
		<form action="/memberSave" method="post">
			<!-- ID :		<input type="text" id="id"		name="id" required="required"> -->
				회원이름 :	<input type="text" name="name"		placeholder="회원 이름을 입력하세요"><p>
				회원급여 :	<input type="text" name="sal"		placeholder="회원 급여를 입력하세요"><p>
				팀 이름 :	<input type="text" name="teamname" 	placeholder="팀 이름을 입력하세요">
			<button type="submit">등록</button>
		</form>
 	</div> <!-- container -->
</body>
</html>

2) 데이터 조회

  • memberList.html ==> thymeleaf 방식으로 만들기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA2 회원 조회</h1>
	<div class="container">
		<div>
			<table border="1">
				<thead>
					<tr>
						<th>No</th>
						<th>회원이름</th>
						<th>팀	이름</th>
					</tr>
				</thead>
				
				<tbody>
					<tr th:each="member : ${memberList}">
						<td th:text="${member.id}"></td>
						<td><a th:href="@{/memberModifyForm(id=${member.id})}" th:text="${member.name}"></a></td>
										<!-- thymeleaf 문법 -->
						<td th:text="${member.team.name}"></td>
					</tr>	
				</tbody>
			</table>
		</div>	<!-- container -->
	</div>
</body>
</html>
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
		 
	// 2. 데이터 조회 => memberList.html과 연결됨
	@GetMapping(value="/members")
	public String listMember(Model model) {
		List<Member> memberList = memberService.getListAllMember();
		System.out.println("memberList.get(0).getName->"+memberList.get(0).getName());
		
		model.addAttribute("memberList",memberList);
		return "members/memberList";
		
	}
}
  • MemberService.class
package com.oracle.oBootJpa02.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	
	// 2. 데이터 조회 => memberList.html과 연결됨
	public List<Member> getListAllMember() {
		List<Member> listMember = memberRepository.findAll();
		System.out.println("MemberService getListAllMember listMember.size()->"+listMember.size());
		return listMember;
	}
	
}
  • JpaMemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	
	// 2. 데이터 조회 => memberList.html과 연결됨
	@Override
	public List<Member> findAll() {
		List<Member> memberList = em.createQuery("select m from Member m", Member.class)
									.getResultList();
		
		return memberList;
	}
	
	
	
}

 

  • MemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import com.oracle.oBootJpa02.domain.Member;

public interface MemberRepository {
	
	Member			memberSave(Member member);
	List<Member>	findAll();
}


 

3) 데이터 수정 폼

  • memberModifyForm.html ==> thymeleaf 방식으로 만들기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<!-- jstl의 core 선언과 같음 -->
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA2 회원 수정</h1>
	<div class="container">
		<form action="/members/memberUpdate" method="post">
		<!-- ID : <input type="text" id="id" name="id" required="required"> -->
			<input type="hidden" name="id"		th:value="${member.id}">
			<input type="hidden" name="teamid"	th:value="${member.team.team_id}">
			
			회원이름 : <input type="text" name="name" 		th:value="${member.name}"><p>
			팀	이름 : <input type="text" name="teamname"	th:value="${member.team.name}">
			<button type="submit">회원 수정 처리</button>
		</form>
	</div>
</body>
</html>
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	// 3. 데이터 수정 폼 => memberModifyForm.html과 연결됨
	@GetMapping(value = "/memberModifyForm")
	public String memberModifyForm(Member member, Model model) {
		System.out.println("MemberController memberModify id->"+member.getId());
		Member member3 = memberService.findByMember(member.getId());
		
		System.out.println("member3->"+member3);
		model.addAttribute("member", member3);
		
		return "members/memberModify";
		
	}
    
    @PostMapping(value = "/members/memberUpdate")
	public String memberUpdate(Member member, Model model) {
		System.out.println("MemberController memberUpdate member->"+member);
		
		memberService.memberUpdate(member);
		System.out.println("MemberController memberUpdate.updateByMember After...");
		
		return "redirect:/members";
	}
}
  • MemberService.class
package com.oracle.oBootJpa02.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}

	// 3. 데이터 수정 폼 => memberModifyForm.html과 연결됨
	public Member findByMember(Long memberId) {
		Member member1 = memberRepository.findByMember(memberId);
		System.out.println("MemberService findByMember member1->"+member1);
		return member1;
	}
    public void memberUpdate(Member member) {
		System.out.println("MemberService memberUpdate member->"+member);
		
		memberRepository.updateByMember(member);
		System.out.println("MemberService memberRepository.updateByMember After...");
		return;
	}
	
}
  • JpaMemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	
	// 3. 데이터 수정 폼 => memberModifyForm.html과 연결됨
	@Override
	public Member findByMember(Long memberId) {
		Member member = em.find(Member.class, memberId);
			// EntityManager의 문법
		return member;
	}
	
}
  • MemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import com.oracle.oBootJpa02.domain.Member;

public interface MemberRepository {
	
	Member			memberSave(Member member);
	List<Member>	findAll();
	Member          findByMember(Long memberId);

}

0807 일지

4) 데이터 수정 후 업데이트 ==> 3) 데이터 수정에 추가됨

  • Member.class
package com.oracle.oBootJpa02.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Entity
@Table(name = "member2")	// TBL 이름
//@Getter
//@Setter
//@ToString
	//@Data가 위의 세 어노테이션의 기능을 함
@Data
@SequenceGenerator(
					name			= "member_seq_gen",	// seq 객체 이름
					sequenceName	= "member_seq_generator", // DB의 SEQUENCE 이름
					initialValue	= 1,
					allocationSize	= 1
					)

public class Member {
			// 객체 이름
	
	@Id
	@GeneratedValue(
						strategy = GenerationType.SEQUENCE,	
						generator = "member_seq_gen"
					)
	
	@Column(name = "member_id" , precision = 10)
	private Long	id;
	@Column(name = "username" , length = 50)
	private String	name;
	private Long	sal;
	
	
	// 관계 설정
	@ManyToOne
	@JoinColumn(name = "team_id") // Team TBL의 team_id와 Join
	private Team team;	// 관련지을 TBL 선언
		// JpaMemberRepository의 member.setTeam(team);과 함께 연결됨
	
	@Transient
		// @Transient: 실제 column이 아닌, Buffer 용도
	private String teamname;	//Team 이름 선언
	@Transient
	private String teamid;
			// memberModifyForm.html의 teamid와 연결하기 위해
	
}
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	// 3. 데이터 수정 후 업데이트 => memberModifyForm.html과 연결됨
	@GetMapping(value = "/memberModifyForm")
	public String memberModifyForm(Member member, Model model) {
		System.out.println("MemberController memberModify id->"+member.getId());
		
		Member member3 = memberService.findByMember(member.getId());
		System.out.println("member3->"+member3);
		model.addAttribute("member", member3);
		
		return "members/memberModify";
		
	}
	
	@PostMapping(value = "/members/memberUpdate")
	public String memberUpdate(Member member, Model model) {
		System.out.println("MemberController memberUpdate member->"+member);
		
		memberService.memberUpdate(member);
		System.out.println("MemberController memberUpdate.updateByMember After...");
		
		return "redirect:/members";
	}
	
}
  • MemberService.class
package com.oracle.oBootJpa02.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}

	// 3. 데이터 수정 후 업데이트 => memberModifyForm.html과 연결됨
	public Member findByMember(Long memberId) {
		Member member1 = memberRepository.findByMember(memberId);
		System.out.println("MemberService findByMember member1->"+member1);
		return member1;
	}
	
	public void memberUpdate(Member member) {
		System.out.println("MemberService memberUpdate member->"+member);
		
		memberRepository.updateByMember(member);
		System.out.println("MemberService memberRepository.updateByMember After...");
		return;
	}
	
}
  • JpaMemberRepository.class 
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}

	
	// 3. 데이터 수정 후 업데이트 => memberModifyForm.html과 연결됨
	@Override
	public Member findByMember(Long memberId) {
		Member member = em.find(Member.class, memberId);
			// EntityManager의 문법
		return member;
	}
	
	@Override
	public int updateByMember(Member member) {
		int result = 0;
		System.out.println("JpaMemberRepository updateByMember member->"+member);
		Member member3 = em.find(Member.class, member.getId());
		// 존재하면 수정하기
		if (member3 != null) {
			// 1. 팀 저장
			System.out.println("JpaMemberRepository updateByMember member.getTeamid()"+member.getTeamid());
			Team team = em.find(Team.class, member.getTeamid());
			if (team != null) {
				team.setName(member.getTeamname());
				em.persist(team);
			}
			// 2. 회원 저장
			member3.setTeam(team);
			member3.setName(member.getName());
			em.persist(member3);
			result = 1;
		
		} else {
			result = 0;
			System.out.println("JpaMemberRepository updateByMember No Exist...");
		}
		
		return result;
	}
	
}
  • MemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}

	// 3. 데이터 수정 후 업데이트 => memberModifyForm.html과 연결됨
	@Override
	public Member findByMember(Long memberId) {
		Member member = em.find(Member.class, memberId);
			// EntityManager의 문법
		return member;
	}
	
	@Override
	public int updateByMember(Member member) {
		int result = 0;
		System.out.println("JpaMemberRepository updateByMember member->"+member);
		Member member3 = em.find(Member.class, member.getId());
		// 존재하면 수정하기
		if (member3 != null) {
			// 1. 팀 저장
			System.out.println("JpaMemberRepository updateByMember member.getTeamid()"+member.getTeamid());
			Team team = em.find(Team.class, member.getTeamid());
			if (team != null) {
				team.setName(member.getTeamname());
				em.persist(team);
			}
			// 2. 회원 저장
			member3.setTeam(team);
			member3.setName(member.getName());
			em.persist(member3);
			result = 1;
		
		} else {
			result = 0;
			System.out.println("JpaMemberRepository updateByMember No Exist...");
		}
		
		return result;
	}
	
}

수정 전
수정 후


4. 데이터 조회 (현장 HW 1)

  • memberList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA2 회원 조회</h1>

	<div class="nav">
		<form action="/members/search" method="get">
				회원검색이름 : <input type="text" name="name" placeholder="검색할 회원 이름을 입력하세요">
			<button type="submit">검색</button><p>
		</form>
	</div>
	
	<div class="container">
		<div>
			<table border="1">
				<thead>
					<tr>
						<th>No</th>
						<th>회원이름</th>
						<th>팀	이름</th>
					</tr>
				</thead>
				
				<tbody>
					<tr th:each="member : ${memberList}">
						<td th:text="${member.id}"></td>
						<td><a th:href="@{/memberModifyForm(id=${member.id})}" th:text="${member.name}"></a></td>
										<!-- thymeleaf 문법 -->
						<td th:text="${member.team.name}"></td>
					</tr>	
				</tbody>
			</table>
		</div>	<!-- container -->
	</div>
</body>
</html>
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	// 4. 회원 이름 검색 => (0807) 현장 HW 1
	@GetMapping(value = "/members/search")
	public String search(Member member, Model model) {
		System.out.println("members/search member.getName()->"+member.getName());
		
		List<Member> memberList = memberService.getListSearchMember(member.getName());
		System.out.println("MemberController /members/search memberList.size()->"+memberList.size());
		model.addAttribute("memberList",memberList);
		
		return "members/memberList";
	}
	
	
}
  • MemberService.class
package com.oracle.oBootJpa02.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}

	// 4. 회원 이름 검색 => (0807) 현장 HW 1
	public List<Member> getListSearchMember(String searchName) {
		System.out.println("MemberService getListSearchMember start...");
		System.out.println("MemberService getListSearchMember SearchName->"+searchName);
		
		List<Member> listMember = memberRepository.findByNames(searchName);
		System.out.println("MemberService getListSearchMember listMember.size()->"+listMember.size());
		return listMember;
	}
	
}
  • MemberRepository.class
package com.oracle.oBootJpa01.repository;

import java.util.List;

import com.oracle.oBootJpa01.domain.Member;

public interface MemberRepository {
	Member			memberSave(Member member);
	List<Member>	findAllMember();
	List<Member>	findByNames(String searchName);

}
  • JpaMemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}

	// 4. 회원 이름 검색 => (0807) 현장 HW 1
	@Override
	public List<Member> findByNames(String searchName) {
		String pname = searchName + '%';
		System.out.println("JpaMemberRepository findByNames name->"+pname);
		
		List<Member> memberList = em.createQuery("select m from Member m where name Like :name", Member.class)
									.setParameter("name", pname)
									.getResultList();
		System.out.println("JpaMemberRepository memberList.size()->"+memberList.size());
		return memberList;
	}
	
}

5. 데이터 조회3 (현장 HW 2)

  • memberList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>JPA2 회원 조회</h1>

	<div class="nav">
		<form action="/members/search" method="get">
				회원검색이름 : <input type="text" name="name" placeholder="검색할 회원 이름을 입력하세요">
			<button type="submit">검색</button><p>
		</form>
	</div>
	
	 
	<div class="nav">
		<form action="/findByListMembers" method="get">
				회원검색 id 큰것:  <input type="text"  name="id"    placeholder="회원검색 id을 입력하세요">
				회원검색 급여 큰것 : <input type="text" name="sal"   placeholder="회원검색 sal을 입력하세요">
			<button type="submit">검색 </button><p>
		</form>
	</div>
	
	<div class="container">
		<div>
			<table border="1">
				<thead>
					<tr>
						<th>No</th>
						<th>회원이름</th>
						<th>팀	이름</th>
					</tr>
				</thead>
				
				<tbody>
					<tr th:each="member : ${memberList}">
						<td th:text="${member.id}"></td>
						<td><a th:href="@{/memberModifyForm(id=${member.id})}" th:text="${member.name}"></a></td>
										<!-- thymeleaf 문법 -->
						<td th:text="${member.team.name}"></td>
					</tr>	
				</tbody>
			</table>
		</div>	<!-- container -->
	</div>
</body>
</html>
  • MemberController.class
package com.oracle.oBootJpa02.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	private final MemberService memberService;
	
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	// 5. 회원 id, 급여 큰 순서로 검색: => (0807) 현장 HW 2
	@GetMapping(value = "findByListMembers")
	public String findByListMembers(Member member, Model model) {
		List<Member> memberList = memberService.getListFindByMembers(member);
		// List<Member> listMember  = memberRepository.findByMembers(member.getId(),member.getSal());
	    // 검색조건으로 입력한 id 큰거 , sal 큰거
		System.out.println("memberList.get(0).getName()->"+memberList.get(0).getName());
		System.out.println("memberList.get(0).getTeam().getName()->"+memberList.get(0).getTeam().getName());
		
		model.addAttribute("memberList",memberList);
		return "members/memberList";
	}
	
}
  •  MemberService.class
package com.oracle.oBootJpa02.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;

@Service
@Transactional
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	
	// 5. 회원 id, 급여 큰 순서로 검색: => (0807) 현장 HW 2
	public List<Member> getListFindByMembers(Member member) {
		System.out.println("MemberService getListFindByMembers start...");
		System.out.println("MemberService getListFindByMembers searchName->"+member);
		
		List<Member> listMember = memberRepository.findByMembers(member.getId(), member.getSal());
		System.out.println("MemberService getListFindByMembers listMember.size()->"+listMember.size());
		
		return listMember;
	}
	
}
  • MemberRepository.class
package com.oracle.oBootJpa02.repository;

import java.util.List;

import com.oracle.oBootJpa02.domain.Member;

public interface MemberRepository {
	
	Member			memberSave(Member member);
	List<Member>	findAll();
	Member          findByMember(Long memberId);
	int 			updateByMember(Member member);
	List<Member> 	findByNames(String searchName);
	List<Member> 	findByMembers(Long pid, Long psal);

}
  • JpaMemberRepository.class 
package com.oracle.oBootJpa02.repository;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.domain.Team;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	@Autowired
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}

	// 5. 회원 id, 급여 큰 순서로 검색: => (0807) 현장 HW 2
	@Override
	public List<Member> findByMembers(Long pid, Long psal) {
		System.out.println("JpaMemberRepository findByNames id->"+pid);
		System.out.println("JpaMemberRepository findByNames sal->"+psal);
		
		List<Member> memberList = em.createQuery("select m from Member m where id > : id and sal > :sal", Member.class)
									.setParameter("id", pid)
									.setParameter("sal", psal)
									.getResultList();
		System.out.println("JpaMemberRepository memberList.size()->"+memberList.size());
		return memberList;
	}
	
	
}

 


(src/test/java 폴더)

  • MemberServiceTest.class
    Tip)
    • @SpringBootTest 
      • 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
      • 반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백
    • @BeforeEach
    • @Test
    • @Rollback
    • 필드 DI
    • 알파테스트
    • 베타테스트: 개발자의 관여없이, 사용자들이 메뉴얼을 통해 진행하는 테스트
package com.oracle.oBootJpa02;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa02.domain.Member;
import com.oracle.oBootJpa02.repository.MemberRepository;
import com.oracle.oBootJpa02.service.MemberService;

@SpringBootTest
	//@SpringBootTest : 스프링 부트 띄우고 테스트(이게 없으면 @Autowired 다 실패)
	//반복 가능한 테스트 지원, 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고
	//테스트가 끝나면 트랜잭션을 강제로 롤백
@Transactional
public class MemberServiceTest {

	@Autowired
	MemberService memberService;	// 필드 DI: 바로 injection 가능
	
	@Autowired
	MemberRepository memberRepository;
	
	@BeforeEach
		// Test하기 전에 공통적으로 실행할 로직을 넣기 위해 사용
	public void before1() {
		System.out.println("Test @BeforeEach...");
	}
	
	
	@Test
	//@Rollback(value = false)
		//@Rollback 값이 false면 commit를 하라는 뜻
	public void memberSave() {
		// 1. 조건
		Member member = new Member();
		member.setTeamname("고구려");
		member.setName("강이식");
		
		// 2. 행위(Action)
		Member member3 = memberService.memberSave(member);
		
		// 3. 결과(Result)
		System.out.println("MemberServiceTest memberSave member3->"+member3);
		
	}
	
	@Test
	
	public void memberFind() {
		// 1. 조건: 회원조회=> 이순신
		Long findId = 4L;
		
		// 2. 행위(Action)
		Member member = memberService.findByMember(findId);
		
		// 3. 결과(Result)
		System.out.println("MemberServiceTest memberFind member->"+member);
		
	}
	
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


질문목록

 


수업교재

3. JPA 1장

7. JPA 핵심 구성요소

 

8. JPA Entity 생명주기

 

9. JPA 영속성 Context

 

11. JPA Database Schema 자동 생성

Database Schema

  1. DDL을 애플리케이션 실행 시점에 자동 생성
    • DDL 생성 기능 예시
      1. 제약조건 추가: 회원 이름은 필수, 10자 초과할수 없다 ex) @Column(nullable = false, length = 10)
      2. 유니크 제약조건 추가: 
        ex) @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
      3. DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않음
  2. 테이블 중심 에서 객체 중심으로
  3. 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
  4. 이렇게 생성된 DDL은 개발 장비에서만 사용하고 운영장비에서 사용하지 말것 권장
  5. 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬 은 후 사용

    속성
    1. create
    2. update
    3. none

질문목록

1. Member.class에서 PK와 아닌 것들을 정하는 기준이 무엇인가?

  1. 먼저 PK에 대해 잠시 짚고가자면...
    • PK는 고유성을 가져야 한다. 즉 각 행(레코드)를 유일하게 식별할 수 있어야 하며, PK 값은 테이블 내에서 중복될 수 없다. 이러한 이유로 Null 값 또한 가질 수 없고, 특정 레코드를 명확히 식별하려면 필수적으로 값이 존재해야 한다.
    • 식별해야하는 이유: 데이터베이스에서 각 레코드를 식별하는 이유는, 예를 들자면 id를 PK로 설정하면 이 값으로 특정 회원을 고유한 값으로 식별할 수 있다
    • PK가 아닌 다른 일반 속성들은 해당 테이블의 실제 데이터를 저장하는 역할을 한다. 예를 들면 Member 테이블에 있는 name, sal, team 등은 특정 회원(PK)에 대한 추가 정보를 저장할 수 있다. 또한 중복값이 가능하여 동명이인을 관리하는 기능 같은 것이 가능하고, Null 값 또한 허용이 가능하다
  2. @Id, @GeneratedValue 어노테이션이 PK와 값을 결정한다!
    • @Id  어노테이션의 필드 안에 정의한 객체는 PK임을 명시한다. 이 필드는 테이블에서 각 레코드를 고유하게 식별하는데 사용한다
    • @GeneratedValue 어노테이션은 PK의 값이 자동으로 생성됨을 명시한다
    • ++ @Column 어노테이션은 데이터베이스에서 위에 명시한 필드의 이름과 상세정보를 정의하는 데 쓰인다
  3. @Id 필드가 끝나고 난 뒤의 메소드들은?
    • 필드가 끝나고 다시 @Column어노테이션이 있다면, 그 아래의 로직은 PK가 아닌 일반 속성이다. 

2. PK를 두 개 만들수 있는가?

  1. 가능함
    • JPA에서 테이블의 PK를 두 개 이상 설정하는 것을 복합키(Composite Key)라고 한다. 이를 구현하는 방법은 두가지가 있는데 @IdClass@EmbeddedId이다
    • @IdClass
      • 별도의 클래스를 만들어 해당 클래스에서 복합 키로 사용할 필드들을 정의한 후, 엔티티 클래스에서 이를 @IdClass로 사용한다
      • 복합 키로 사용할 필드들을 분리하여 엔티티 클래스에 직접 정의하며, 엔티티 클래스에 정의된 필드들이 @Id로 표시된다
      • @IdClass는 코드가 간결하고 이해하기 쉬워, 단순한 복합 키를 사용할 때 적합하다
    • @EmbeddedId
      • 복합 키를 별도의 임베디드 클래스(@Embeddable)로 정의한 후, 엔티티 클래스에서 @EmbeddedId로 사용한다.
      • 복합 키를 하나의 객체로 묶어서 엔티티 클래스에 포함시키며, 복합 키에 해당하는 필드들이 하나의 임베디드 객체로 처리된다
      • @EmbeddedId는 복합 키에 대한 추가 로직이나 메서드가 필요할 때 유용합니다. 복합 키를 하나의 객체로 관리할 수 있기 때문에 더 복잡한 상황에서 유리할 수 있다

3. @Id 어노테이션의 필드 안에 @column필드를 만들고 PK를 두 개 이상 만드는 것은 불가능한가?

  1. 불가능함
    • 복합 키(Composite Key)를 만들려면 여러 개의 필드를 Primary Key로 설정해야 하는데, @Id 어노테이션은 단일 필드에만 적용되기 때문이다. 즉, 하나의 필드에만 @Id를 적용할 수 있다

4. PK를 꼭 int나 Long처럼 숫자로만 정해야하는가? String 같은 것은?

  1. String같은 문자형도 가능하기는 하다. 다만 추천하지는 않는다
    • 데이터 유형 제한 없음: Primary Key는 반드시 숫자일 필요가 없으며, String, UUID, Date 등 다양한 데이터 유형을 사용할 수 있다.
      다만, 실무에서 많이 쓰이는 것은 관리 편의성과 성능 등의 이유로 주로 Long이나 int 같은 숫자형이다
    • 고유성과 효율성: Primary Key는 테이블 내에서 유일해야 하며, 고유성을 보장해야 하는데, String으로 지정하면 동일한 문자열이 Primary Key로 존재할 수 없다
    • 자동 생성: 숫자형 Primary Key는 자동 생성 전략(@GeneratedValue)과 함께 사용하기 쉽다. 
      그러나 String 타입의 경우에는 자동 생성 전략을 사용할 수 없으며, 이를 보완하기 위해서는 별도로 UUID 같은 고유한 문자열을 생성하여 설정해야 한다
    • 길이 제한: 지나치게 긴 문자열은 인덱싱 성능에 영향을 줄 수 있기 때문에 String를 PK로 사용할 때는 길이를 제한하는 것이 좋다

=> PK를 두 개 이상 만드는 것은 복잡하다. 그렇다면  FK를 두 개 만들어서 엮어볼까?


5. 그렇다면 Foreign Key를 두 개 만드는 방법은 무엇인가?

 

  1. @ManyToOne, @JoinColumn을 두 번 사용하기
    1. 두 개의 다른 필드가 각각 다른 테이블의 Primary Key(혹은 다른 열)을 참조하는 경우에 사용된다.
      이를 구현하기 위해 JPA에서는 @ManyToOne, @JoinColumn을 두 번 사용하여 각각의 Foreign Key를 설정할 수 있다.

==> 의문사항: 로직을 살펴보니 Member 테이블에만 외래키가 생기도록 구성이 되어있네?


6. MEMBER 테이블에만 기본키를 만드는 로직을 만든 것은 왜인가?

  1. 단방향 관계를 만들어 유지보수를 용이하게 하기 위해서!
    • 로직과 SQL에서 속성을 살펴보면 Member 엔티티가 Team 엔티티를 참조하지만, Team 엔티티는 Member를 직접 참조하지 않는 구조이다
    • 단방향 관계: 현재 코드에서 Member 엔티티는 Team 엔티티와 연관 관계를 맺고 있으며, 외래 키(team_id)는 Member 테이블에만 존재한다.
      즉, 하나의 Member가 하나의 Team에 속하고, 그 Team에 대한 참조를 Member 테이블에서 외래 키를 통해 관리한다
    • 이렇게 단방향으로 관계를 설정하는 것이 실무에서 많이 사용하는 일반적인 방법이다.
      또한 간단하고 효율적이며 오류 발생 가능성도 적다

7. MEMBER 테이블과 TEAM 테이블 둘 다 기본키와 외래키를 다 만들어서 연결시키면 문제가 생기는가?

  1. 생길 수 있다!
    • 양방향 관계: 이렇게 두 테이블을 서로 참조하게 만드는 방식을 양방향 관계라고 한다.
      이 경우 Team 엔티티에 List<Member>와 같은 컬렉션을 추가하고, mappedBy 속성을 사용하여 관계의 주인을 명시해야 한다. 이는 복잡해질 수 있으며, 모든 경우에 적합하지 않다
    • 순환 참조 문제: 두 테이블에 모두 외래 키를 설정하게 되면 순환 참조가 발생할 수 있는데, 이는 데이터베이스 설계와 JPA 매핑에서 혼란을 야기할 수 있다
    • 중복 데이터 문제: 양쪽에 외래 키가 있으면 데이터를 중복해서 저장해야 할 수 있으며, 이는 데이터 무결성 문제를 일으킬 수 있다
    • 복잡한 쿼리: 양방향 관계를 사용하면 JPA에서 복잡한 쿼리가 생성될 수 있고, 성능 문제로 이어질 수 있다

8. 외래키를 가진 쪽과 가지지 않은 쪽의 차이는 무엇인가?

  1. 관계의 주인(owner)
    • JPA에서 관계의 주인(owner) 이라는 개념은 외래 키를 관리하는 쪽을 의미한다
    • 외래 키가 있는 엔티티: 관계의 주인(Owner)입니다. 이 엔티티가 외래 키를 관리하며, 데이터베이스의 관계를 설정하고 갱신하는 역할을 한다
    • 외래 키가 없는 엔티티: 관계의 주인이 아니며, 관계를 거울처럼 바라보는 역할을 한다

9. (단순한 궁금증) member보다는 Team이 더 큰 속성처럼 보이는데, 왜 member 테이블에 외래키 관리 권한을 주었는가?

    1. 큰 속성이라고 관리권한을 갖는 것이 아니다! => 외래 키 관리 주체 결정의 이유는?
      => 누가 누구를 참조하느냐!!!
      1. 단순한 연관 관계 관리Member 엔티티가 Team 엔티티를 참조하는 경우, Member 테이블에 외래 키를 두는 것이 관계를 관리하기에 더 간단할 수 있다. 이 방법은 일반적으로 간단하고 효율적이다.
      2. 데이터의 밀접한 연관성: Member와 Team의 관계에서 Member가 실제로 Team에 속하는 것이므로, Member 테이블이 Team을 참조하는 것이 논리적으로 자연스러울 수는 있다.
        다만, Member가 Team을 참조하고 있으며, 하나의 Team에 여러 Member가 속할 수 있으므로, Member 테이블에 외래 키를 두는 것이 더 명확하다
      3. 양방향 관계 설정: 만약 양방향 관계를 설정한다고 가정해보자.
        1. Member가 Team을 참조하고 Team이 여러 Member를 가진다고 가정하면, Member 엔티티에서 외래 키를 관리하는 것이 관계의 명확성을 높이고 데이터 무결성을 유지하는 데 도움이 된다
        2. JPA에서는 관계의 주인을 명시하는 mappedBy 속성을 사용하여 양방향 관계를 설정할 수 있는데, 관계의 주인을 명확히 지정하면 데이터베이스의 외래 키를 관리하는 쪽을 제어할 수 있다
      4. 직관적: Member가 Team을 참조하는 것이 더 직관적일 수 있으며, Member에서 Team에 대한 변경 사항을 더 쉽게 처리할 수 있다
      5. 변경사항 제어: Member에 외래 키를 두면, Team에 대한 변경을 Member에서 직접 제어할 수 있
    2. 단점
      • 데이터베이스의 논리적 설계와는 맞지 않을 수 있으며, Team의 입장에서는 자신이 직접 Member를 참조하지 않기 때문에 데이터 조작이 복잡해질 수 있다
      • 양방향 관계를 설정한다고 가정하면, Team에서 Member를 관리하려면 추가적인 설정과 처리가 필요하다

==> 결론: 데이터의 논리적 연관성과 관리의 단순함을 위해서

    1.  

4. JPA 2장

 


오늘의 숙제

'Spring' 카테고리의 다른 글

2024_08_08_목~08_12_월  (0) 2024.08.08
2024_08_07_수~08_08_목  (0) 2024.08.07
2024_08_05_월~08_06_화  (0) 2024.08.05
2024_08_02_금  (0) 2024.08.02
2024_08_01_목  (0) 2024.08.01