
이번 포스트에서는 백엔드 개발에서 자주 사용하는 JPA(Java Persistence API)에 대해 간단하게 설명하고, Spring과 함께 어떻게 사용할 수 있는지 알아보겠습니다.
JPA의 핵심 기능도 함께 다루며, 예제 코드를 통해 공부해보려고 합니다.
먼저 ORM의 간단한 개요부터 알아보겠습니다.
ORM(Object-Relational Mapping)은 객체와 관계형 DB 사이의 데이터를 매핑해주는 기술입니다.
쉽게 말해, 객체를 통해 DB 테이블을 조작할 수 있게 해줍니다.
일반적으로 자바 프로그램에서는 데이터를 객체로 다루고, DB 테이블과 SQL을 사용해 데이터를 관리합니다.
그런데, 이 두 가지는 다소 다른 개념이죠. ORM은 이 간극을 메워줍니다.
자바 객체와 DB 테이블을 연결해주고, 우리가 객체를 조작하면 ORM이 알아서 SQL로 변환해 DB에 반영합니다.
JPA(Java Persistence API)는 자바에서 ORM을 위한 표준 인터페이스입니다.
즉, JPA는 자바 애플리케이션에서 DB와 객체 간의 매핑을 쉽게 할 수 있도록 도와주는 ORM 기술의 표준입니다.
대표적인 구현체로는 Hibernate가 있습니다.
JPA는 단순한 CRUD 기능 외에도 몇 가지 중요한 핵심 기능을 제공합니다.
이를 잘 이해해야 JPA를 제대로 활용할 수 있습니다.
영속성 컨텍스트는 JPA에서 엔티티 객체를 관리하는 일종의 '저장소'입니다.
엔티티가 영속성 컨텍스트에 포함되면, JPA는 이 객체의 상태를 추적하며, 필요할 때 DB에 자동으로 반영합니다.
User user = new User();
user.setName("dakcoh");
user.setEmail("dakcoh@example.com");
entityManager.persist(user); // 영속성 컨텍스트에 저장
1차 캐시
영속성 컨텍스트는 1차 캐시를 제공하여 동일한 트랜잭션 안에서는 같은 객체를 여러 번 조회할 때 DB에 다시 접근하지 않고 캐시에서 가져옵니다. 이를 통해 성능이 향상됩니다.
JPA는 엔티티의 변경 사항을 자동으로 감지해 트랜잭션이 끝날 때 DB에 반영해줍니다.
즉, 객체의 값을 수정하고 별도의 SQL을 작성하지 않아도, 트랜잭션이 커밋될 때 자동으로 변경 사항이 DB에 저장됩니다.
User user = entityManager.find(User.class, 1L);
user.setEmail("new_email@example.com"); // DB에 반영됨
성능 최적화
변경 감지 기능을 통해 불필요한 업데이트를 줄일 수 있지만, 너무 많은 엔티티가 관리될 경우 성능 문제가 발생할 수 있으니 주의해야 합니다.
연관된 엔티티를 언제 로드할지 결정하는 방법입니다.
기본적으로 지연 로딩(LAZY)을 사용해 필요할 때만 데이터를 불러오는 것이 성능에 유리합니다.
@ManyToOne(fetch = FetchType.LAZY)
private User user; // 지연 로딩 설정
성능 고려
즉시 로딩(EAGER)은 관련된 모든 데이터를 한 번에 가져오지만, 불필요한 데이터를 함께 로드할 수 있어 성능 저하가 발생할 수 있습니다. 가능하면 지연 로딩(LAZY)을 사용하시면 좋습니다.
JPA는 SQL 대신 JPQL이라는 객체 지향 쿼리 언어를 사용합니다.
테이블이 아닌 객체를 대상으로 쿼리를 작성할 수 있어 객체 지향적인 개발이 가능합니다.
String jpql = "SELECT u FROM User u WHERE u.name = :name";
List<User> users = entityManager.createQuery(jpql, User.class)
.setParameter("name", "dakcoh")
.getResultList();
복잡한 쿼리는 네이티브 SQL 사용
JPQL은 객체 지향적으로 쿼리를 작성할 수 있다는 장점이 있지만, 복잡한 쿼리가 필요할 때는 네이티브 SQL을 사용하는 것도 좋은 방법입니다.
이제 Spring에서 JPA를 사용해 간단한 CRUD(Create, Read, Update, Delete)를 구현해보겠습니다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters and Setters
}
@Entity는 이 클래스가 DB 테이블과 연결된다는 것을 의미하고,@Id는 기본 키를 나타냅니다.@GeneratedValue는 기본 키를 자동 생성하는 전략을 설정합니다.
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name); // 이름으로 사용자 찾기
}
JpaRepository를 상속하면 기본적인 CRUD 메서드를 사용할 수 있으며, 이름으로 사용자를 검색하는findByName메서드를 추가했습니다.
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
public User createUser(@RequestBody User user) {
return userRepository.save(user); // 사용자 저장
}
}
POST요청으로 들어온User객체를 DB에 저장하는 코드입니다.save()메서드를 통해 사용자가 저장됩니다.
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userRepository.findById(id)
.map(user -> ResponseEntity.ok(user))
.orElse(ResponseEntity.notFound().build());
}
ID로 사용자를 조회하는 코드입니다.
findById()메서드로 DB에서 해당 ID를 가진 사용자를 찾습니다.
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
return userRepository.findById(id)
.map(user -> {
user.setName(updatedUser.getName());
user.setEmail(updatedUser.getEmail());
userRepository.save(user); // 변경된 사용자 저장
return ResponseEntity.ok(user);
})
.orElse(ResponseEntity.notFound().build());
}
기존 사용자의 정보를 수정하는 코드입니다.
findById()로 사용자를 찾고, 변경된 내용을 저장합니다.
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
return userRepository.findById(id)
.map(user -> {
userRepository.delete(user); // 사용자 삭제
return ResponseEntity.noContent().build();
})
.orElse(ResponseEntity.notFound().build());
}
ID로 사용자를 찾아 삭제하는 코드입니다.
delete()메서드를 사용해 사용자를 삭제합니다.
이번 포스트에서는 ORM과 JPA의 개념, 그리고 JPA의 핵심 기능을 알아보았습니다.
Spring과 함께 JPA를 사용해 간단한 CRUD 기능을 구현하는 방법도 함께 살펴봤습니다.
JPA는 객체 지향적인 방식으로 DB와 상호작용할 수 있게 해주어 개발 생산성을 높이는 강력한 도구이지만, 성능 문제를 유발할 수 있는 지점도 있으니 주의가 필요합니다.
다음 포스트에서는 JPA 성능 최적화 방법에 대해 다뤄보겠습니다.