JpaRepository를 보면 findById
와 getOne
두가지의 비슷한 기능을 하는 메서드가 존재한다.
먼저 시그니처를 살펴보자.
T getOne(ID id);
Optional<T> findById(ID id);
둘다 Id를 파라미터로 받도록 되어있고, Id에 매칭되는 객체 하나만 가져오는데 어떤 차이가 있어서 다르게 제공하고 있는걸까?
단순히 Optional로 결과를 받는 차이가 있는것처럼 보이지만, 실제로는 더 중요한 차이점이 있다.
Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an javax.persistence.EntityNotFoundException on first access. Some of them will reject invalid identifiers immediately.
getOne은 내부적으로 EntityManager.getReference()
를 통해 엔티티를 가져오게 되어있다. 이 메서드는 lazy loading을 지원하는데, 호출되는 시점에는 일단 proxy를 가져오며 실제로 가져온 엔티티의 속성에 접근하는 순간 DB에 접근하는 방식을 사용한다.
Retrieves an entity by its id.
실제 DB를 바로 조회해서 필요한 데이터를 가져온다. 당연히 반환되는 객체도 데이터가 매핑되어있는 실제 엔티티 객체이다.
그럼 어떤 경우에 사용하면 getOne의 이점을 얻을 수 있을까?
아래 예시를 확인해보자.
@Entity
data class Board(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var title: String,
)
@Entity
data class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var title: String,
@ManyToOne
@JoinColumn(name = "board_id")
var board: Board,
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "post", orphanRemoval = true, fetch = FetchType.EAGER)
var comments: MutableList<Comment> = mutableListOf()
)
@Test
@Transactional
fun testFindById() {
val board = boardRepository.findByIdOrNull(1)!!
val post = Post(
title = "sample title",
board = board
)
postRepository.save(post)
}
@Test
@Transactional
fun testGetOne() {
val board = boardRepository.getOne(1)
val post = Post(
title = "sample title",
board = board
)
postRepository.save(post)
}
먼저 testFindById의 실행로그를 살펴보자.
Hibernate: select board0_.id as id1_0_0_, board0_.title as title2_0_0_ from board board0_ where board0_.id=?
Hibernate: insert into post (board_id, like_count, title) values (?, ?, ?)
코드 작성한 순서대로 예상한대로 동작한다.
실제 DB에 접근해서 Board를 만들고 이를 통해 연관관계를 매핑한다.
그 이후 post테이블에 저장하는 쿼리를 실행한다.
그러나 getOne을 사용하는 경우 쿼리는 아래와 같다.
Hibernate: insert into post (board_id, like_count, title) values (?, ?, ?)
board테이블을 조회하는 쿼리가 없다.
실제로 Id기반으로 연관관계는 매핑이 잘 되지만, board의 데이터를 사용하지 않는 예제코드에서는 굳이 Board 테이블을 접근할 필요가 없기때문에 이 경우 성능적으로 이득을 얻을 수 있는것이다.
그러나 이때문에 위험요소도 생길 수 있는데, DB자체에 foriegn key 제약조건이 걸려있지 않다면 잘못된 데이터가 들어갈수도 있다.
Board(Id=5)인 데이터가 실제하지 않음에도 Post에 추가를 할 수 있다.