// JPA 표준 인터페이스 사용
@PersistenceContext
private EntityManager entityManager; // 실제론 Hibernate의 구현체가 주입됨
// Hibernate 직접 사용
Session session = entityManager.unwrap(Session.class);
// 최초 조회: DB에서 조회
User user1 = entityManager.find(User.class, 1L); // SELECT 쿼리 실행
// 동일 트랜잭션 내 재조회: 캐시에서 조회
User user2 = entityManager.find(User.class, 1L); // 쿼리 실행 없음
// 1차 캐시 내용 확인
boolean isCached = entityManager.contains(user1); // true
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
// 쓰기 지연 저장소에 SQL 저장
entityManager.persist(new User("user1")); // INSERT SQL 생성
entityManager.persist(new User("user2")); // INSERT SQL 생성
// 실제 DB에 SQL 실행
tx.commit(); // 한 번에 모아서 실행
tx.begin();
// 영속 엔티티 조회
User user = entityManager.find(User.class, 1L);
// 엔티티 데이터만 수정
user.setName("newName");
// 별도의 persist() 호출 필요 없음
tx.commit(); // 변경 감지가 자동으로 UPDATE SQL 생성 및 실행
User user = new User();
user.setName("user1"); // 영속성 컨텍스트와 관련 없음
entityManager.persist(user); // 영속성 컨텍스트에서 관리됨
entityManager.detach(user); // 영속성 컨텍스트에서 분리
entityManager.remove(user); // 삭제 상태
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
}
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders; // orders 접근 시점에 SQL 실행
}
User user = entityManager.find(User.class, 1L); // SELECT user만 실행
user.getOrders(); // 이 시점에 SELECT orders 실행
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Order> orders; // user 조회 시 orders도 함께 조회
}
User user = entityManager.find(User.class, 1L); // SELECT user + JOIN orders
// N+1 문제 발생 예시
List<User> users = em.createQuery("select u from User u", User.class)
.getResultList(); // SELECT * FROM user
// users 순회하면서 orders 접근 시 각각 추가 쿼리 발생
for (User user : users) {
user.getOrders().size(); // SELECT * FROM orders WHERE user_id = ?
}
// JOIN FETCH를 사용하여 한 번에 데이터 조회
List<User> users = em.createQuery(
"SELECT u FROM User u JOIN FETCH u.orders", User.class)
.getResultList();
@EntityGraph(attributePaths = {"orders"})
@Query("SELECT u FROM User u")
List<User> findAllWithOrders();
@Entity
public class User {
@BatchSize(size = 100)
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
// 필요한 데이터만 조회하여 DTO로 변환
List<UserDTO> users = em.createQuery(
"SELECT new com.example.UserDTO(u.id, u.name) FROM User u",
UserDTO.class)
.getResultList();
User user1 = entityManager.find(User.class, 1L); // DB 조회
User user2 = entityManager.find(User.class, 1L); // 1차 캐시에서 조회
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
public class User {
// 엔티티 정의
}
// 단건 처리
for (User user : users) {
user.setActive(true);
entityManager.persist(user);
}
// 벌크 연산
entityManager.createQuery(
"update User u set u.active = true where u.lastLoginDate < :date")
.setParameter("date", date)
.executeUpdate();
// 읽기 전용 세션으로 조회
Session session = entityManager.unwrap(Session.class);
session.setDefaultReadOnly(true);
List<User> users = session.createQuery("from User", User.class).list();
Q: JPA와 MyBatis의 차이점을 설명하고, 어떤 상황에서 JPA를 선택해야 하는지 설명해주세요.
A: JPA와 MyBatis는 다음과 같은 차이점이 있습니다:
패러다임
생산성
JPA 선택이 적절한 상황:
Q: 영속성 컨텍스트의 이점을 실무에서의 예시와 함께 설명해주세요.
A: 영속성 컨텍스트는 다음과 같은 이점을 제공합니다:
// 동일 트랜잭션에서 같은 엔티티를 여러 번 조회할 때 성능 향상
User user1 = userRepository.findById(1L); // DB 조회
User user2 = userRepository.findById(1L); // 캐시에서 조회
@Transactional
public void updateUser(Long id, String newName) {
User user = userRepository.findById(id);
user.setName(newName); // 명시적인 save() 호출 불필요
} // 트랜잭션 종료 시 자동으로 UPDATE SQL 실행
@Transactional(readOnly = true)
public String getUserName(Long id) {
User user = userRepository.findById(id);
return user.getName(); // orders는 로딩되지 않음
}
Q: N+1 문제가 발생하는 상황과 해결 방법을 실제 예시와 함께 설명해주세요.
A: N+1 문제는 연관 관계에서 발생하는 성능 문제입니다.
발생 상황:
@Entity
public class Order {
@ManyToOne(fetch = FetchType.EAGER)
private User user;
}
// N+1 문제 발생
List<Order> orders = orderRepository.findAll(); // SELECT * FROM orders
// 각 order마다 user를 조회하는 추가 쿼리 발생
// SELECT * FROM users WHERE id = ? (N번 실행)
해결 방법:
1. 페치 조인
@Query("SELECT o FROM Order o JOIN FETCH o.user")
List<Order> findAllWithUser();
@EntityGraph(attributePaths = {"user"})
List<Order> findAll();
# application.properties
spring.jpa.properties.hibernate.default_batch_fetch_size=100
Q: JPA의 영속성 전이(Cascade)와 고아 객체 제거에 대해 설명하고, 주의할 점을 말씀해주세요.
A: 영속성 전이와 고아 객체 제거는 엔티티의 라이프사이클을 관리하는 방법입니다.
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders; // User 저장 시 연관된 Order도 함께 저장
}
@Entity
public class User {
@OneToMany(mappedBy = "user", orphanRemoval = true)
private List<Order> orders; // 연관관계가 끊어진 Order 자동 삭제
}
주의사항:
Q: JPA에서 성능 최적화를 위해 사용할 수 있는 방법들을 설명해주세요.
A: JPA에서는 다음과 같은 성능 최적화 방법들을 제공합니다:
@Transactional(readOnly = true)
@Query("SELECT u FROM User u")
List<User> findAllUsers(); // 스냅샷 생성 및 변경 감지 비활성화
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.lastLoginDate < :date")
int updateUserStatus(@Param("status") String status, @Param("date") LocalDateTime date);
@Query("SELECT new com.example.UserDTO(u.id, u.name) FROM User u")
List<UserDTO> findAllUserDtos(); // 필요한 컬럼만 조회
Pageable pageable = PageRequest.of(0, 10);
Page<User> users = userRepository.findAll(pageable);
이러한 최적화는 상황에 따라 적절히 선택하여 사용해야 하며, 실제 성능 측정을 통해 효과를 확인해야 합니다.