문제 상황
- JPQL 을 통해 ADMIN(관리자)인 회원들을 조회하는 과정에서 조회 쿼리도 나가지 않고, 엔티티 객체 정보도 저장되지 않는 문제가 발생.
실제 구현 코드
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("Spring1");
member.setAge(15);
member.setTeam(team);
member.setType(MemberType.ADMIN);
em.persist(member);
em.flush();
em.close();
String query = "select m.username, 'HELLO', true from Member m " +
"where m.type = :memberType";
List<Object[]> result = em.createQuery(query)
.setParameter("memberType", MemberType.ADMIN)
.getResultList();
for (Object[] objects : result) {
System.out.println("objects[0] = " + objects[0]);
System.out.println("objects[1] = " + objects[1]);
System.out.println("objects[2] = " + objects[2]);
}
tx.commit();
}
- 코드만 보고 무엇이 문제인지 알았다면, 영속성 컨텍스트와 트랜잭션과 관련한 이해도가 높다고 할 수 있다. (거의 1시간 이상을 헤맸다..)
- 런타임 에러도 발생하지 않아서, 엄청 간단한 문제인데도 시간이 오래 걸림.
해결
- 해결 방법은 너무 간단했다.
em.close()
가 문제였던 것.
em.close()
는 영속성 컨텍스트를 종료해버리고, 내부적으로 DB Connection 을 반환한다.
- 즉, tx.commit() 이라는 트랜잭션 커밋 단계 전에 엔티티 매니저를 통한 DB Connection 을 끊어버렸기 때문에, 결과적으로 DB 에는 저장이 안된 것이다.
- 헷갈릴 수 있었던 부분은, em.flush() 를 통해 INSERT 쿼리는 정상적으로 나간다는 것이다. 그럼 보통 이 때 DB 에 내용들이 반영된다고 생각하는데, 반은 맞고 반은 틀린 얘기다.
트랜잭션 생명주기가 끝나고, 엔티티 매니저를 종료해줘야 정상적으로 DB Connection 을 반환하면서 최종 저장되는 것이다.
- 실제로, finally 부분에 em.close() 를 빼도, INSERT 쿼리는 나가지만 DB 에 들어가면 반영되지 않는 것을 확인할 수 있다.
느낀점...
- 해결 방법은 너무 쉽지만, 관련 개념들을 계속 찾아보니 생각보다 어려운 부분들도 많았다. JPA 가 객체와 테이블 사이에서 쿼리를 매핑해주는 역할을 하는 만큼, JDBC API를 사용해 Database 와 통신까지 담당한다는 것을 다시 한 번 느끼는 시간이었다.