티스토리 뷰
자바 ORM 표준 JPA 프로그래밍 - 기본편
김영한님 강의듣고 정리하기
1. JPA 시작하기
스프링과 스프링 데이터 JPA를 쓰지않고 JPA 사용해보기
public class JpaMain {
public static void main(String[] args) {
// 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체의 공유
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 엔티티 매니저는 쓰레드간에 공유X (사용하고 버려야한다.)
EntityManager em = emf.createEntityManager();
// JPA의 모든 데이터 변경은 트랜잭션 안에서 실행한다.
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
tx.commit();
}catch (Exception e){
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
JPQL
테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
SQL을 추상화해서 특정 데이터베이스 SQL에 의존 X, JPQL을 한마디로 정의하면 객체지향 SQL
QueryDSL
JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음, 비표준 오픈소스 프레임워크이다.
NativeQuery
JPA에서 JPQL 대신 직접 SQL을 사용하는 것을 말한다.
특정 데이터베이스에 의존하는 기능을 사용해야 할 때 사용한다.
2. 영속성 관리 - 내부 동작 방식
영속성 컨텍스트
엔티티를 영구 저장하는 환경이라는 뜻이고 논리적인 개념이다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.
엔티티의 생명주기
// 객체를 생성만한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
// 영속성 컨텍스트에서 삭제 (준영속)
em.detach(member);
// db에서 삭제 (삭제)
em.remove
영속성 컨텍스트(Persistence Context)의 이점
- 1차 캐시
영속화된 엔티티를 저장하여 @id값이 같은 엔티티를 찾을 때 직접 DB에 쿼리 실행을 요청하지 않고 엔티티가 저장된 공간에서 가져오게 되는데 이 공간을 1차 캐시라 한다.
- 동일성 보장
동일한 @id 값으로 검색한 엔티티는 동일성이 보장된다. 즉, 동일한 레퍼런스라는 뜻이다.
영속성 컨텍스트가 없다면 DB에서는 user1이 한명인데, 객체로는 user1이 3명이 존재 할 수 있다.
- 쓰기 지연
한 트랜잭션안에서 여러번 persist 함수를 호출하여 DB에 저장을 시도할 때 persist 함수가 호출된 시점이 아닌 트랜잭션이 커밋되는 시점에 한 번에 insert 쿼리가 실행되어 네트워크 비용을 줄일 수 있고 트랜잭션이 커밋되지 않거나 중간에 의도치 않은 익셉션이 발생하면 트렌젝션이 롤백이되어 DB에 데이터가 반영되지 않는다.
- 변경 감지
영속화된 엔티티의 값을 변경한다면 다시 persist 함수를 호출하여 DB에 명령을 보내는 것이 아니라 트렌젝션이 커밋되는 시점에 스냅샵과 비교하여 엔티티의 데이터가 변경이 되었다면 update 쿼리실행을 요청한다.
- 지연 로딩
@ManyToOne 등의 엔티티간 연관관계가 맺어져 있는 엔티티를 검색할때 연관관계가 있는 엔티티는 프록시 데이터로 채우고 실제로 사용 할 때 검색하게 하는 지연 로딩이 되어 불필요한 쿼리를 실행하지 않게 도와준다.
플러시
영속성 컨텍스트의 변경내용을 데이터베이스에 반영 (영속성 컨텍스트에 있던 값이 사라지는게 아니라 동기화 한다.)
트랜잭션이라는 작업단위가 중요하다. 커밋직전에만 동기화 하면 된다.
영속성 컨텍스트를 플러시 하는 방법
- em.flush() - 직접호출
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 호출 - 플러시 자동 호출
준영속 상태
영속상태의 엔티티가 영속성 컨텍스트에서 분리, 영속성 컨텍스트가 제공하는 기능 사용 못한다.
준영속 상태 만드는법
- em.detach(entity) - 특정 엔티티만 준영속 상태로 전환
- em.clear() - 영속성 컨텍스트를 완전히 초기화
- em.close() - 영속성 컨텍스트를 종료
3. 엔티티 매핑
@Entity
Entity가 붙은 클래스는 JPA가 관리하고, 엔티티라 한다.
JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수이다.
- 기본생성자 필수
- final 클래스, enum, interface, inner 클래스 사용 X
- 저장할 필드에 final 사용 X
데이터베이스 스키마 자동 생성
운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
매핑 어노테이션 정리
- @Column - 컬럼 매핑
- @Temporal - 날짜 타입 매핑 (LocalDate, LocalDateTime 사용 시 생략가능)
- @Enumerated - enum 타입 매핑
- @Lob - BLOB, CLOB 매핑
- @Transient - 특정 필드를 컬럼에 매핑하지 않음 (매핑무시)
@Column 속성
기본키 매핑
직접할당 : @Id만 사용
자동생성 (@GeneratedValue)
- IDENTITY : 데이터베이스에 위임, MYSQL (AUTO_INCREMENT)
- JPA는 보통 트랜잭션 커밋 시점에 INSERT 쿼리를 날리는데 AUTO_INCREMENT는 데이터베이스에 INSERT 쿼리를 실행한 후에 ID값을 알 수가 있어서 IDENTITY 전략은 em.persist() 시점에 즉시 INSERT 쿼리를 DB에 날린다.
- SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE (@SequenceGenerator 필요)
- TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용 (@TableGenerator 필요)
- AUTO : 방언에 따라 자동 지정 (AUTO_INCREMENT 또는 Sequnce Object 사용)
4. 연관관계 매핑 기초
용어이해
- 방향 (Direction) : 단방향, 양방향
- 다중성 (Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
- 연관관계의 주인 (Owner) : 객체 양방향 연관관계는 관리 주인이 필요
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다. (객체 지향적인 방법이 아니다.)
단방향 연관관계
단방향은 외래키를 사용하는 곳에만 외래키 매핑을 작성해주면 된다.
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID") // name = (필드명)_(참조할 테이블에 기본키 컬럼명)
private Team team;
}
조회
Member findMember = em.find(Member.class, member.getId()); // 조회
Team findTeam = findMember.getTeam(); // 참조를 사용해서 연관관계조회
양방향 연관관계와 연관관계의 주인
Member 엔티티는 단방향과 동일
@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
}
Team 엔티티는 컬렉션 추가
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // team이 연관관계의 주인이다.
List<Member> members = new ArrayList<Member>(); // members에 값을 넣어도 아무일도 일어나지 않는다.
}
조회
Team findTeam = em.find(Team.class, team.getId()); // 조회
int memberSize = findTeam.getMembers().size(); // 역방향 조회
연관관계의 주인과 mappedBy
객체와 테이블간에 연관관계를 맺는 차이를 이해해야한다.
객체연관관계 = 2개
- 회원 -> 팀 연관관계 1개 (단방향)
- 팀 -> 회원 연관관계 1개 (단방향)
테이블 연관관계 = 1개
- 회원 <-> 팀의 연관관계 1개 (양방향)
객체의 양방향 관계는 서로 다른 단방향 관계 2개이다. 그런데 테이블은 외래키 하나로 두테이블의 연관관계를 관리한다.
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정해야한다.
- 연관관계의 주인만이 외래키를 관리 (등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 amppedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인 지정
- 외래키가 있는 곳을 주인으로 정하는 것이 좋다.
양방향 매핑시 가장 많이 하는 실수는 주인이 아닌 곳에만 넣으면 db에 반영이 안된다.
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);
주인인 곳에만 넣으면 db에 반영이 되지만 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하는게 좋다.
메서드를 만들어서 넣어주는게 편하다.
// 연관관계 주인이 들어있는 엔티티(Member)에 메소드 편의상 만들어줌
public void changeTeam(Team team) {
this.team = team; // 연관관계 주인에 값을 넣어준다.
team.getMembers().add(this); // 주인이 아닌곳에도 넣어준다.
}
///////////////////////////////////
// 또는 연관관계에 주인이 아닌엔티티(Team)에 써줘도 된다. (둘중 한개를 골라쓰면됨)
public void addMember(Member member){
member.setTeam(this);
members.add(member);
}
양방향 매핑시에 무한루프를 조심하자
- lombok toString 사용하면 무한루프가 생길 수 있다.
- Controller에서 Entity를 반환하지 말고 Dto를 만들어서 반환하자
양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료
- 양방향 매핑은 반대방향으로 조회 기능이 추가된 것 뿐
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않는다.)
- 연관관계의 주인은 외래키의 위치를 기준으로 정해야한다.
Reference
www.inflearn.com/course/ORM-JPA-Basic/dashboard
'Programming > Springboot' 카테고리의 다른 글
[Springboot] JPA, N + 1 쿼리 증가 문제 (0) | 2021.05.15 |
---|---|
[Springboot] 자바 ORM 표준 JPA 프로그래밍 - 3 (0) | 2021.04.16 |
[Springboot] 자바 ORM 표준 JPA 프로그래밍 - 2 (0) | 2021.04.11 |
[Springboot] Spring 헷갈리는 내용 (21.03.25) (0) | 2021.03.25 |