인프런 이영한님의 강의를 바탕으로 작성된 내용입니다.
DDL을 애플리케이션 실행 시점에 자동생성
* DDL(Data Definition Language) : 데이터베이스를 정의하는 언어이며, 데이터를 생성, 수정, 삭제하는 등의 데이터의 전체의 골격을 결정하는 역할을 하는 언어 -> create : 데이터베이스, 테이블등을 생성 alter : 테이블을 수정 drop : 데이터베이스, 테이블을 삭제 truncate : 테이블을 초기화
테이블 중심 객체 중심
여러명이 사용하는 테스트 서버/ 개발 서버에 왠만하면 사용안하는게 나음
DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
• EnumType.ORDINAL: enum 순서를 데이터베이스에 저장
• EnumType.STRING: enum 이름을 데이터베이스에 저장
@Enumerated(EnumType.STRING) 로 사용 : 이유) 예 enum 목록이 초기 {USER, ADMIN} 에서 {GUEST, USER, ADMIN} 로 변경시 ORDINAL을 사용하면 순서가 뒤로 밀려 치명적이 버그가 발생 할 수 있다.
- 직접 할당
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
기본키 생성을 데이터베이스에 위임하기 때문에 id값을 처음에 알고있지 못함
JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행 하고 DB에서 식별자를 조회
@Entity
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1) // 1부터 id값을 시작
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
private Long id;
stratege 가 sequence 인것을 확인후 id 값을 가져오기 위해 데이터베이스와 통신을 계속하면 성능이 떨어지지 않을까?
allocationSize = 50 속성을 통해 다음 Sequence id를 얻기전까지 어플리케이션 메모리의 id를 하나씩 높여가면서 id를 부여, 만약 51에 도달하면 다시 DB로 부터 그 다음 시퀀스를 받아 온다.
Member member1 = new Member();
member1.setUsername("A");
Member member2 = new Member();
member2.setUsername("B");
Member member3 = new Member();
member3.setUsername("C");
System.out.println("===========================");
// DB SEQ 1| 1
// DB SEQ 51| 1 -> 성능 최적화를 하기위해 두번 호출된다. 50개씩 메모리를 사용하기 위해
// DB SEQ 51| 2
// DB SEQ 51| 3
em.persist(member1);
em.persist(member2);
em.persist(member3);
System.out.println("member1.getId : "+ member1.getId());
System.out.println("member2.getId : "+ member2.getId());
System.out.println("member3.getId : "+ member3.getId());
System.out.println("===========================");
- 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉 내내는 전략
- 장점: 모든 데이터베이스에 적용 가능
- 단점: 성능
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 1 )
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
private Long id;


OrderItem 객체를 통해 orderId와 itemId 를 다 가지고 있음 테이블과 똑같이 설계한 것임, 이 모델을 통해 개선해 나가는 작업을 진행할 것이다.
// 조회 -> jpa 에게 계속 물어봐야한다. 객체지향 스럽지않다.
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
객체는 참조를 사용해서 연관된 객체를 찾는다.
테이블과 객체 사이에는 이런 큰 간격이 존재

// 저장
Team team1 = new Team();
team1.setName("TeamA");
em.persist(team1);
Member member = new Member();
member.setUsername("member1");
//member.setTeamId(team1.getTeamId()); // 테이블 맞춤 설계
member.setTeam(team1); // 객체지향 모델링
em.persist(member);
em.flush();// 영속성 컨텍스트에 쌓여있는 1차캐시 commit 후 싱크를 맞춘후
em.clear(); // 영속성 컨텍스트 비운다 -> DB에서 데이터를 찾아오기 위해
Member findMember = em.find(Member.class, member.getId());
// 조회 -> jpa 에게 계속 물어봐야한다. 객체지향 스럽지않다.
// Long findTeamId = findMember.getTeamId();
// Team findTeam = em.find(Team.class, findTeamId);
// 조회 객체 지향 모델링
Team findTeam = findMember.getTeam();
System.out.println("findTeam = " + findTeam.getName());
//
Team newTeam= em.find(Team.class, 100L); // team_id : 100L 있다고 가정
findMember.setTeam(newTeam); // DB의 외래키가 100L으로 수정

@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long teamId;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>(); // 관례 : 초기화를 해놔야 null point가 뜨지 않기 때문에
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//@Column(name = "TEAM_ID")
//private Long teamId;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.




외래 키가 있는 곳을 주인으로 정해라.

ManyToOne 쪽이 주인이 된다. -> 다(Many) 쪽이 주인이 된다.
// 연관 관계 메서드 또는 상태를 변경하는 메서드 명에 set을 쓰지 않는다. Getter Setter 관례 때문에
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
단방향 매핑만으로도 이미 연관관계 매핑은 완료
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가 가능 (테이블에 영향을 주지 않는다.)

주인이 아닌쪽은 읽기만 가능하기 때문에 team.getMembers().add(member) 를 호출 하더라도 데이터베이스를 조회해 보면 데이터가 추가되지 않는것을 확인할 수 있다.
그렇다면 주인쪽에서만 member.setTeam(team) 을 호출해서 연관관계 설정을 해주면 될까?
주인쪽에만 설정해주면 돠는게 맞긴 하지만 가장 권장하는 방법은 주인이 아닌쪽에도 설정을 해주것이다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
// team.getMembers().add(member);
Team findTeam = em.find(Team.class, team.getId()); //1차 캐시에 있음
List<Member> members = findTeam.getMembers();
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
em.persist(team);
→ team 엔티티가 영속성 컨텍스트의 1차 캐시에 저장됨
Team findTeam = em.find(Team.class, team.getId());
→ em.find()로 team 엔티티를 조회하면 위에서 1차 캐시에 저장된 team 엔티티를 반환한다.
List<Member> members = findTeam.getMembers();
→ 조회시 아무것도 출력되지 않는다.
why?
// team.getMembers().add(member);
→ 이 부분이 주석처리가 되어있기 때문이다.
이 부분이 주석처리가 되어있어도 tx.commit() 을 호출하는 시점에는 데이터베이스에 정상적으로 반영이 되기 때문에 DB에 저장하는 것 자채는 문제가 없지만 저 시점에서 team.getMembers() 로 회원을 조회해오면 컬렉션에는 저장되어 있지 않아서 뭔가 굉장히 헷갈리고 실수를 하게 된다.
참고) team.getMembers()를 호출하면 1차 캐시에서 가져오는 것이 아니라 팀에 소속된 회원들을 조회하는 SELECT 쿼리가 실행된다.
그러므로 양방향 연관관계를 설정할 때에는 양쪽에다가 값을 전부 세팅하는것을 권장한다.