JDBC
JDBC를 이용한 SQL 전달
- 데이터베이스에 데이터를 관리하려면 SQL를 사용해야 한다.
- 자바로 작성한 애플리케이션은 JDBC API를 사용하여 SQL를 데이터베이스에 전달한다.
- 데이터베이스는 자바와 다르게 데이터 중심의 구조를 가지므로 직접 저장하거나 조회를 할 수 없다.
- CRUD하려면 너무 많은 SQL과 JDBC API를 작성해야 한다.
문제점
- DAO를 열어서 어떤 SQL이 실행되고 있는지 또 어떤 객체들이 함께 조회되는지 일일이 확인해야한다.
- 강한 의존관계 때문에 객체에 필드를 하나 추가할 때도 DAO의 CRUD 코드와 SQL 대부분을 변경해야한다.
- 계층 분할이 어렵다.
- 엔티티를 신뢰할 수 없다.
- SQL에 의존적 개발을 피하기 어렵다.
패러다임 불일치
- 객체 인스턴스를 생성한 후에 객체를 메모리가 아닌 어딘가에 영구 보관(데이터베이스)등 해야한다.
- 객체가 단순하면 객체의 모든 속성 값을 꺼내서 파일이나 데이터베이스에 저장하면 되지만, 부모 객체를 상속받았거나, 다른 객체를 참조하고 있다면 객체의 상태를 저장하기는 쉽지 않다.
- 관계형 데이터베이스는 데이터 중심으로 구조화되어 있고 객체와 지향하는 목적이 달라 패러다임 불일치 문제라고 한다.
연관 관계
- 객체는 참조를 통해 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
- 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.
객체를 테이블에 모델링
- 객체를 테이블에 맞추어 모델링하면 테이블에 저장하거나 조화할때 편리함
- 관계형 데이터 베이스는 조인이라는 기능으로 외래 키 값을 그대로 보관
- 객체는 연관된 객체의 참조를 보관해야 참조를 통해 연관된 객체를 찾음
객체 모델은 외래 키가 필요없고 참조만 있으면 되고 테이블은 참조가 필요 없고 외래 키만 있으면 된다.
객체 그래프 탐색
- 참조를 사용해서 연관된 객체를 찾는 것을 객체 그래프 탐색이라고 한다.
- SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할지 정한다.
- JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 사용한다.
- 실제 객체를 사용하는 시점까지 데이터 베이스를 미룬다고 하여 지연 로딩이라고 한다.
member.getOrder().getOrderITem()
JPA
- 자바 진영의 ORM 기술 표준
- 객체를 마치 컬렉션에 저장하듯이 ORM 프레임워크 객체와 테이블을 매핑하여 패러다임 불일치 해결
- 생산성, 유지보수, 패러다임 불일치 해결, 성능, 데이터 접근 추상화등의 장점이 있다.
엔티니 매니저 설정
엔티티 매니저 팩토리 생성
- persistence.xml의 설정 정보를 사용해 엔티티 매니저 팩토리를 생성
- JPA 구현체에 따라서 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다.
- 앤티티 매니저 팩토리는 애플리케이션 전체에서 한 번만 생성하고 공유해서 사용해야 한다.
EntityManagerFactory emf = Persistence.createEntityManagerFacotry("entity")
엔티티 매니저 생성
- 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간 공유하거나 재사용 해서는 안된다.
EntityManager em = emf.createEntityManager();
종료
em.close();
emf.close();
트랜잭션 관리
- JPA를 사용하면 항승 트랜잭션 안에서 데이터를 변경해야 한다.
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
logic(em);
tx.commit();
} catch(Exception e) {
tx.rollback();
}
엔티티 매니저
- 엔티티 매니저를 생성하는 비용은 거의 들지 않음
- 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유가능
- 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유 불가
영속성 컨텍스트
영속성 : 영원히 계속되는 성질이나 능력
엔티티를 영구히 저장하는 환경
엔티티 생명 주기
- 비영속 : 영속성 컨텍스트와 관계 없는 상태
- 영속 : 영속성 컨텍스트에 저장된 상태(영속성 컨텍스트에 의해 관리된다는 뜻)
- 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 : 삭제된 상태
엔티티 컨텍스트 특징
- 영속성 컨텍스트와 식별자 값(영속 상태는 식별자 값이 반드시 있어야 한다.)
- 영속성 컨텍스트와 데이터베이스 저장
- 플러시(flush) : JPA는 트랜잭션을 커밋하는 순간 데이터 베이스에 반영
엔티티 컨텍스트 장점
1차 캐시
- 1차 캐시에서 식별자 값으로 엔티티를 찾는다면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 조회
- 1차 캐시에 없으면 데이터베이스를 조회해서 엔티티를 생성 후 1차 캐시에 저장한 뒤 영속 상태의 엔티티 반환
엔티티 등록
- 엔티티 매니저는 트랙잭션을 커밋하기 직전까지 데이터베이스에 저장하지 않고 내부 쿼리 저장소에 INSERT SQL에 모아둔다.
- 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스 보내는데 이것을 쓰기 지연이라고 한다.
엔티티 수정
- SQL을 사용하면 수정 쿼리를 직접 작성하는데 규모가 커질수록 수정 쿼리도 늘어난다.
- 즉 지브니스 로직을 분석하기 위해 비용이 들고 직간접적으로 SQL에 의존하게 된다.
- 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 변경 감지(dirty checking)이라고 한다.
em.update(entity)
와 같은 코드가 필요가 없다.
- 영속 상태의 엔티티에만 적용하며 모든 필드를 업데이트 한다.
- 모든 필드를 업데이트 하면 데이터 전송량이 증가하는 단점이 있으나 수정 쿼리가 항상 같고 데이터 베이스에서 이전에 한 번 파싱된 쿼리를 재사용 할 수 있다.
JPA는 엔티티를 영속성 컨텍스트에 보관하는데 최초 상태를 복사해서 저장하는 것을 스냅샷이라고 하며 플러시 기준으로 엔티티를 비교해서 변경된 엔티티를 찾는다.
변경 감지 순서
- 트랜잭션을 커밋하면 엔티티 매니저 내부에서 flush를 호출한다.
- 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
- 만약 변경된 엔티티가 있으면 수정 쿼리를 새성하여 쓰기 지연 저장소에 저장한다.
- 쓰기 지연 저장소를 데이터베이스에 보낸다.
- 데이터베이스를 트랜잭션 커밋한다.
엔티티 삭제
- 삭제 대상 엔티티를 넘겨주면 엔티티 삭제한다.
- 삭제 쿼리를 쓰기 지연 저장소에 등록한다.
em.remove(entity)
를 호출하는 순간 영속성 컨텍스트에 제거되는데 재사용하지 말아야한다.
플러시(flush)
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
변경 감지가 모든 엔티티를 스냅샷과 비교하여 찾은 다음 쓰기 지연 SQL 저장소에 등록 이후 쿼리를 데이터 베이스에 전송한다.
플러시하는 방법
- em.flush()를 직접 호출(거의 사용하지 않는다.)
- 트랜잭션 커밋 시 자동 호출
- JPQL 쿼리 실행 시 자동 호출
- find() 매소드는 호출할 때는 플러시가 실행되지 않는다.
플러시 모드 옵션
- FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시(기본값)
- FlushModeType.COMMIT : 커밋할 때만 플러시
플러시는 영속성 컨텍스트에 보관된 엔티티를 지우는것이 아닌 변경 내용을 데이터베이스에 동기화 하는것
연관관계
객체는 참조를 사용해서 관계를 맺고 테이블은 외래 키를 사용해서 관계를 맺는다.
- 방향 : 단방향(둘 중 한 쪽만 참조), 양방향(양쪽 모두 서로 참조)
- 다중성 : 일대일(1:1), 일대다(1:N). 다대일(N:1), 다대다(N:M)
- 회원 객체와 팀 객체는 단방향 관계(필드)
- 회원 테이블과 회원 팀은 양방향 관계(외래키를 통해 양쪽에서 조인 가능)
단방향 연관관계
@JoinCoulmn
외래 키를 매핑할때 사용하는 어노테이션
- 생략하면 외래 키를 찾을 때 기본 전략 사용
- 필드명+_+참조하는 테이블 칼럼명
@ManyToOne
다대일 관계에서 사용하는 어노테이션
- optional : false 설정시 연관된 엔티티 항상 존재(true)
- fetch : 글로벌 패치 전략
@ManyToOne
=FetchType.EAGER
@OneToMany
=FetchType.LAZY
- cascade : 영속성 전이
조회
Member member = em.find(Member.class,"member");
Team team = member.getTeam();
team.getName();
수정
- em.update() 메소드 없다.
- 트랜잭션 커밋할때 더티 체킹한다.
삭제
- 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야한다.
- 외래 키 제약조건으로 인해, 데이터베이스에서 오류가 발생한다.
양방향 연관관계
- 데이터베이스 테이블은 외래 키 하나로 양방향으로 조회할 수 있다.
- mappedBy 속성은 양방향 매핑일 때 사용하는데 반대쪽 매핑이 필드 이름을 값으로 준다.
연관관계의 주인
두 객체의 연관관계 중 하나를 정해서 테이블의 외래키를 관리하는것
- 객체에는 양방향 연관관계라는 것이 없다.
- 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 잘 묶어서 양방향인 것처럼 보이는것
- 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나이다.
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록,수정,삭제)할 수 있다. 반면에 주인이 아닌 쪽은 읽기만 할 수 있다.
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정
- 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
- 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리 할 수 있다. 주인이 아닌 반대편은 읽기만 가능하고 외래키를 변경하지는 못한다.
- 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.
member.setTeam(team);
team.getMEmbers().add(member);
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
- 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
- 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
- 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.
연관관계 주인을 정하는 기준
연관관계의 주인은 외래 키의 위치와 관련해서 정해야지 비즈니스 중요도로 접근하면 안 된다.
연관관계 매핑
다대일
다대일 단방향[N:1]
Member.team으로 팀 엔티티를 참조할 수 있지만 반대로 팀에는 회원을 참조하는 필드가 없으므로 다대일 단방향 연관관계
다대일 양방향[N:1, 1:N]
- 양방향은 외래 키가 있는 쪽이 연관관계 주인
- 양방향 연관관계는 항상 서로를 참조
- 편의 메소드를 사용하는것이 좋으나 무한루프에 빠지지 않도록 주의
일대다
일대다 단방향[1:N]
- 보통 자신이 매핑한 테이블의 외래 키를 관리하지만, 반대쪽 테이블에 있는 외래 키를 관리
- 일대다 관계에서 외래 키는 항상 다쪽 테이블에 존재
@JoinColumn
을 명시하여 조인 테이블을 사용하는 것을 방지
- 외래 키가 다른 테이블에 있어 연관관계 처리를 위한 UPDATE SQL을 추가로 실행
- 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장
일대다 양방향[1:N,N:1]
- 공식적으로 존재하지 않음
- 관계형 데이터베이스 특성상 다대일 관계는 항상 다 쪽에 외래 키가 있다.
- 다대일 양방향 매핑을 권장
일대일[1:1]
- 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다.
- 주 테이블에 외래 키가 있는 것을 선호
- 외래 키에 데이터베이스 유니크 제약조건 추가
다대다[N:M]
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현 불가
- 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블 사용
@ManyToMany
사용, @JoinTable
로 연결 테이블 지정(편리하나 실무에서는 사용x)
- 연결 테이블용 엔티티 추가
- 비식별 관계(외래 키로만 사용하는 새로운 식별자) 관계를 권장
@Entity
public class Member {
@Id @GenerateValue
@Column(name = "MEMBER_ID")
private String id;
private String username;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<Order>();
}
@Entity
public class Product {
@Id @GenerateValue
@Column(name = "PRODUCT_ID")
private String id;
private String name;
}
@Entity
public class Order {
@Id @GenerateValue
@Column(name = "ORDER_ID")
private Long Id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product
private int orderAmount;
private DATE orderDate;
}
[참고자료][자바 ORM 표준 JPA 프로그래밍]