JPQL은 테이블 대상으로 쿼리하는 것이 아니라, 엔티티 객체를 대상으로 쿼리한다.
select m from Student as s where s.age < 18
파라미터 바인딩 할 때는 아래와 같이 작성한다. 파라미터를 넣는 방법은 :{변수명} 을 작성하면된다. 아래 예시 코드는 Spring Data JPA 인데, @Query에 쿼리를 작성하고, 메서드 파라미터로 쿼리에 들어가는 파라미터의 변수명을 입력하면된다.
public interface QuizRepository extends JpaRepository<Quiz, Long>, QuizRepositoryCustom {
@Modifying(clearAutomatically = true)
@Query("update Quiz q set q.status = :status where q.id in :quizIds")
void changeStatus(@Param(value = "quizIds") List<Long> quizIds, @Param(value = "status") QuizStatus status);
}
JPA 프로젝션(Projection)이란 JPQL 또는 QueryDSL을 사용하여 엔티티의 전체가 아닌 일부 필드만 조회하는 방식을 의미한다.
즉, 특정 컬럼만 선택해서 조회하는 것
예를 들어, User 엔티티에서 id, name만 조회하고 싶을 때 사용한다.
엔티티 프로젝션 (Entity Projection)
엔티티 전체를 조회하는 방식
List<User> users = em.createQuery("SELECT u FROM User u", User.class)
.getResultList();
필드 기반 프로젝션 (Scalar Projection)
특정 필드만 조회하는 방식
List<String> names = em.createQuery("SELECT u.name FROM User u", String.class)
.getResultList();
여러 필드를 함께 조회하는 방식
List<Object[]> results = em.createQuery("SELECT u.name, u.email FROM User u")
.getResultList();
for (Object[] row : results) {
String name = (String) row[0];
String email = (String) row[1];
}
DTO 기반 프로젝션 (DTO Projection)
JPQL에서 DTO를 직접 매핑하는 방식
List<UserDTO> users = em.createQuery(
"SELECT new com.example.dto.UserDTO(u.name, u.email) FROM User u",
UserDTO.class)
.getResultList();
JPQL에서는 SQL에서 사용되는 서브쿼리를 작성할 때 다음과 같은 함수들을 사용할 수 있다.
EXISTS (존재 여부 확인)
SELECT u FROM User u
WHERE EXISTS (
SELECT o FROM Order o WHERE o.user = u AND o.status = 'DELIVERED'
)
IN (서브쿼리 결과와 비교)
SELECT u FROM User u
WHERE u.id IN (
SELECT o.user.id FROM Order o WHERE o.status = 'CANCELED'
)
ALL (모든 결과와 비교)
= ALL, <= ALL 같은 형태로 사용 가능
SELECT u FROM User u
WHERE u.age >= ALL (
SELECT u2.age FROM User u2 WHERE u2.status = 'ACTIVE'
)
ANY (하나라도 만족하면 TRUE)
= ANY, <= ANY, = ANY 형태로 사용 가능
SELECT p FROM Product p
WHERE p.price < ANY (
SELECT p2.price FROM Product p2 WHERE p2.category = 'ELECTRONICS'
)
SOME (ANY와 동일)
❌ SELECT (SELECT AVG(u.age) FROM User u) FROM User u2 -- 불가능!
❌ SELECT u FROM (SELECT * FROM User WHERE age > 20) AS subquery -- 불가능!
• CONCAT
• SUBSTRING
• TRIM
• LOWER, UPPER
• LENGTH
• LOCATE
• ABS, SQRT, MOD
• SIZE, INDEX(JPA 용도)
명시적 조인: join 키워드 직접 사용
select m from Member m join m.team t
묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생
(내부 조인만 가능)
select m.team from Member m
✅ fetch join은 따로 정리
JPA 기본 적으로 dirty checking 을 활용해서 update를 처리한다. 그런데 엄청 많은 데이터를 일괄적으로 변경하려고 하면 데이터 하나에 update 쿼리 하나가 나가기 때문에 성능적으로 좋지 않다. 이럴 때 사용하는 게 벌크 연산이다.
JPA에서 벌크 연산을 수행하는 방법은 크게 두 가지가 있다.
@Modifying + @Query (Spring Data JPA)
Spring Data JPA에서는 @Modifying 어노테이션을 사용하여 JPQL을 직접 작성하여 벌크 연산을 수행할 수 있다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query("UPDATE User u SET u.status = 'INACTIVE' WHERE u.lastLogin < :date")
int deactivateOldUsers(@Param("date") LocalDateTime date);
@Modifying
@Query("DELETE FROM User u WHERE u.status = 'DELETED'")
int deleteInactiveUsers();
}
EntityManager를 사용한 벌크 연산
Spring Data JPA 없이 EntityManager를 직접 사용하여 벌크 연산을 수행할 수도 있다.
@Transactional
public void bulkUpdateStatus(EntityManager entityManager) {
Query query = entityManager.createQuery(
"UPDATE User u SET u.status = 'INACTIVE' WHERE u.lastLogin < :date"
);
query.setParameter("date", LocalDateTime.now().minusMonths(6));
int updatedCount = query.executeUpdate();
System.out.println("비활성화된 사용자 수: " + updatedCount);
}
벌크 연산은 영속성 컨텍스트를 무시하고 직접 DB에 반영하기 때문에 몇 가지 주의해야 할 점이 있다.
벌크 연산 후 영속성 컨텍스트 초기화
벌크 연산은 영속성 컨텍스트를 거치지 않고 DB에 바로 적용되므로, 영속성 컨텍스트에 남아 있는 엔티티 정보와 불일치할 수 있다.
따라서 벌크 연산 후 clear()를 호출하여 영속성 컨텍스트를 초기화하는 것이 좋다.
@Modifying
@Query("UPDATE User u SET u.status = 'INACTIVE' WHERE u.lastLogin < :date")
int deactivateOldUsers(@Param("date") LocalDateTime date);
@Transactional
public void updateUserStatus() {
int count = userRepository.deactivateOldUsers(LocalDateTime.now().minusMonths(6));
entityManager.clear(); // 영속성 컨텍스트 초기화
}
clearAutomatically 옵션
Spring Data JPA에서 @Modifying을 사용할 때, 벌크 연산 후 자동으로 영속성 컨텍스트를 초기화하고 싶다면 다음과 같이 설정할 수 있다.
@Modifying(clearAutomatically = true)
@Query("UPDATE User u SET u.status = 'INACTIVE' WHERE u.lastLogin < :date")
int deactivateOldUsers(@Param("date") LocalDateTime date);
@Transactional 사용 여부
Spring Data JPA에서 @Modifying을 사용하면 기본적으로 트랜잭션이 필요함.