자바 ORM 표준 JPA 프로그래밍 스터디
자바 ORM 표준 JPA 프로그래밍 스터디 리뷰
jpa예제코드 : https://github.com/holyeye/jpabook
Chapter 1. JPA
- 객체모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 서로 다르다. 문제는 이 패러다임의 차이를 극보복하려고 개발자가 너무 많은 시간과 코드를 소비한다는 점이다.
- 더 어려운 문제는 객체지향 애플리케이션 답게 정교한 객체 모델링은 힘을 잃고 점점
- 진정한의미의 계층 분할이 어려움.
- 엔티티를 신뢰할 수 없다.
- SQL에 의존적인 개발을 피하기 어렵다.
JPA 기능
저장기능
jpa.persist(member); //저장
- 객체를 데이터베이스에 저장한다.
조회기능
String memberId = “helloId”;
Member member = jpa.find(Member.class, memberId); //조회
- 객체하나를 조회한다.
수정기능
Member member = jpa.fing(Member.class, memberId);
member.setName(“이름변경”); //수정
연관된 객체 조회
Member member = jpa.fing(Member.class, memberId);
Team team = member.getTeam(); //연관된 객체 조회
Chapter 2. JPA 시작
지연로딩
데이터 베이스의 조회를 늦추는 기능
//처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDate();//Order사용하는 시점에 SELECT ORDER SQL
Member 사용할 때마다 Order 함께 사용시 한 테이블 조회하는 것 보다 Member는
비교 방법
비교와 동등성비교 두가지 비교방법이 있음.
- 동일성 비교 == 비교이다.객체의 인스턴스 주소값을 비교한다.
- 동등성 비교는 equals() 메소드 사용한 객체 내부의 값을 비교한다.
//동일성 테스트 코드
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2;// 결과는 false임. 왜냐면
//둘은 같은 DB로우에서 조회했지만 객체 측면에서 둘은 다른 인스턴스이기 때문에 비교시 서로다른값으로 취급된다.
Chapter 3. 영속성 관리
엔티티 생명주기
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed) : 영속성 컨텍스트에 저장된 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed):삭제된 상태
엔티티매니저 메서드 정리
EntityManagerFactory
- 엔티티매니저 팩토리는 비용이크다.
- 비용이 크기 때문에 DB당 1개 씩 팩토리 선언하여 사용하는것이 좋다.
- 여러 스레드가 동시에 접근해도 문제되지 않는다. 스레드간 공유해도 된다.
EntityManager - 엔티티 매니저 생성시 영속성 컨텍스트가 하나 만들어진다.
- 엔티티매니저 통해 영속성 컨텍스트에 접근하고 관리할 수 있다.
- 동시성 문제 발생하기 때문에 문제된다. 동시접근 금물 스레드간 공유금지
EntityTransaction - 엔티티매니저
- begin, commit, rollback
EntityTransaction tx = em.getTransaction(); // 트랜잭션API
try{
tx.begin(); //트랜잭션 시작
test.get(); //비지니스로직~
tx.commit(); // 최종 커밋
}catch(Exception e){
tx.rollback(); //예외 발생시 롤백
}
중요한 정리내용
- 엔티티 매니저는 엔티티 매니저 팩토리에서 생성한다. 자바를 직접 다루는 J2SE환경에서는 엔티티 매니저를 만들면 그 내부에 영속성 컨텍스트도 함께 만들어 진다. 이 영속성 컨텍스트는 엔티티 매니저를 통해서 접근할 수 있다.
- 영속성 컨텍스트는 애플리케이션과 DB사이에 객체를 보관하는 가상의 DB 같은 역할을 한다. 영속성 컨텍스트 덕분에 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능을 사용할 수 있다.
- 영속성 컨텍스트에 저장한 엔티티는 플러시 시점에 DB에 반영되는데 일반적으로 트랜잭션을 커밋할 때 영속성 컨텍스트가 플러시 된다.
- 영속성 컨텍스트가 관리하는 엔티티를 더 이상 관리하지 못하면 그 엔티티는 준영속 상태의 엔티티라 한다. 준영속 상태의 엔티티는 더는 영속성 컨텍스트가 제공하는 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 같은 기능을 사용할 수 없다.
Chapter 4. 엔티티 매핑
jpa에서 제공하는 매핑어노테이션
- 객체와 테이블 매핑 : @Entity, @Table
- 기본 키 매핑 : @Id
- 필드와 컬럼매핑: @Column
- 연관관계 매핑 @ManyToOne, @JoinColumn
@GeneratedValue
세가지전략이있다.
- IDENTITY - 기본키 생성을 테이터베이스에 위임한다. 예를들어 Mysql 경우 AUTO_INCREMENT를 사용하여 기본키를 생성한다.
- SEQUENCE - 데이터베이스에 특별한 오브젝트 시퀀스를 사용해 기본키 생성
- TABLE - 데이터베이스에 키 생성 전용 테이블을 하나 만들고 이를 사용하여 기본키를 생성한다.
- AUTO - JPA구현체가 자동으로 생성전략을 결정한다.
필드와 컬럼 매핑 분류
- @Column : 컬럼을 맵핑한다.
- @Enumerated : 자바의 enum 타입을 맵핑한다.
- @Temporal : 날짜 타입을 맵핑한다.
- @Lob : BLOB, CLOB 타입을 맵핑한다.
- @Transient : 특정 필드를 데이터베이스에 매핑하지 않는다.(저장도, 조회도 않는다. 주로 임시로 어떤값을 보관할때 사용)
- @Access : JPA가 엔티티에 접근하는 방식을 지정한다.
- @Column 속성 정리
- name : 필드와 매핑할 테이블의 컬럼이름 객체의 필드 이름
- nullable(DDL) : null값의 허용 여부를 설정한다. false로 설정하면 DDL 생성 시에 not null제약조건이 붙는다.
- unique(DDL) : @Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용한다. 만약 두 컬럼 이상을 사용해서 유니크 제약조건을 사용하려면 클래스 레벨에서 @Table.uniqueConstraints를 사용해야한다.
- columnDefinition(DDL) : 데이터베이스 컬럼 정보를 직접 줄 수 있다.
- length(DDL) : 문자 길이 제약조건, String타입에만 사용 디폴트값 255
- precision, scale(DDL) : BigDecimal 타입에서 사용
- @Enumerated 속성 정리
- EnumType.ORDINAL : enum 순서를 데이터베이스에 저장,(주의해서 사용해야하며 STRING사용을 권장한다.)
- EnumType.STRING : enum 이름을 데이터베이스에 저장
2.1 @Enumerated 사용 예시
enum RoleType{
ADMIN, USER
}
//다음은 enum 이름으로 매핑한다.
@Enumerated(EnumType.STRING)
private RoleType roleType;
//enum은 다음처럼 사용한다.
member.setRoleType(RoleType.ADMIN); //DB에 문자 ADMIN으로 저장된다.
//@Enumerated를 사용하면 편리하게 enum 타입을 데이터베이스에 저장할 수 있다.
//EnumType.STRING은 enum 이름 그대로 ADMIN은 'ADMIN','USER' 문자그대로 DB에저장된다.
//roleType.ORDINAL 경우 값이 추가될 경우 기존의 값이 변경되지 않는문제가있어 권장하지않음
//roleType 자바의 enum을 사용해 회원의 타입을 구분했다. 일반 회원은 USER 관리자는 ADMIN이다. 이처럼 자바의 enum을 사용하려면
//@Enumerated 어노테이션으로 매핑해야한다.
@Enumerated(EnumType.STRING)
private RoleType roleType;
//자바의 날짜타입은 @Temporal을 사용해 매핑한다.
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
//회원을 설명하는 필드는 길이 제한이 없다. 따라서 DB의 VARCHAR타입 대신 CLOB 타입으로 저장한다. @LOB을 사용하면 CLOB, BLOB타입을 매핑할 수 있다.
@Lob
private String description;
@Transient
private String temp;
HBM2DDL 주의사항
- 운영서버에서 create, create-drop, update처럼 DLL을 수정하는 옵션은 절대사용하면안됨.
- 오직 개발단계에서만 사용
개발환경에 따른 추천전략- 개발초기단계에서는 create 또는 update
- 초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI 서버는 create또는 create-drop
- 테스트 서버는 update 또는 validate
- 스테이징과 운영서버는 validate 또는 none
Chapter 5. 연관관계 매핑 기초
연관관계 매핑 중요 사항
- 객체의 참조와 외래 키를 매핑하는 것이 연관관계 매핑의 목표
- 방향[단방향, 양방향]
- 다중성[1:1, 1:N, N:1, N:M]
- 연관관계의 주인 지정
@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;
}
try{
//팀 저장
Team team = new Team();
team.setName("teamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("hello");
member.setTeam(team); // 단방향 연관관계 설정, 참조 저장
em.persist(member);
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
//검색
정리
- 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
- 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
- 양방향 연ㄴ관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야한다.
연관관계의 주인을 정하는 기준 - 단방향은 항상 외래 키가 있는 곳을 기준으로 매핑하면된다.
Chapter 6. 다양한 연관관계 매핑
Chapter 7. 고급 매핑
- 상속관계 매핑과 @MappedSuperClass를 제외한 개념은 잘사용되지 않음.
상속관계 매핑
정의 : 슈퍼타입과 서브타입으로 구성된 관계모델로 구성되어있다. ««관계형 데이터베이스를 객체화 했을경우 이러한 전략방법은 3가지 존재한다.
- 각각 테이블 변환 -> 조인전략
- 통합테이블 변환 -> 단일 테이블 전략
- 서브타입 테이블 변환 -> 테이
- 조인 전략 -> 부모테이블의 기본키 + 외래키를 사용한 전략
- 단일 테이블 전략 ->
@MappedSuperClass
- 부모클래스 -> 테이블매핑은 하지않는다.
- 부모 -> 자식클래스의 매핑정보만 제공한다.
하지만 이 어노테이션 경우 사용할일이 거의 없으므로 추상클래스로 대체하면된다.
Chapter 8. 프록시와 연관관계 관리
- 즉시로딩 : 엔티티 조회할 때 연관된 엔티티도 함께 데이터베이스에서 조회한다.
- 지연로딩 : 엔티티의 값을 실제 사용하는 시점에 데이터베이스에서 팀 엔티티에 필요한 데이터를 조회하는것이다.
프록시
jpa에서 식자로 엔티티 하나 조회시 EntityManager.find()를 사용한다. 이 메소드는 영속성 컨텍스트에 엔티티가 없으면 데이터베이스를 조회한다.
Member member = em.find(Member.class, "member1");
- 위와 같이 엔티티를 직접 조회시 조회한 엔티티를 실제 사용하든 않든 DB를 통해 조회하게된다.
조회시점을 미루려면 EntityManager.getReference(Member.class, ““member1”) 메소드를 사용하면된다.
Member member = em.getReference(Member.class, "member1");
- 이 메소드 호출시 JPA는 DB 조회 않고 실제 엔티티객체도 생성않는다. 대신 DB 접근을 위한 프록시 객체를 반환한다.
프록시 특징
- 클래스 상속받아 만들어지므로 실제 클라스와 겉 모양이 같다. 따라서 사용하는 입장에서는 이것이 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면된다.
- 프록시 객체는 실제 객체에 대한 참조를 보관한다. 그리고 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화
프록시 객체는 member.getName()처럼 실제 사요될 때 데이터 베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 객체의 초기화라 한다.
//프록시 초기화 예제
//MemberProxy 반환
Member member = em.getReference(Member.class, "id1");
member .getName(); // 1. getName();
//프록시 클래스 예상 코드
class MemberProxy extends Member{
Member targer = null; // 실제 엔티티 참조
public String getName(){
if(target == null){
//2.초기화요청
//3. DB조회
//4. 실제 엔티티 생성 및 참조 보관
this.target = ...;
}
//5.target.getName();
return target.getName();
}
}
위의 코드에 대한 초기화 과정을 분석해보자
- 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라 한다.
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한번 만 초기화된다.
- 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference() 를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. 하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킨다.
준영속 상태와 초기화
준영속 상태와 초기화에 관련된 코드는 다음과 같다.
//MemberProxy반환
Member member = em.getReference(Member.class, "id1");
transaction.commit();
em.close(); //영속성 컨텍스트 종료
memeber.getName(); //준영속 상태 초기화 시도
//org.hibernate.LazyInitalizationException예외발생
이 코드를 보면 em.close() 메소드로 영속성 컨텍스트를 종료해서 member는 준영속 상태다. member.getName()을 호출하면 프록시를 초기화해야 하는데 영속성 컨텍스트가 없으므로 실제 엔티티를 조회할 수 없다. 따라서 예외가 발생한다.
즉시로딩과 지연로딩
프록시 객체는 주로 연관된 엔티티를 지연로딩 할 때 사용한다.
- 즉시로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
-
설정방법 : @ManyToOne(fetch = FetchType.EAGER) 정리 : 즉시로딩(EAGER) 연관된 엔티티를 즉시 조회한다. 하이버네이트는 가능하면 SQL조인을 사용해서 한번에 조회한다.
- 지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
- 예: memeber.getTeam().getName()처럼 조회한 팀 엔티티를 실제 사용하는 시점에 JPA가 SQL을 호출해서 팀 엔티티를 조회한다.
- 설정방법 : @ManyToOne(fetch = FetchType.LAZY) 정리 : 연관된 엔티티를 프록시로 조회한다. 프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회한다.
- 지연로딩활용 예시 페치전략 N:1, @ManyToOne, @OneToOne -> 즉시로딩 1:N, @OneToMany, @ManyYoMany -> 지연로딩
프록시와 컬렉션 래퍼
JPA 기본 페치 전략
영속성 전이와 고아 객체
영속성 전이
- CASCADE : 특정 엔티티를 영속 상태로 만들때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다. 부모 엔티티를 저장 할때 자식 엔티티도 함께 저장할 수 있다.
고아 객체 부모 엔티티와 연관관계가 끊긴 자식 엔티티를 뜻한다. 이러한 엔티티를 자동으로 삭제하는 기능을 제공한다.
부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.
@OneToMany(mappedBy = "parent", orphanRemoval = true) // orphanRemoval = true <<이 옵션이 고아된 자식 엔티티 자동으로 삭제
Chapter 9. 값 타입
기본 값 타입
임베디드 타입
값 타입과 불변 객체
Chapter 10. 객체지향 쿼리 언어
JPQL
QueryDSL
- 오픈소스임.
- 필요로하는것은 querydsl-jpa와 querydsl-apt(쿼리타입 생성용)가 필수.
public void queryDSL(){
EntityManager em = emf.createEntityManager();
JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m"); // 생성되는 JPQL 별칭이 m
List<Member> members =
query.from(qMember)
.where(qMember.name.eq("회원1"))
.orderBy(qMember.name.desc())
.list(qMember);
}
//QueryDSL 검색기능 쿼리
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<Item> list =
query.from(item)
.where(item.name.eq("좋은상품").and(item.price.gt(20000)))
.list(item); //조회할 프로젝션 지정
네이티브SQL
객체지향 쿼리 심화
Chapter 11. 웹 애플리케이션 제작
- 실습 개발 환경 뷰 : JSP,JSTL 웹 계층 : 스프링 MVC 데이터 저장 계층 : JPA, 하이버네이트 기반 프레임워크 : 스프링 프레임워크 빌드 : 메이븐
hibernate.id.new_generator_mappings : true
@Embedded 엔티티 단위로 객체화
@Embeddedable 클래스 단위로 객체화
@PersistenceContext
순수 자바 환경에서는 엔티티 매니저 펙토리에서 엔티티 메니저를 직접 생성해서 사용했지만, 스프링이나 J2EE 컨테이너를 사용할 시 컨테이너가 엔티티매니저를 관리하고 제공해준다.
컨테이너가 관리하는 엔티티매니저를 주입하는 어노테이션이다.
Chapter 12. 스프링 데이터 JPA
Spring Data JPA
- 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다.
개발자가 직접 구현체를 개발하지 않아도 된다.
public interface MemberRepository extends JpaRepository<Member, Long>{
Member findByUsername(String username);
}
public interface ItemRepository extends JpaRepository<Item, Long>{
}
일반적인 CRUD 메소드는 JpaRepository 인터페이스가 곹옹으로 제공하므로 문제가 없다. 그런데 MemberRepository.findByUsername(…)처럼 직접 작성한 공통으로 처리할 수 없는 메소드는 스프링데이터 JPA에서 메소드 이름을 분석해서 JPQL을 실행한다.
select m from Member m where username =:username
spring data jpa 필요라이브러리
- spring-data-jpa, spring-data-commons에 의존하므로 두 라이브러리 필수임.
스프링설정에 javaconfig 환경설정 방법이다. 스프링 데이터 jpa 는 애플리케이션 실행 시 basepackage에 있는 리포지토리 인터페이스들을 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한 다음 스프링 빈으로 등록한다. 따라서 개발자가 직접 구현클래스를 만들지 않아도 된다.
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig{
}
Spring-data-jpa에서 JpaRepository 인터페이스를 상속받으면 사용할 수 있는 주요 메서드 몇가지가 있다.
- save(S) : 새로운 엔티티는 저장.persist()하고 이미 있는 엔티티는 수정.merge()한다.
- delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()를 호출하는것이다.
- getOne(Id) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference()를 호출하는것이다.
- findOne(Id) : 엔티티를 하나 조회한다. 내부에서 EntityManager.find()를 호출하는것임.
- findAll() : 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 제공할 수 있다.
쿼리 메소드 기능
메소드 이름만으로 쿼리를 생성하는 기능으로 인터페이스에 메소드만 선언하면 메소드의 이름으로 적절한 JPQL쿼리를 생성해 실행한다.
스프링데이터 JPA가 제공하는 쿼리 메소드는 크게 3가지 있음.
- 메소드 이름으로 쿼리생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의
- 메소드 이름으로 쿼리생성
public interface MemberRepository extends Repository<Member, Long>{ List<Member> findByEmailAndName(String email, String name); }
인터페이스에 정의한 findByEmailAndName() 메소드 실행하면 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행한다. 자동생성된 JPQL은 다음과같다.
select m from Member m where m.email = ?1 and m.name = ?2
물론 정해진 규칙에 따라서 메소드 이름을 지어야한다. 스프링데이터 JPA 공식문서가 제공하는 표를 참고하라
Comments