오늘의 코딩순서
(폴더: 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 테이블 컬럼을 맵핑함
- 속성
- name : 맵핑할 테이블의 컬럼 이름을 지정
- insertable : 엔티티 저장시 선언된 필드도 같이 저장
- updateable : 엔티티 수정시 이 필드를 함께 수정
- table : 지정한 필드를 다른 테이블에 맵핑
- nullable : NULL을 허용할지, 허용하지 않을지 결정
- unique : 제약조건을 걸 때 사용
- columnDefinition : DB 컬럼 정보를 직접적으로 지정할 때 사용
- length : varchar의 길이를 조정합니다. 기본값으로 255가 입력
- precsion, scale : BigInteger, BigDecimal 타입에서 사용, 각각 소수점 포함 자리수, 소수의 자리수를 의미
- @SequenceGenerator
- 테이블 마다 시퀀스 오브젝트를 따로 관리하고 싶을때 사용하는 어노테이션
- sequence를 지원하는 프로그램의 데이터베이스에서만 사용할 수 있음
- 속성
- name : 식별자 생성기 이름이며 필수로 작성
- sequnceName : 데이터베이스에 등록되어 있는 시퀀스 이름으로 기본값은 hibernate_sequence 입니다.
- initialValue : DDL 생성 시에만 사용되며 DDL을 생성할 때 처음 시작하는 수로 기본값은 1입니다.
- allocationSize : 시퀀스 한 번 호출에 증가하는 수로 기본값은 50입니다.
- catalog, schema: 데이터베이스 catelog, schema 이름
- SEQUENCE?: 데이터베이스의 특별한 오브젝트 시퀀스를 사용하여 기본키를 생성함,
DB Sequence란 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
- @ManyToOne
- Foreign Key를 걸어주는 TBL이 주체⭐
- @JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가짐
- N:1의 관계, 단방향의 연관관계
- 가장 많이 쓰이는 연관관계임
∵ 엔티티의 관계를 표현하고 FK 관리에 있어서 가장 자연스럽기 때문
- @JoinColumn: 엔티티 테이블에 FK 칼럼을 정의해줌
- 속성
- name: 매핑할 외래 키의 이름을 지정하는 속성
- precision: 데이터베이스 열의 전체 자릿수를 지정하는 값을 가져오거나 설정함
- @Id
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
- 임시선언
- @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
- 알파테스트
- 베타테스트: 개발자의 관여없이, 사용자들이 메뉴얼을 통해 진행하는 테스트
- @SpringBootTest
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
- DDL을 애플리케이션 실행 시점에 자동 생성
- DDL 생성 기능 예시
- 제약조건 추가: 회원 이름은 필수, 10자 초과할수 없다 ex) @Column(nullable = false, length = 10)
- 유니크 제약조건 추가:
ex) @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )}) - DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않음
- DDL 생성 기능 예시
- 테이블 중심 에서 객체 중심으로
- 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
- 이렇게 생성된 DDL은 개발 장비에서만 사용하고 운영장비에서 사용하지 말것 권장
- 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬 은 후 사용
속성- create
- update
- none
질문목록
1. Member.class에서 PK와 아닌 것들을 정하는 기준이 무엇인가?
- 먼저 PK에 대해 잠시 짚고가자면...
- PK는 고유성을 가져야 한다. 즉 각 행(레코드)를 유일하게 식별할 수 있어야 하며, PK 값은 테이블 내에서 중복될 수 없다. 이러한 이유로 Null 값 또한 가질 수 없고, 특정 레코드를 명확히 식별하려면 필수적으로 값이 존재해야 한다.
- 식별해야하는 이유: 데이터베이스에서 각 레코드를 식별하는 이유는, 예를 들자면 id를 PK로 설정하면 이 값으로 특정 회원을 고유한 값으로 식별할 수 있다
- PK가 아닌 다른 일반 속성들은 해당 테이블의 실제 데이터를 저장하는 역할을 한다. 예를 들면 Member 테이블에 있는 name, sal, team 등은 특정 회원(PK)에 대한 추가 정보를 저장할 수 있다. 또한 중복값이 가능하여 동명이인을 관리하는 기능 같은 것이 가능하고, Null 값 또한 허용이 가능하다
- @Id, @GeneratedValue 어노테이션이 PK와 값을 결정한다!
- @Id 어노테이션의 필드 안에 정의한 객체는 PK임을 명시한다. 이 필드는 테이블에서 각 레코드를 고유하게 식별하는데 사용한다
- @GeneratedValue 어노테이션은 PK의 값이 자동으로 생성됨을 명시한다
- ++ @Column 어노테이션은 데이터베이스에서 위에 명시한 필드의 이름과 상세정보를 정의하는 데 쓰인다
- @Id 필드가 끝나고 난 뒤의 메소드들은?
- 필드가 끝나고 다시 @Column어노테이션이 있다면, 그 아래의 로직은 PK가 아닌 일반 속성이다.
2. PK를 두 개 만들수 있는가?
- 가능함
- JPA에서 테이블의 PK를 두 개 이상 설정하는 것을 복합키(Composite Key)라고 한다. 이를 구현하는 방법은 두가지가 있는데 @IdClass와 @EmbeddedId이다
- @IdClass
- 별도의 클래스를 만들어 해당 클래스에서 복합 키로 사용할 필드들을 정의한 후, 엔티티 클래스에서 이를 @IdClass로 사용한다
- 복합 키로 사용할 필드들을 분리하여 엔티티 클래스에 직접 정의하며, 엔티티 클래스에 정의된 필드들이 @Id로 표시된다
- @IdClass는 코드가 간결하고 이해하기 쉬워, 단순한 복합 키를 사용할 때 적합하다
- @EmbeddedId
- 복합 키를 별도의 임베디드 클래스(@Embeddable)로 정의한 후, 엔티티 클래스에서 @EmbeddedId로 사용한다.
- 복합 키를 하나의 객체로 묶어서 엔티티 클래스에 포함시키며, 복합 키에 해당하는 필드들이 하나의 임베디드 객체로 처리된다
- @EmbeddedId는 복합 키에 대한 추가 로직이나 메서드가 필요할 때 유용합니다. 복합 키를 하나의 객체로 관리할 수 있기 때문에 더 복잡한 상황에서 유리할 수 있다
3. @Id 어노테이션의 필드 안에 @column필드를 만들고 PK를 두 개 이상 만드는 것은 불가능한가?
- 불가능함
- 복합 키(Composite Key)를 만들려면 여러 개의 필드를 Primary Key로 설정해야 하는데, @Id 어노테이션은 단일 필드에만 적용되기 때문이다. 즉, 하나의 필드에만 @Id를 적용할 수 있다
4. PK를 꼭 int나 Long처럼 숫자로만 정해야하는가? String 같은 것은?
- String같은 문자형도 가능하기는 하다. 다만 추천하지는 않는다
- 데이터 유형 제한 없음: Primary Key는 반드시 숫자일 필요가 없으며, String, UUID, Date 등 다양한 데이터 유형을 사용할 수 있다.
다만, 실무에서 많이 쓰이는 것은 관리 편의성과 성능 등의 이유로 주로 Long이나 int 같은 숫자형이다 - 고유성과 효율성: Primary Key는 테이블 내에서 유일해야 하며, 고유성을 보장해야 하는데, String으로 지정하면 동일한 문자열이 Primary Key로 존재할 수 없다
- 자동 생성: 숫자형 Primary Key는 자동 생성 전략(@GeneratedValue)과 함께 사용하기 쉽다.
그러나 String 타입의 경우에는 자동 생성 전략을 사용할 수 없으며, 이를 보완하기 위해서는 별도로 UUID 같은 고유한 문자열을 생성하여 설정해야 한다 - 길이 제한: 지나치게 긴 문자열은 인덱싱 성능에 영향을 줄 수 있기 때문에 String를 PK로 사용할 때는 길이를 제한하는 것이 좋다
- 데이터 유형 제한 없음: Primary Key는 반드시 숫자일 필요가 없으며, String, UUID, Date 등 다양한 데이터 유형을 사용할 수 있다.
=> PK를 두 개 이상 만드는 것은 복잡하다. 그렇다면 FK를 두 개 만들어서 엮어볼까?
5. 그렇다면 Foreign Key를 두 개 만드는 방법은 무엇인가?
- @ManyToOne, @JoinColumn을 두 번 사용하기
- 두 개의 다른 필드가 각각 다른 테이블의 Primary Key(혹은 다른 열)을 참조하는 경우에 사용된다.
이를 구현하기 위해 JPA에서는 @ManyToOne, @JoinColumn을 두 번 사용하여 각각의 Foreign Key를 설정할 수 있다.
- 두 개의 다른 필드가 각각 다른 테이블의 Primary Key(혹은 다른 열)을 참조하는 경우에 사용된다.
==> 의문사항: 로직을 살펴보니 Member 테이블에만 외래키가 생기도록 구성이 되어있네?
6. MEMBER 테이블에만 기본키를 만드는 로직을 만든 것은 왜인가?
- 단방향 관계를 만들어 유지보수를 용이하게 하기 위해서!
- 로직과 SQL에서 속성을 살펴보면 Member 엔티티가 Team 엔티티를 참조하지만, Team 엔티티는 Member를 직접 참조하지 않는 구조이다
- 단방향 관계: 현재 코드에서 Member 엔티티는 Team 엔티티와 연관 관계를 맺고 있으며, 외래 키(team_id)는 Member 테이블에만 존재한다.
즉, 하나의 Member가 하나의 Team에 속하고, 그 Team에 대한 참조를 Member 테이블에서 외래 키를 통해 관리한다 - 이렇게 단방향으로 관계를 설정하는 것이 실무에서 많이 사용하는 일반적인 방법이다.
또한 간단하고 효율적이며 오류 발생 가능성도 적다
7. MEMBER 테이블과 TEAM 테이블 둘 다 기본키와 외래키를 다 만들어서 연결시키면 문제가 생기는가?
- 생길 수 있다!
- 양방향 관계: 이렇게 두 테이블을 서로 참조하게 만드는 방식을 양방향 관계라고 한다.
이 경우 Team 엔티티에 List<Member>와 같은 컬렉션을 추가하고, mappedBy 속성을 사용하여 관계의 주인을 명시해야 한다. 이는 복잡해질 수 있으며, 모든 경우에 적합하지 않다 - 순환 참조 문제: 두 테이블에 모두 외래 키를 설정하게 되면 순환 참조가 발생할 수 있는데, 이는 데이터베이스 설계와 JPA 매핑에서 혼란을 야기할 수 있다
- 중복 데이터 문제: 양쪽에 외래 키가 있으면 데이터를 중복해서 저장해야 할 수 있으며, 이는 데이터 무결성 문제를 일으킬 수 있다
- 복잡한 쿼리: 양방향 관계를 사용하면 JPA에서 복잡한 쿼리가 생성될 수 있고, 성능 문제로 이어질 수 있다
- 양방향 관계: 이렇게 두 테이블을 서로 참조하게 만드는 방식을 양방향 관계라고 한다.
8. 외래키를 가진 쪽과 가지지 않은 쪽의 차이는 무엇인가?
- 관계의 주인(owner)
- JPA에서 관계의 주인(owner) 이라는 개념은 외래 키를 관리하는 쪽을 의미한다
- 외래 키가 있는 엔티티: 관계의 주인(Owner)입니다. 이 엔티티가 외래 키를 관리하며, 데이터베이스의 관계를 설정하고 갱신하는 역할을 한다
- 외래 키가 없는 엔티티: 관계의 주인이 아니며, 관계를 거울처럼 바라보는 역할을 한다
9. (단순한 궁금증) member보다는 Team이 더 큰 속성처럼 보이는데, 왜 member 테이블에 외래키 관리 권한을 주었는가?
- 큰 속성이라고 관리권한을 갖는 것이 아니다! => 외래 키 관리 주체 결정의 이유는?
=> 누가 누구를 참조하느냐!!!- 단순한 연관 관계 관리: Member 엔티티가 Team 엔티티를 참조하는 경우, Member 테이블에 외래 키를 두는 것이 관계를 관리하기에 더 간단할 수 있다. 이 방법은 일반적으로 간단하고 효율적이다.
- 데이터의 밀접한 연관성: Member와 Team의 관계에서 Member가 실제로 Team에 속하는 것이므로, Member 테이블이 Team을 참조하는 것이 논리적으로 자연스러울 수는 있다.
다만, Member가 Team을 참조하고 있으며, 하나의 Team에 여러 Member가 속할 수 있으므로, Member 테이블에 외래 키를 두는 것이 더 명확하다 - 양방향 관계 설정: 만약 양방향 관계를 설정한다고 가정해보자.
- Member가 Team을 참조하고 Team이 여러 Member를 가진다고 가정하면, Member 엔티티에서 외래 키를 관리하는 것이 관계의 명확성을 높이고 데이터 무결성을 유지하는 데 도움이 된다
- JPA에서는 관계의 주인을 명시하는 mappedBy 속성을 사용하여 양방향 관계를 설정할 수 있는데, 관계의 주인을 명확히 지정하면 데이터베이스의 외래 키를 관리하는 쪽을 제어할 수 있다
- 직관적: Member가 Team을 참조하는 것이 더 직관적일 수 있으며, Member에서 Team에 대한 변경 사항을 더 쉽게 처리할 수 있다
- 변경사항 제어: Member에 외래 키를 두면, Team에 대한 변경을 Member에서 직접 제어할 수 있
- 단점
- 데이터베이스의 논리적 설계와는 맞지 않을 수 있으며, Team의 입장에서는 자신이 직접 Member를 참조하지 않기 때문에 데이터 조작이 복잡해질 수 있다
- 양방향 관계를 설정한다고 가정하면, Team에서 Member를 관리하려면 추가적인 설정과 처리가 필요하다
==> 결론: 데이터의 논리적 연관성과 관리의 단순함을 위해서
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 |