자바 ORM 표준 JPA 프로그래밍 - 기본편
Intro
자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 내용을 정리 해보았습니다.
1. JPA 소개
- JPA?
- Java Persistence API
- 자바 진영의 ORM 기술 표준
- ORM?
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORM 프레임워크가 중간에서 매핑
- JPA는 애플리케이션과 JDBC 사이에서 동작
- JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
- 영속성 컨텍스트
2. 영속성 컨텍스트
- 영속성 컨텍스트란?
- 영속성 컨텐스트는 엔티티를 영구 저장하는 환경이라는 뜻
- 애플리케이션과 DB 사이에서 객체를 보관하는 가상의 DB 역할을 한다.
- 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리하게 된다
- 엔티티 생명주기
- 비영속 (new/transient) - 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속 (managed) - 영속성 컨텍스트에 관리되는 상태
- 준영속 (detached) - 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed) - 삭제된 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성 (identity) 보장
Member a = em.find(Member.class, "member1"); Member b = em.find(Member.class, "member1"); System.out.println(a == b); //동일성 비교 true
- 트랜잭션을 지원하는 쓰기 지연 (transactional writer-behind)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- 변경 감지 (Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
- 지연 로딩 (Lazy Loading)
- 지연로딩은 연관 관계 매핑되어 있는 엔티티를 조회 시 우선 프록시 객체를 반환하고, 실제로 필요할 때 쿼리를 날려 가져오는 기능이다.
즉, 필요할 때 데이터를 가져오는 기능이다.
- 지연로딩은 연관 관계 매핑되어 있는 엔티티를 조회 시 우선 프록시 객체를 반환하고, 실제로 필요할 때 쿼리를 날려 가져오는 기능이다.
3. 엔티티 매핑
- 엔티티 매핑 소개
- 객체와 테이블 매핑: @Entity, @Table
- 필드와 컬럼 매핑: @Column
- 기본 키 매핑: @Id
- 연관관계 매핑: @ManyToOne, @JoinColumn
package hellojpa;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@Enumerated(EnumType.STRING) //enum 타입 매핑
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP) //날짜 타입 매핑
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob //BLOB, CLOB 매핑
private String description;
//Getter, Setter…
}
단방향 연관관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne // 현재 클래스가 Many, 참조 대상이 One 이라고 설정해주는 역할을 함
@JoinColumn(name = "TEAM_ID") //참조 대상인 Team 객체와 실제 테이블에 있는 FK 컬럼을 매핑해주는 역할을 함
private Team team;
}
- 연관관계 저장
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
- 참조로 연관관계 조회 - 객체 그래프 탐색
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
- 연관관계 수정
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
member.setTeam(teamB);
양방향 연관관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 나를 참조하고 있는 엔티티에서 어떤 필드에 나를 매핑해두었는지 명시
// Member 테이블에서 team 이라는 변수명으로 Team 객체를 참조하고 있으니 mappedBy의 값으로 team을 준다
List<Member> members = new ArrayList<Member>();
}
연관관계의 주인
- 양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용X
- 주인이 아니면 mappedBy 속성으로 주인 지정
- 누구를 주인으로?
- 외래 키가 있는 곳을 주인으로 정해라 (1:N 에서 N쪽을 연관관계 주인으로)
- 여기서는 Member.team이 연관관계의 주인
- 양방향 매핑시 가장 많이 하는 실수 (연관관계의 주인에 값을 입력하지 않음)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**
em.persist(member);
- 양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)
4. 프록시
- Member 엔티티를 조회할 때 Team도 함께 조회해야 할까?
- 실제로 필요한 비즈니스 로직에 따라 다르다
- 비즈니스 로직에서 필요하지 않을 때가 있는데, 항상 Team을 함께 가져와서(join) 사용할 필요는 없다
- 낭비가 발생하게 된다
- JPA는 이 낭비를 하지 않기 위해, 지연로딩과 프록시라는 개념으로 해결한다
- em.find() vs em.getReference()
- em.find() 는 DB를 통해서 실제 엔티티 객체를 조회하는 메서드
- em.getReference() 는 DB의 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 메서드이다
프록시 특징
- 실제 클래스를 상속받아서 만들어진다 (하이버네이트가 내부적으로 상속받아서 만든다)
- 실제 클래스와 겉 모양이 같다
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다
- 프록시 객체는 실제 객체의 참조를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
- 프록시 객체는 처음 사용할 때 한 번만 초기화 된다
즉시 로딩과 지연 로딩
- 프록시 학습 처음에 했던 질문 Member를 조회할 때 Team도 함께 조회 해야 할까?
- 비즈니스 로직에서 단순히 멤버 로직만 사용하는데 함께 조회하면, 아무리 연관관계가 걸려있다고 해도 손해이다
- JPA는 이 문제를 지연로딩 LAZY를 사용해서 프록시로 조회하는 방법으로 해결 한다
@Entity public class Member { @Id @GeneratedValue private Long id; @Column(name = "USERNAME") private String name; @ManyToOne(fetch = FetchType.LAZY) //** @JoinColumn(name = "TEAM_ID") private Team team; }
- Member와 Team을 자주 함께 사용한다면? (즉시로딩)
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- 프록시와 즉시로딩 주의
- @ManyToOne, @OneToOne은 기본이 즉시 로딩
- @OneToMany, @ManyToMany는 기본이 지연 로딩
- 지연 로딩 활용 - 실무
- 모든 연관관계에 지연 로딩을 사용해라!
- 실무에서 즉시 로딩을 사용하지 마라!
- 즉시 로딩은 상상하지 못한 쿼리가 나간다
Leave a comment