Java Query Language (JQL)
JQL은 사실 정식 명칭이 아니라 Java 엔티티 이름으로 쿼리를 작성할 때 사용되는 언어를 지칭한다. JPQL과 QueryDSL에서 이를 사용한다. 반면, SQL은 데이터베이스의 테이블 이름을 기준으로 쿼리를 작성한다.
1. JPQL (Java Persistence Query Language)
JPQL은 Java의 엔티티를 기준으로 쿼리를 작성한다. 이를 사용하기 위해 EntityManager나 @Query 애노테이션을 활용한다.
1-1. EntityManager.createQuery()
쿼리 문자열과 엔티티 클래스를 직접 사용하여 쿼리를 생성한다. 쿼리 파라미터는 key-value 문자열을 통해 매핑한다. 문자열로 직접 쿼리를 작성하는 방식은 여러 문제점이 있다. 이를 해결하기 위해 상수 클래스를 선언하여 문자열을 관리하는 방법을 권장한다.
@Test
public void testEmCreateQuery() {
String qlString = "select u from User u where u.username = :username";
User findUser = em.createQuery(qlString, User.class)
.setParameter("username", "teasun")
.getSingleResult();
assertEquals("teasun", findUser.getUsername());
}
1-2. @Query (Repository interface)
@Query 애노테이션을 통해 엔티티 이름으로 쿼리를 작성한다. 변수 바인딩은 ?변수순번이나 :변수명을 사용한다.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.username = ?1")
List<User> findByUsername(String username);
}
2. QueryDSL (JPAQueryFactory)
QueryDSL은 엔티티의 매핑 정보를 활용하여 쿼리에 적합하도록 Q클래스로 재구성하는 기술이다. JPAQueryFactory는 이 Q클래스를 활용하여 객체나 함수로 쿼리를 작성하고 실행하게 해주는 기능을 제공한다.
@Autowired
JPAQueryFactory queryFactory;
public User findUserByUsername(String username) {
QUser qUser = QUser.user;
return queryFactory.selectFrom(qUser)
.where(qUser.username.eq(username))
.fetchOne();
}
3. @DynamicInsert, @DynamicUpdate
@DynamicInsert는 Hibernate에서 엔티티의 저장 시 일부 필드가 null일 경우 해당 필드를 SQL INSERT 문에서 제외하는 어노테이션이다. 이를 사용하면 데이터베이스 테이블의 기본값 설정을 활용할 수 있다.
@Entity
@DynamicInsert
public class Product {
@Id
private Long id;
private String name;
private Double price;
private Double discount; // 이 필드가 null이면 INSERT 문에서 제외된다.
// 생성자, 게터, 세터 생략
}
@DynamicUpdate는 Hibernate에서 엔티티의 갱신 시 변경된 필드만을 대상으로 SQL UPDATE 문을 생성한다. 이를 통해 불필요한 데이터베이스 작업을 줄일 수 있다.
@Entity
@DynamicUpdate
public class Product {
@Id
private Long id;
private String name;
private Double price;
private Double discount; // 이 필드가 변경되지 않았다면 UPDATE 문에서 제외된다.
// 생성자, 게터, 세터 생략
}
두 쿼리문의 실행시간 141ms->133ms, 미약하지만 분명한 성능향상이 있었다. 대량의 쿼리문을 다루게 된다면 유의미하게 될 것이다.
4. 프로젝션 (Projection)
프로젝션은 특정 열만 선택하여 데이터를 조회하는 기술로, 전체 엔티티가 아닌 필요한 데이터만을 추출하는 방식이다. 이 방식은 데이터베이스 쿼리의 효율성을 크게 향상시킬 수 있다.
성능 향상 기대 부분:
- 네트워크 트래픽 감소: 필요한 데이터만을 전송하기 때문에 네트워크를 통해 전송되는 데이터 양이 감소한다.
- 메모리 사용량 감소: 애플리케이션 서버에서 처리하는 데이터 양이 줄어들어 메모리 사용량이 감소한다.
- 속도 향상: 데이터베이스에서 데이터를 처리하고 전송하는 속도가 빨라진다. 특히, 데이터베이스가 불필요한 컬럼을 읽어들이고 처리하는 비용이 감소한다.
활용 예시:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Product> product = query.from(Product.class);
query.select(product.get("name")); // 오직 이름만 조회한다.
List<String> names = entityManager.createQuery(query).getResultList();
- 사용자 인터페이스에서 간단한 목록을 보여줄 때, 전체 정보가 아닌 특정 정보만 보여줘야 할 때 유용하다.
- 대량의 데이터가 있는 테이블에서 몇 개의 열만 필요한 리포트나 분석을 수행할 때 사용된다.
5. Query by Example (QBE)
Query by Example(QBE)은 샘플 객체를 기반으로 쿼리를 생성하는 방법으로, 객체의 필드 값에 따라 동적으로 쿼리 조건이 결정된다. 이는 복잡한 쿼리를 작성하지 않고도, 객체의 상태를 이용하여 데이터를 조회할 수 있게 해준다.
성능 향상 기대 부분:
- 개발 효율성 향상: 쿼리를 직접 작성하는 대신 객체를 이용해 쿼리를 생성하므로, 개발 시간과 노력이 줄어든다.
- 유지보수 용이: 코드가 간결해지고, 수정이 필요한 경우 객체의 상태만 조정하면 되기 때문에 유지보수가 쉬워진다.
- 동적 쿼리 생성: 사용자의 입력이나 상황에 따라 다르게 쿼리를 생성할 수 있어, 런타임에 유연하게 대응할 수 있다.
활용 예시:
Product exampleProduct = new Product();
exampleProduct.setName("Pen"); // 이름이 'Pen'인 제품을 찾는다.
Example<Product> example = Example.of(exampleProduct);
List<Product> products = productRepository.findAll(example);
- 사용자가 검색 조건을 다양하게 입력하는 경우, 각각의 검색 조건에 맞는 결과를 동적으로 생성해야 할 때 사용된다.
- 특정 필드 값에 따라 다르게 결과를 필터링해야 할 때, 예를 들어 상품 검색에서 색상, 크기, 브랜드 등 다양한 조건을 조합하여 검색하는 경우이다.
'Diary > TIL' 카테고리의 다른 글
2024-04-06) Pinpoint 등록 시도 (0) | 2024.05.15 |
---|---|
2024-04-05) ngrinder 적용 시도 (0) | 2024.05.14 |
2024-04-03) JPA 필기 2 (0) | 2024.05.14 |
2024-04-02) JPA 필기 1 (0) | 2024.05.13 |
2024-04-01) 프로메테우스, 그라파나 학습 (0) | 2024.05.13 |