인프런 이영한님의 강의를 바탕으로 작성된 내용입니다.
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 쿼리가 실행된다.
그러므로 양방향 연관관계를 설정할 때에는 양쪽에다가 값을 전부 세팅하는것을 권장한다.