맨 밑에 그려져 있는 Data Tier를 Data Access Layer, 즉 Persistence Layer라고 부른다.
여기서 Persistence라는 단어는 영속성이란 의미를 가지고 있다.
(질문) 영속성이란 단어의 의미가 무엇이고, 왜 여기에 사용될까?
영속성이란 데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성을 얘기 한다.
영속성을 갖지 않는 데이터(예 : list)는 단순히 메모리 상에서만 존재하기 때문에 프로그램을 종료하면 해당 데이터는 사라지게 된다.
Persistence Layer는 데이터에 영속성을 부여해주는 계층이라는 의미이다.
DB처리를 통한 영속성을 부여하는 경우 이를 직접 JDBC를 이용하여 구현 하기도 하고, Persistence framework를 이용하여 구현한다.
모든 Persistence Framework는 내부적으로 JDBC API를 이용한다.
참고) 데이터베이스 접근 기술 분류
JDBC
Persistence Framework
ORM
JPA
SQL Mapper
MyBatis, JdbcTemplate
(질문) ORM과 SQL Mapper의 차이점 - 지향점(목표) 관점에서
ORM은 관계형 데이터베이스의 ‘관계’를 Object에 반영하자는 것이 목적이라면, SQL Mapper는 단순히 필드를 매핑시키는 것이 목적이라는 점에서 지향점의 차이가 있다.
JPA
자바 ORM 기술에 대한 API 표준 명세로서, Java에서 제공하는 API이다.
JPA 구성 요소
javax.persistance 패키지로 정의된 API 그 자체
JPQL(Java Persistence Query Language)
객체/관계 메타데이터
JPA의 대표적인 구현체는
Hibernate
EclipseLink
DataNucleus
OpenJPA
TopLink Essentials
등이 있다.
Hibernate
JPA의 구현체 중의 하나이다.
Hibernate는 Repository계층을 관리함으로서 객체 지향적으로 데이터를 관리하게 되어, 개발자가 비지니스 로직에 집중 할 수 있는 환경을 제공한다.
1. Hibernate 아키텍처
전체 아키텍처 관점
Hibernate API 관점
Configuration(설정)
일반적으로 hybernate.properties 또는 hibernate.cfg.xml 파일로 작성된다. Java환경에서는 @Configuration으로 주석을 단 클래스가 Configuration이다. Session Factory에서 Java Application 및 Database와 함께 작업할 때 사용한다.
Session Factory
사용자 어플리케이션에서 Session Factory에 Session 객체를 요청하면 Session Factory는 Configuration 정보를 사용하여 Session Object를 인스턴스화 한다.
Most applications create a Hibernate SessionFactory singleton that’s cached for the lifecycle of the app because the object is resource-intensive to create.
Session
응용 프로그램과 데이터베이스 간의 상호작용을 나타낸다.
세션의 인스턴스는 SessionFactory Bean으로 부터 생성된다.
세션 객체는 응용 프로그램과 데이터베이스에 저go장된 데이터 간의 인터페이스를 제공한다.
수명이 짧은 객체이며 JDBC 연결을 래핑한다.
데이터의 1 차 수준 캐시 (필수)를 보유한다.
org.hibernate.Session 인터페이스는 객체를 삽입, 갱신, 삭제하는 메소드를 제공한다.
또한 Transaction, Query 및 Criteria에 대한 팩토리 메소드를 제공한다.
Query
응용프로그램이 하나 이상의 저장된 객체(Persistence Object)에 대해 데이터베이스를 쿼리할 수 있게한다.
Transaction
데이터의 영속성을 달성시키고(commit) 예기치 못한 상황이 발생할 경우 롤백할 수 있게한다.
Persistent objects
Persistent objects는 관계형 데이터베이스의 한 필드로서 유지되는 평범한 오래된 Java 객체들(POJOs)이다.
구성 파일(hibernate.cfg.xml 또는 hybernate.properties)에서 구성하거나 @Entity 어노테이션으로 선언될 수 있다.
First-level cache
데이터베이스와 상호 작용하는 동안 Hibernate Session 객체가 사용하는 기본 캐시를 나타낸다. Session cache라고도 하며, 현재 세션 내에서 객체를 현재 Session에 저장한다. Session에 저장된 객체에서 DB에 하는 모든 요청은 Session cache를 거친다. Session 객체가 활성중일 때만 Session cache를 사용할 수 있다.
1차 캐시는 영속성 컨텍스트 내부에 있고, 트랜잭션이 시작하고 종료할 때까지만 유효하다.
Second-level-cache
여러 세션에 걸쳐 객체를 저장하는 데 사용된다(application 단위의 cache). Second-level-cache는 명시적으로 사용되며, 해당 캐시에 대한 캐시 공급자를 제공해야 한다. 일반적인 Second-level-cache 공급자 중 하나로 EhCache가 있다.
어플리케이션 단위의 캐시로서, 어플리케이션을 종료할 때까지 캐시가 유지된다.
엔티티 매니저를 통해 데이터를 조회할 때 우선 2차 캐시에서 찾고 없으면 데이터베이스에서 찾게된다.
2차 캐시는 동시성을 극대화하려고 캐시한 객체를 직접 반환하지 않고 복사본을 만들어서 반환한다.
symmetric: x.equals(y) 가 참이라면 y.equals(x) 역시 참이어야 한다.
transitive: x.equals(y) 가 참이고 y.equals(z) 가 참일 때 x.equals(z) 역시 참이어야 한다.
consistent: x.equals(y) 가 참일 때 equals 메서드에 사용된 값이 변하지 않는 이상 몇 번을 호출해도 같은 결과가 나와야 한다.
x가 null이 아닐 때 x.equals(null) 은 항상 거짓이어야 한다.
(질문) 엔티티에 Equals와 HashCode 를 재정의하지 않았을 때의 문제점은?
일반적으로는 한 엔티티 매니저의 영속성 컨텍스트에서 1차 캐시를 이용해 같은 ID의 엔티티를 항상 같은 객체로 가져올 수 있다. 하지만 1차 캐시를 초기화한 후 다시 데이터베이스에서 동일한 엔티티를 읽어오는 경우 초기화 전에 얻었던 객체와 이후에 얻은 객체가 서로 다른 객체로 생성된다.
그렇다면 어떻게 재정의해야 할까?
가장 먼저 생각나는 방식은 PK 하나로 재정의 하는 것이다.
하지만 이는 NPE가 발생할 수 있는 부분을 내재하고 있다.
엔티티가 아직 영속성 컨텍스트에 관리되지 않는 trasient한 경우 (DB에 아직 저장하지 않은 경우 등) PK로만 만든 equals를 통해 비교하면 NPE가 발생한다.
### 해결 방법
NaturalId 혹은 비즈니스 키를 통해 해결하는 방법이 있다.
@NaturalId 어노테이션은 Unique와 인덱스 생성을 자동으로 도와주는 어노테이션이며, 비즈니스 키는 개발자가 스스로 Unique 하게 설정해놓은 회원의 이메일 혹은 책의 isbn 아이디 등을 말한다.
네이밍 전략
논리 이름 (테이블 명, 어트리뷰트 명) 등을 명시하지 않았을 때의 네이밍 전략에 대해 설명하고자 한다.
ImplicitNamingStrategyJpaCompliantImpl 라는 구현체가 JPA에서 사용된다.
예시
StudyGroup 이 엔티티 클래스명이고, 따로 @Table 을 사용하지 않았다면
STUDY_GROUP 이 Database의 테이블 명으로 자동으로 설정된다.
접근 전략
엔티티의 어트리뷰트에 접근하는 방법을 정의할 수 있다.
필드 접근 방식
리플렉션을 사용한다.
필드를 직접 읽고 쓸 수 있다고 한다.
@Access(AccessType.FIELD) 를 통해 사용한다.
속성 기반 접근 방식
함수 (getter, setter)를 통해 접근한다.
@Id 어노테이션을 필드에 붙이지 않고, getter 함수 위에 붙여 암시적으로 속성 기반 접근 방식이라는 것을 보여준다.
@IdpublicLonggetId(){returnthis.id;}
필드 접근 방식의 장점
불필요한 getter, setter 구현 불필요
프록시 작업시 버그 방지
Hibernate는 getter 메소드를 호출하는 시점에 값을 초기화 한다.
equals를 프록시 객체로 사용하는 코드가 실행되면, equals는 필드를 직접 접근하기 때문에 npe와 같은 오류가 발생한다.
식별자
특징
UNIQUE, NOT NULL, IMMUTABLE
@Id
PK로 사용할 필드 위에 붙임
모든 primitive 타입, Wrapper 타입, String, java.util.Date, java.sql.Date, BigDemical, BigInteger에 사용할 수 있다.
@GeneratedValue
AUTO (default)
UUID의 경우 org.hibernate.id.UUIDGenerator를 사용한다.
숫자 (Integer, Long) 타입의 경우 org.hibernate.id.enhanced.SequenceStyleGenerator 를 사용한다.
@AllArgsConstructor@NoArgsConstructorpublicclassUserIdimplementsSerializable{privateStringid;privateLocalDateTimeregDate;// equals & hashCode 부분 생략}
id로 사용할 클래스 요구사항
public 클래스일 것
기본 생성자 필수
엔티티 클래스에서 작성한 필드 명과 동일하게 작성할 것 (컬럼명이 아님에 주의)
Serializable을 구현해야 함
equals와 hashCode를 구현해야 함
Associations (관계 설정)
ManyToOne
FetchType.EAGER 가 디폴트
OneToMany
FetchType.LAZY가 디폴트
OneToOne
FetchType.EAGER 가 디폴트
Any
상속관계의 다양한 엔티티를 연관관계로 가져야 할 때 사용
@Entity@Table(name="property_holder")publicclassPropertyHolder{@IdprivateLongid;@Any@AnyDiscriminator(DiscriminatorType.STRING)@AnyDiscriminatorValue(discriminator="S",entity=StringProperty.class)@AnyDiscriminatorValue(discriminator="I",entity=IntegerProperty.class)@AnyKeyJavaClass(Long.class)@Column(name="property_type")@JoinColumn(name="property_id")privatePropertyproperty;//Getters and setters are omitted for brevity}
@Entity@Table(name="property_repository")publicclassPropertyRepository{@IdprivateLongid;@ManyToAny@AnyDiscriminator(DiscriminatorType.STRING)@Column(name="property_type")@AnyKeyJavaClass(Long.class)@AnyDiscriminatorValue(discriminator="S",entity=StringProperty.class)@AnyDiscriminatorValue(discriminator="I",entity=IntegerProperty.class)@Cascade(ALL)@JoinTable(name="repository_properties",joinColumns=@JoinColumn(name="repository_id"),inverseJoinColumns=@JoinColumn(name="property_id"))privateList<Property<?>>properties=newArrayList<>();//Getters and setters are omitted for brevity}
ManyToMany
다대다 관계에서 쓸 수 있고, 중간 엔티티를 자동으로 만들어주지만, 사용하지 말자.
(질문) ManyToMany를 사용하면 안되는 이유
중간 테이블에 칼럼 추가가 불가능하고, 세밀한 쿼리를 실행하기 어렵기 때문에 사용하지 않는 편이 좋다.
컬렉션
@ElementCollection
값 타입을 리스트와 같은 컬렉션으로 가지고 있는 필드를 엔티티에 넣을 때 사용
값 타입은 Embeddable 타입도 포함
Natural Id
@NaturalId
값 타입에 사용한다고 생각할 수 있는데, 엔티티 필드 위에 사용할 수도 있고, 여러 필드에 한 번에 사용할 수도 있다.
Partitioning
(질문) 파티셔닝이 무엇인지와 얻는 이점은?
논리적인 데이터 element들을 다수의 entity로 쪼개는 행위를 뜻하는 일반적인 용어
즉 큰 table이나 index를, 관리하기 쉬운 partition이라는 작은 단위로 물리적으로 분할하는 것을 의미한다.
물리적인 데이터 분할이 있더라도, DB에 접근하는 application의 입장에서는 이를 인식하지 못한다.
DBMS는 분할에 대해 각종 기준(분할 기법)을 제공하고 있다. 분할은 ‘분할 키(partitioning key)’를 사용한다.
@DiscriminatorFormula("case when debitKey is not null "+"then 'Debit' "+"else ("+" case when creditKey is not null "+" then 'Credit' "+" else 'Unknown' "+" end) "+"end ")