JPA는 특정 데이터베이스에 종속되지 않으며 hibernate.dialect 속성에 DB 지정해서 모든 DB방언을 사용할 수 있다.
엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유하고, 팩토리에서 만들어진 @PersistenceContext
엔티티 매니저는 쓰레드간에 공유X (사용하고 버려야 한다)
JPA의 모든 데이터 변경은 트랜잭션(@Transactional(readOnly=false)) 안에서 실행된다.
JPA를 사용하면 엔티티 객체를 중심으로 개발하며 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색한다. 따라서 데이터를 찾기 위해 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공한다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근 : EntityManager.persist()
영속(em.persist(member)), 비영속(new Member), 준영속(분리, detach), 삭제(em.remove)
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다. SQL저장소에 저장해 놓음
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit();
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) -> update 쿼리 필요없음
직접호출
, 트랙잭션 커밋시점
, JPQL실행 시점
EnumType.STRING
) : 자바 enum 타입을 매핑할 때 사용IDENTITY: 데이터베이스에 위임, MYSQL , IDENTITY 전략은 commit()시점이 아닌 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE , @SequenceGenerator
필요
성능 최적화에 allocationSize
사용(시퀀스 한 번 호출에 증가하는 수)
TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용 ,@TableGenerator
필요
AUTO: 방언에 따라 자동 지정, 기본값
• 데이터베이스 스키마 자동 생성 : hibernate.hbm2ddl.auto=
create : 기존테이블 삭제 후 다시 생성 (DROP + CREATE)
create-drop : create와 같으나 종료시점에 테이블 DROP
update : 변경분만 반영(운영DB에는 사용하면 안됨)
validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
none : 사용하지 않음
운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
• 개발 초기 단계는 create 또는 update
• 테스트 서버는 update 또는 validate
• 스테이징과 운영 서버는 validate 또는 none
N이 주인
따라서 N-1이 진짜 매핑으로 외래키를 가지고 있고, 데이터를 등록,수정할 수 있음(ex Member.team)
, 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자. 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)@JoinColumn("1의 Id명")
: 주인 Entity에 선언(N쪽), 외래키를 매핑할 때 사용하는 어노테이션으로 참조하는 테이블의 기본 키 컬럼명을 쓴다.(MappedBy= "필드명")
: 1의 Entity에 선언, 반대쪽에 자신이 매핑되어 있는 필드명@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
@Entity
public class Team {
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
…
양방향일 때는 연관관계 메서드를 만들어서 순수한 객체상태일 때도 작동하도록 해야한다.
양쪽 모두의 관계를 맺어주는 것을 하나의 코드처럼 사용하는 것이 안전하다.
멤버에 팀을 세팅할 때, 멤버가 다른 팀에 속해있을 수 있으므로 확인 후, 그 팀과의 연관관계를 끊고 팀을 세팅해야 한다. 위보다 아래의 코드가 더 적절하다.
N 대 1의 관계에서 N쪽 엔티티에 메서드를 작성한다.
• 상속관계 매핑
DB에는 객체의 상속관계와 유사한 슈퍼타입,서브타입 관계라는 모델링 기법이 있음
@Inheritance(strategy=InheritanceType.XXX)
@DiscriminatorColumn(name=“DTYPE”) : 부모 클래스에 선언
@DiscriminatorValue(“XXX”) : 하위 클래스에 선언
①조인 전략(JOINED) : 테이블 정규화, 쿼리가 복잡하고 조인으로 성능저하
②단일 테이블 전략(SINGLE_TABLE) : 모든 엔티티의 필드를 부모 클래스에 넣음
③클래스 모두 테이블로 구현(TABLE_PER_CLASS)
• @MappedSuperclass : 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 하는 클래스에 사용하는 어노테이션. 부모 클래스로서 역할을 하며 자식 클래스에 매핑정보만 제공하고 엔티티로 만들어지지 않음, 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
• 프록시
실제 객체를 상속 받아서 만들어지며, 실제 객체의 참조(target)를 보관한다. 프록시 객체를 처음 사용할 때, 초기화가 되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해진다. 그래서 프록시 객체는 실제 사용되기 전까지 데이터를 로딩 하지 않고 실제 사용시점에 쿼리문이 실행된다. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출한다. 지연로딩을 가능하게 함
• 즉시로딩, 지연로딩(fetch= FetchType)
@ManyToOne, @OneToOne은 기본설정은 즉시 로딩
실무에서는 다 지연로딩으로 설정하고 JPQL fetch 조인이나, 엔티티 그래프 기능을 사용
지연로딩으로 설정하면 N 대 1에서 1의 엔티티는, 실제 엔티티 대신에 프록시 객체를 넣어둔다.
특정 엔티티의 상태를 바꿀 때, 연관된 엔티티의 상태도 같이 바꾸고 싶을 때 자식쪽에 선언
(All, persist, Remove ..)
=>확실하게 자식을 관리하는 엔티티가 부모 하나일때만 사용해야 하며, 라이프 사이클이 같을 때 써야한다. 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
• 고아 객체 제거(orphanRemoval = true) : 부모 엔티티와 연관관계가 끊어진(참조가 제거된 엔티티) 자식 엔티티를 자동으로 삭제, 자식이 참조하는 부모가 하나뿐일 때만 사용
(=> @OneToOne, @OneToMany만 가능)
parent1.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거 => 고아가 됨
CascadeType.ALL + orphanRemovel=true
: 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
기본 값타입 : 기본타입(int, double), 래퍼 클래스(Integer, Long), String,
생명주기를 엔티티의 의존, 기본 타입은 항상 값을 복사하기 때문에 공유 불가(ex int a = b)
임베디드 타입 : 새로운 값 타입을 직접 정의할 수 있음 , 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함 (ex - Address(city, street, zipcode)) , 기본 생성자 필수
임베디드 타입은 기본타입이 아니라 객체타입이므로 참조를 공유함
=> 따라서 값 타입은 불변객체
로 만들어야함 (@Setter를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자)
동일성
(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
동등성
(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용
@Configuration
@EnableJpaAuditing
public class AuditingField {
@Bean
public AuditorAware<String> auditorAware(){
return new AuditorAwareImpl();
}
}
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
String userId = "";
if (authentication != null){
userId = authentication.getName();
}
return Optional.of(userId);
}
}