JPA 는 Java Persistence Api 의 줄임말으로, 자바의 ORM 표준 기술이다.
ORM 은 Object-relational mapping(객체 관계 매핑)으로 객체를 관계형 데이터베이스에 연결해주는 역할을 한다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(1L);
member.setName("Hello");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
EntityManagerFactory: 자바를 실행하면 단 하나의 객체만 존재하며 사용자의 모든 요청마다 EntityManager 를 만들어서 반환해준다.
EntityManager: 사용자의 요청마다 만들어지며, 사용 후에는 무조건 파기한다.
EntityTransaction: 데이터베이스 사용 중 오류가 발생 할 경우 롤백, 성공적으로 끝날 시 commit 을 하여 데이터베이스 사용에 안정적인 흐름을 제공한다.
특이점으로는 entityManager의 경우 사용자가 사용을 끝냈을 경우 종료를 해야하며 entityManagerFactory 는 서버가 꺼지기 직전에 종료를 해야한다.
(요즘 jpa 는 내부적으로 동작해준다.)
영속성 컨텍스트는 눈에 보이지 않는 하나의 공간이라고 생각하면 쉽다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근이 가능하며, 보통은 한 트랜잭션 단위로 만들어 진다.
엔티티의 생명 주기는
Member member = new Member(); // 비영속 상태
member.setId(1L);
member.setName("hello");
em.persist(member); // 영속 상태
em.detach(member); // 준영속 상태
em.remove(member); // 삭제 상태(데이터베이스에서 삭제)
1차 캐시: 영속성 컨텍스트안에 1차캐시가 존재해 영속이 되어 있는 엔티티 같은 경우 재검색 했을때 디비를 거치지 않고 바로 결과값을 반환해준다.
동일성 보장: 영속성 컨텍스트에서 이미 데이터가 들어가 있으면 같은 주소값을 가진 객체를 반환한다.
트랜잭션을 지원하는 쓰기 지연: 쓰기 지연 SQL 저장소라는 곳에 따로 저장해두었다가 flush 가 발생하면 데이터베이스에 쓰기 실행
쓰기 지연을 사용안하고 바로 디비로 SQL 을 날리면 되지 않나요?
- 많은 데이터가 있을 시 버퍼링 기능을 사용하여 디비와 한번만 커넥트함으로써 성능의 이점을 가져갈 수 있다.
Member findMember = em.find(Member.class, 1L); //영속 상태
findMember.setName("changeName"); // 변경 감지가 일어남
Member findMember = em.find(Member.class, 1L);
findMember.getTeam(); // 지연 로딩
@Column: 필드와 컬럼 매핑
@ManyToOne, @OneToMany: 연관관계 매핑
@Lab: 문자의 경우 BLOB, 나머지는 CLOB
@Enumerated: enum 타입 매핑 (EnumType.STRING 만 사용할 것!)
@Transient: 특정 필드를 무시
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="member_name")
private String name;
@ManyToOne
private Team team;
@Lab
private String description;
@Enumerated(EnumType.STRING)
private MemberType type;
}
xml 일 경우 <property name="hibernate.hbm2ddl.auto" value="create" />
yml 일 경우
spring:
hibernate:
ddl-auto: create
개발할 때만 create, update 등 사용, 실제 서버에서는 절대로 사용을 하지 않습니다.(none 적용)
객체는 참조를 이용해 연관된 객체를 찾는 반면에 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾는다.
기존에는 id 값을 통해서 테이블을 검색했지만 Jpa 에서는 객체를 통해 검색하는 것처럼 행동할 수 있다.
외래키로 양쪽 둘다 검색을 할수 있는 반면 객체는 둘다 사용할 수 없다.
하지만 @OneToMany
를 사용해 객체에서도 양방향(단방향 2개)를 만들 수 있다.
@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
@ManyToOne
@JoinColumn(name="team_id")
private Team team;
//team
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<>();
members.add(member); //실제 데이터 베이스에 적용되지 않음
setTeam(team); // 연관관계의 주인이기 때문에 team 이 성공적으로 잘 들어감
위에서 setTeam 을 통해 넣어줬을때 members 에 보면 데이터가 아무것도 없다. 다음과 같은 메서드로 양방향으로 세팅을 해줘야 한다.
//member
changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
프로젝트를 개발할 때 단방향으로 먼저 개발할고 필요할 때 양방향으로 세팅하는 것이 좋다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
즉시 로딩을 사용하면 예상하지 못한 sql 들이 나가기 때문에 실무에서는 반드시 지연 로딩을 사용하는 것을 추천드립니다.
Parent parent = new Parent();
Child child1 = new Child();
parent.addChild(child1); // child1 을 persist 하지 않아도 영속 상태가 됨
em.persist(parent)
Parent parent = new Parent();
Child child1 = new Child();
parent.addChild(child1);
em.flush();
em.clear();
Parent findParent = em.fine(parent.getId(), Parent.class);
findParent.getChildren().remove(0); // 객체에서만 삭제되는게 아니라 디비에서도 삭제 됨
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() { // 기본생성자 필수
}
...
}
@Entity
public class Memeber {
@Id @GeneratedValue
private Long id;
@Embedded
private Address address;
...
}
임베디드 타입이나 값 타입을 컬렉션으로 jpa 에서 사용할 수 있도록 도와주고 있다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@ElementCollection
@CollectionTable(name = "friend_name", joinColumns =
@JoinColumn(name = "member_id")
)
@Column(name = "friend_name") // 여기 이름으로 테이블이 생성됨
private Set<String> friendNames = new HashSet<>();
@ElementCollection
@CollectionTable(name = "address", joinColumns =
@JoinColumn(name = "member_id")
)
private List<Address> addressList = new ArrayList<>();
}
@Entity
@Table(name = "address")
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@OneToMany(cascade = CASCADETYPE.ALL, ohparnRemoval = true)
@JoinColumn(name = "member_id")
private List<AddressEntity> addressList = new ArrayList<>();
}
jpa 로 전체를 해결할 수 없으므로 쿼리 언어를 사용한다.
여러가지가 있지만 실무에서 추천받은 언어들은 다음과 같다.
select m from Member m where m.id = 10
String username = "hello";
em.createQuery("SELECT m FROM Member m where m.username=:username", Member.class)
.setParameter("username", username)
.getResultList();
String username = "hello";
em.createQuery("SELECT m FROM Member m where m.username=?1", Member.class)
.setParameter(1, username)
.getResultList();
em.createQuery("SELECT m FROM Member m", Member.class)
.setFirstResult(50)
.setMaxResults(100)
.getResultList();
연관 필드를 이용하면 묵시적으로 내부 조인이 발생함(판별하기 어려움) - 실무에서 비추천하는 방식
ex)
select o.member from Order o // JPQL
select m.* from Orders o inner join Member m on o.member_id = m.id //SQL
ex)
select m from Member m join fetch m.team; //JPQL
select m.*, t.* from member m inner join Team t on m.team_id=t.id; //SQL
여러 테이블을 조인할 경우 페치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO 로 반환하는 것이 좋음