데이터들이 프로그램이 종료되어도 사라지지 않고 어떤 곳에 저장되는 개념
자바에서는 데이터의 영속성을 위한 JDBC를 지원해주는데, 매핑 작업을 개발자가 일일히 수행해야 하는 번거로움이 있다.
Persistence Layer에 JDBC가 있음
mapper inerface와 mapper 구현체인 xml을 1대1 매칭
자바 ORM의 기술 표준
대표적인 오픈소스로 Hibernate
JPA는 단순히 명세이기 때문에 구현이 없다.
대부분 interface, annotation으로 이루어져 있다.
package javax.persistence;
import ...
public interface EntityManager {
public void persist(Object entity);
public <T> T merge(T entity);
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
// More interface methods...
}
그래서 JPA를 사용하기 위해서 JPA를 구현한 Hibernate같은 ORM 프레임워크를 사용해야 한다.
CRUD 메소드 기본 제공
쿼리를 만들지 않아도 됨
1차 캐싱, 변경 감지, 지연 로딩 제공
MyBatis는 쿼리가 수정되어 데이터 정보가 바뀌면 그에 사용되고 있던 DTO와 함께 수정해주어야 하는 반면에, JPA는 객체만 바꾸면 된다.
복잡한 쿼리는 해결이 어렵다.
1차 캐시
EntityManager
인스턴스는 자체 1차 캐시를 가지고 있다.EntityManager
가 엔티티를 처음 조회하면, 그 엔티티는 1차 캐시에 저장된다.EntityManager
내에서 동일한 엔티티를 다시 조회하려고 할 때, JPA는 데이터베이스에 쿼리를 보내지 않고 1차 캐시에서 해당 엔티티 반환EntityManager
인스턴스와 캐시 공유하지 않음EntityManager
가 닫히면 1차 캐시와 같이 제거EntityManager
인스턴스간에 공유 가능Hibernate가 지원하는 메소드 내부에서 JDBC API가 동작하고 있으며, 단지 개발자가 직접 SQL을 작성하지 않을 뿐이다.
JPA의 핵심인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.
쓰기 지연(Write-Behind)
변경 감지(Dirty-Checking)
지연 로딩
@Entity
@Getter
public class Member extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 패치 타입 LAZY 설정
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", insertable = false, updatable = false)
private Team team;
public void changeTeam(Team team) {
this.team = team;
this.team.getMembers().add(this);
}
}
장점
단점
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaExample {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 엔티티 조회
User user = em.find(User.class, userId);
em.getTransaction().commit();
em.close();
}
}
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// CRUD 메서드 및 페이징/정렬 기능이 자동으로 제공됩니다.
}
// 서비스 또는 컨트롤러에서 사용
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long userId) {
return userRepository.findById(userId).orElse(null);
}
}
name
컬럼이 없고 username
컬럼이 있다면…//컴파일 오류
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class User {
@Id
private Long id;
private String name; // 데이터베이스에는 'username'이라는 컬럼이 있어야 한다.
// 기타 getter 및 setter 생략
}
//런타임 오류
public interface UserMapper {
@Select("SELECT id, name FROM users")
User findUserById(Long id);
}
⇒ JPA는 컴파일 시점에 오류가 발생하고 MyBatis는 해당 함수를 호출했을 때 런타임 오류가 발생함
public interface UserMapper {
@Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
}
public interface UserRepository extends JpaRepository<User, Integer> {
User save(User user);
}