Java Persistence API
자바 진영의 ORM 기술 표준
ORM (Object-relational mapping)
JPA는 애플리케이션과 JDBC 사이에서 동작
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
//엔티티 매니저 팩토리 생성, 비용이 많이 들어 하나만들어서 공유해서 사용
//hello는 JPA설정파일 네임
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
//엔티티 매니저 생성, 비용이 많이 들지 않으며 공유해서 사용하면 동시성문제 발생
EntityManager em = emf.createEntityManager();
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
//객체를 생성한 상태 (비영속)
Member member = new Memeber();
member.setId("member1"):
member.setName("성명");
//객체를 생성한 상태 (비영속)
Member member = new Memeber();
member.setId("member1"):
member.setName("성명");
//객체를 저장한 상태 (영속)
em.persist(member);
em.find()나 JPQL를 사용해서 조회한 엔티티도 영속 상태이다.
//회원 엔티티를 영속성 컨텍스트에서 분리
em.detach(member); //특정 엔티티만 준영속상태
em.clear(); //영속성 컨텍스트 초기화
em.close(); //영속성 컨텍스트 종료
//객체를 삭제한 상태
em.remove(member);
1차캐시
영속성컨텍스트에 저장되어 있을 경우 조회시 1차캐시에서 조회
저장이 되어 있지 않을 경우 데이터베이스에서 1차캐시에 저장 후 조회
동일성 보장
1차캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
트랜잭션을 지원하는 쓰기지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // 트랜잭션 커밋
변경감지
특별한 update 없이 엔티티 변경을 감지하여 데이터 자동변경
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작
//영속성 엔티티 조회
Member findMember = em.find(Member.class, "memberA");
//영속성 엔티티 수정
findMember.setName("updateName");
transaction.commit(); // 트랜잭션 커밋
변경순서
트랜잭션 커밋 -> 엔티티 매니저 내부에서 먼저 플러시 호출
엔티티와 스냅샷을 비교해서 변경된 엔티티 찾는다.
변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소로 보낸다.
쓰기 지연 저장소의 SQL을 데이터베이스로 보낸다.
데이터베이스 트랜잭션을 커밋변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
지연로딩
뒤에 자세히 설명할 예정
<property name="hibernate.hbm2ddl.auto" value="create" />
운영장비에는 절대 create,create-drop,update 사용하면 안됨
제약조건추가 : @Column(nullable = false, length =10)
유니크 제약조건 추가 : @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME","AGE"} )})
IDENTITY : 데이터베이스에 위임, MYSQL
SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용,ORACLE
@SequenceGenerator 필요
TABLE : 키 생성용 테이블 사용
@TableGenerator 필요
AUTO : 데이터베이스 방언에 따라 자동 지정, 기본값@Id @GeneratedValue(strategy = GeneratedType.AUTO) private Long id;
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
//@Column(name = "TEAM_ID")
//private Long teamId;
//팀의 기본키대신 팀이라는 객체와 직접매핑
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
//저장
Team team = new Team();
team.setName("Team");
em.persist(team);
Member member = new Memeber();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정으로 참조 저장
em.persist(member)
//조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
//수정
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
member.setTeam(teamB); //새로운 팀으로 변경
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
Team 엔티티에 컬렉션 추가 (Member 엔티티는 기존과 동일)
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
...
}
반대 방향으로 객체 그래프 탐색
//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회
테이블의 양방향 한개의 양방향 관계이지만,
객체의 양방향 관계는 서로 다른 단방향 2개다.
연관관계의 주인
객체의 두 관계중 하나를 연관관계의 주인으로 지정
연관관계의 주인만이 외래키를 관리 (등록,수정이 가능)
주인이 아닌 쪽은 읽기만 가능
주인은 mappedBy 속성 사용불가능 / 주인이 아니면 mappedBy 속성으로 지정
외래키가 있는 곳을 주인으로 정해라 / 예시에서는 Memeber.team이 주인
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Memeber();
member.setName("member1");
team.getMembers().add(member); //주인이 아닐 경우 저장되지 않음
member.setTema(team); //연관관계주인에 값 설정
//순수 객체 상태를 고려해서 항상 양쪽에 값 설정
//연관관계 편의 메소드로 편하게 값설정
실질적으로는 다대일 매핑을 주로 사용
조인전략
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
단일 테이블 전략
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
구현 클래스마다 테이블 전략
출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
일반적으로 조인전략을 사용하고 간단한 구조일 경우 단일 테이블 전략 사용
구현 클래스마다 테이블전략은 사용하지 않도록
@MappedSuperclass
공통 매핑 정보가 필요시에 사용
단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할
예를들면 등록자,등록일,수정자,수정일 같은 공통으로 적용하는 정보
직접생성해서 사용하지 않으므로 추상클래스 권장
@MappedSuperclass
public abstract class BaseEntity {
@Column(name = "INSERT_BY")
private String createBy;
private LocalDateTime createDate;
private String modifiedBy;
private LocalDateTime modifiedDate;
...
}
//사용시
public class Item extends BaseEntity {
...
}