자바는 객체 지향적인 언어
데이터베이스는 관계형 DB
이 둘의 패러다임이 다르다
객체와 관계형 데이터베이스의 차이
1. 상속
2. 연관관계f
3. 데이터 타입
4. 데이터 식별 방법
객체는 상속관계가 있지만, 관계형 데이터베이스에서는 객체지향에서 말하는 상속관계는 없다.(비슷하게 슈퍼타입 서브타입으로 만들 수는 있음)
객체는 참조를 사용, 테이블은 외래 키를 사용해 JOIN을 해야됨.
보통 객체를 테이블에 맞추어 모델링. (테이블에서 사용하는 데이터나 객체가 가지고 있는 데이터가 다르기 때문에 DB를 사용한다면 테이블에 맞추어 설계. 그렇지 않으면 SQL문법 사용하기 귀찮아짐).
이는 객체다운 모델링은 아니다. 객체는 참조로 연관관계를 모델링 한다. 객체 안에서는 복잡한 변환 가정이 필요없다.
또한 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다. 하지만 SQL은 처음 실행하는 SQL에 따라 탐색 범위가 결정 된다.
객체 답게 모델링 할 수록 관계형 데이터베이스 매핑 작업만 늘어난다.
이런 문제들을 해결한 것이 ORM기술 즉 JPA이다.
ORM?
생산성의 증가 : 따로 SQL을 짤 필요가 거의 없음.
유지보수 : SQL의 경우 전부 다 바꿔줘야되지만, JPA의 경우 필드 추가만 하면 됨.
JPA는 객체지향 언어와 관계형 DB의 패러다임 불일치를 해결
상속, 연관관계, 데이터 타입, 데이터 식별 방법의 차이점을 전부 해결해준다.
1차 캐시와 동일성 보장
트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
INSERT
UPDATE
지연 로딩(Lazy Loading)과 즉시(Eagle) 로딩
중요한 점!!
JPA를 한다고 해서 절대 RDB를 몰라도 되는 것이 아님. 오히려 SQL을 잘 알아여 JPA에서 자동으로 만들어주는 쿼리를 생각할 수 있고, 매칭이 된다.
JPA와 RDB를 잘 공부해라.
JPA는 특정 데이터베이스에 종속 x
각각의 데이터베이스가 제공하는 SQL문법과 함수는 조금씩 다르다.
ex) MySQL의 VARCHAR, 오라클의 VARCHAR2 , 문자열 자르기 표준 SUBSTRING(), 오라클은 SUBSTR(), 페이징 MySQL은 LIMIT , Oracle은 ROWNUM
옵션으로 넣을 수 있는 설정.
hibernate.show_sql
hibernate.format_sql
hibernate.use_sql_comments
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnitName");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
//... logic
tx.commit();
} catch(Exception e) {
tx.rollback
} finally{
em.close();
emf.close();
}
위의 코드는 순수하게 JPA를 사용하고 있는 모습이고, 실제로 스프링과 함께 사용하게 된다면 위의 코드가 대부분 사라지게 될 것이다.
주의
JPA는 JPQL로 쿼리를 만들수 있다. SQL문과 거의 비슷하다고 볼 수 있어서 SQL을 잘 알면 금세 익힐 수 있다.
JPQL은 뒤에서 다루겠다.
JPA를 이해하는데 가장 중요한 용어.
엔티티를 영구 저장하는 환경 -> 엔티티를 영속성 컨텍스트에 저장한다는 개념.
J2SE 환경
EntityManager와 Persistence Context 1:1 매칭
스프링 프레임워크의 환경
EntityManager를 통해 Persistence Context와 N 대 1 매칭
엔티티의 생명주기
비영속(new/transient) - PersistenceContext와 전혀 관계가 없는 새로운 ㅏㅇ태
영속(Managed) - PersistenceContext에 관리되는 상태
준영속(detached) - PersistenceContext에 저장되었다가 분리된 상태
삭제 - 삭제된 상태
Member member = new Member(); // 비영속 상태. JPA와 아무 관련 없음
em.persist(member); // 영속 상태 PersistenceContext에 관리되고 있는 상태, 아직 DB에 저장되지는 않음.
em.detach(member); //PersistenceContext에서 분리, 준영속 상태
em.remove(member); // 객체를 삭제한 상태
persist를 통해 엔티티를 조회하면 영속 컨텍스트에서 1차 캐시에서 조회를 한다.
만약 1차 캐시에 member가 있다면 반환, 없다면 DB에서 조회후 1차 캐시에 저장한뒤 반환한다.
영속 엔티티의 동일성 보장
같은 트랜잭션에서는 1차 캐시로 반복 가능한 읽기(REPEATABLE READ)등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
쓰기 지연
JPA는 INSERT 쿼리들을 모아서 commit 시점에 날린다.
Dirty Checking(변경 감지)
JPA는 찾아온 데이터를 변경하면 commit 시점에 dirty checking을 통해서 업데이트 쿼리를 날린다.
flush()를 날리며 엔티티와 스냅샷(최초로 영속 컨텍스트에 들어온 상태)을 비교해서 달라진 점이 있다면 update를 날려준다.
그래도 persist로 호출해야 하지 않나? 결론부터 말하자면 더티 체킹을 활용하라.
영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것
2가지 모드
FlushModeType.AUTO(기본값) 사실 바꿀일 없다고 보면 됨.
FlushModeType.COMMIT
변경 감지
수정된 엔티티 쓰기 지연 SQL 저장소에 등록,
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정 삭제 쿼리)
em.flush() - 직접 호출
트랜잭션 커밋 - 플러시 자동 호출
JPQL 쿼리 실행 - 플러시 자동 호출
주의
플러시가 호출된다고 해서 영속성 컨텍스트가 비워지는 것은 아니다. persistence context의 변경 내용을 데이터베이스에 동기화 할 뿐이다.
이런 매커니즘이 가능한 이유는 바로 커밋 직전에만 동기화 하면 되기 때문이다.
영속 상태의 엔티티각 영속성 컨텍스트에서 분리(detached)
영속성 컨텍스트가 제공하는 기능을 사용하지 못함.
준영속 상태를 만드는 방법 몇가지
em.detach(entity); //특정 엔티티만 준영속 상태로 전환
em.clear(); //영속성 컨텍스트를 초기화
em.close(); //영속성 컨텍스트를 종료
@Entity
가 붙은 클래스는 JPA가 관리하는 엔티티
@Table
은 엔티티와 매핑할 테이블 지정
필드와 컬럼 매핑
@Column
은 필드와 컬럼을 매핑
@Enumerated
@Lob
데이터베이스 BLOB, CLOB 타입과 매핑
이제는 거의 사용하지 않는 것
@Temporal
java.util.Date, java.util.Calendar 매핑, 자바8 시간, 날짜 타입 등장 이후로 안씀-@transient
필드 매핑 x , 데이터베이스에 저장,조회 x
hibernate.hbm2ddl.auto=옵션 //
create - 기존 테이블 삭제 후 다시 생성(Drop + Create)
create-drop - create와 같으나 종료 시점에 테이블 drop
update - 변경분만 반영
validate - 엔티티와 테이블이 정상 매핑되었는지만 확인
none - 사용하지 않음 , 관례상 적는거임. 사실 주석을 하거나, 아무거나 없는걸 넣는것과 같음.
//운영 장비에서는 절대 create, create-drop, update 사용하면 안된다.
// 개발 초기는 create 또는 update, 테스트 서버는 update , validate
// 스테이징과 운영서버는 validate 또는 none
// 사실은 개발 단계에서나 조금 사용하지 다른 상황에서는 거의 사용하지 말자
DDL 생성 기능
@Id
@GeneratedValue
크게 두가지가 있다.
직접할당 -> @Id만 사용
자동생성 -> @GeneratedValue 사용
AUTO를 빼면 기본으로 3가지가 있음.
SEQUENCE를 직접 안만들면 hibernate_sequence라는 시퀀스를 적용해줌. 직접 지정할 수도 있음.
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
권장하는 식별자 전략
IDENTITY전략의 특징
SEQUENCE전략의 특징
위의 테이블 매핑을 많이 봤을 것이다. 이것은 일반적인 관계형 DB에서의 관계를 나타낼때 만들 수 있는 테이블일 것이다. 하지만 JPA에서는 이런 식으로 매핑하지 않는다.
• 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
• 테이블의 외래키를 객체에 그대로 가져옴
• 객체 그래프 탐색이 불가능
• 참조가 없으므로 UML도 잘못됨
객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.
객체의 참조와 테이블의 외래키를 매핑
예시
멤버는 팀의 pk를 외래키로 가지고 있다. Member (N) : TEAM(1) 의 관계
이 상황에서 객체로 매핑을 시켜준다면
@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;
...
}
객체의 연관관계 매핑은 바로 이런 식으로 객체를 필드값으로 받는 것.
@ManyToOne
- 멤버는 FK로 TEAM을 받는 것이기때문에, 1대 N의 관계에서 N이라는 것.
@JoinColumn(name = "TEAM_ID")
로 무엇과 조인되는 컬럼인지 알려주는 것
핵심은 외래키와 객체의 매핑. 이것을 잘 기억하자!
테이블의 연관관계는 외래키 하나로 양방향으로 조회할 수 있다. 하지만 객체에서는 맴버에서 팀으로 갈수는 있지만 팀에서 맴버로 갈 수는 없다.
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
// 핵심
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
…
}
팀에서 맴버로 가는것은 @OneToMany
이다. 그리고 맵되는 것을 이야기 해줘야 된다. 이때는 맴버의 team 필드에 매핑 되는 것이니 mappedBy = "team"
이라 쓰면 된다.
객체 연관관계 = 2개
o 회원 -> 팀 연관관계 1개(단방향)
o 팀 -> 회원 연관관계 1개(단방향)
테이블 연관관계 = 1개(FK하나로 끝이남)
o 회원 <-> 팀의 연관관계 1개(양방향)
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단 뱡향 관계 2개다.
객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어 야 한다
연관 관계의 주인
객체에 2개의 단방향 연관관계를 만들어 양방향 관계를 만들어냈다. 그렇다면 이때, 둘 중 하나로 외래 키를 관리해야 한다. 과연 누구로 관리해야 되는가?
답은 바로 외래 키이다. 외래 키가 있는 있는 곳을 주인으로 정하면 된다.
양방향 매핑 규칙
• 객체의 두 관계중 하나를 연관관계의 주인으로 지정
• 연관관계의 주인만이 외래 키를 관리(등록, 수정)
• 주인이 아닌쪽은 읽기만 가능
• 주인은 mappedBy 속성 사용X
• 주인이 아니면 mappedBy 속성으로 주인 지정
양방향 매핑시 가장 많이 하는 실수
완전히 flush, clear하면 문제가 없다. 하지만 영속성 컨텍스트에서 flush 되기전에 찾게되면 SQL문이 날라가기전의 상태가 찾아져서 온다.
테스트 케이스에서는 JPA없이 순수한 자바코드로도 작성을 하는데, 이때 주인쪽에만 값을 넣으면 반대쪽(주인이 아닌 쪽)에서 값이 이상하게 나온다.
즉 양방향 연관관계에서는 항상 양쪽에 값을 설정하자
그런데, 두번이나 넣기 귀찮거나, 깜빡할 수 있다.
이럴때 연관관계 편의 메서드를 만들자.
Member(주인쪽)에서 Team과 관련된 값을 변경할때..
public void changeTeam(Team tema) {
this.team=team;
team.getMembers().add(this);
}
주의점
양방향 매핑시에 무한 루프를 조심하자.
다음으로...