Spring발행일 2024. 8. 17.원본 https://blog.naver.com/jword_/223550919620 ↗

자바 JPQL 이 글만 보세요

자바 JPQL 이 글만 보세요 — #자바 #JPQL #자바JPQL #자바JPA #개발자의도구들 🚨📝 📖 📒✏️💡🔍 AI스쿨 ms...

#Spring#Naver Blog

#자바 #JPQL #자바JPQL #자바JPA #개발자의도구들

🚨📝 📖 📒✏️💡🔍

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring 프로젝트를 진행 중입니다. 전체적인 목차를 보시려면 여기​를 눌러주세요.

이미지

잠깐 복습하고 가실게요

이전글에서 JAVA에서 JPA를 사용하는 방법과 기본적인 CRUD 메서드에 대해 소개를 해드렸습니다. 본격적으로 시작전에 짧막하게 정리해보겠습니다.

​

  • JPA는 ORM을 통해 DB의 table과 JAVA class를 자동으로 매핑해준다.
  • JPA를 통해 쿼리를 날리려면 EntityManager가 필요하다. (우리는 Hibernater로 등록했어요)
  • EntityManager은 기본적인 CRUD연산 메서드를 지원해준다.
  • 메서드를 사용하면 JPA가 알아서 DB에 Query를 생성 후 날리고 결과를 반환받고 다시 Class로 변환해서 우리에게 전달해준다.

​

나타난 문제

이전글에서 나타난 문제는 EntitManger가 복잡한 연산에 대한 method가 직접 구현되어 있지 않았습니다.

package com.example.webserver.repository;​import com.example.webserver.entity.BoardEntity;import lombok.extern.slf4j.Slf4j;import jakarta.persistence.\;​import java.util.List;import java.util.Optional;​@Slf4jpublic class BoardRepositoryImpl implements BoardRepository { EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("onlyJPAUnit"); EntityManager entityManager = entityManagerFactory.createEntityManager();​ @Override public List<BoardEntity> findAll() { return null; } @Override public Optional<BoardEntity> findById(long id) { return Optional.of*(entityManager.find(BoardEntity.class, id)); } @Override public void removeById(long id) { entityManager.remove(id); }; @Override public void save(BoardEntity board){ entityManager.persist(board); };}​

위 코드에서 기본적인 CRUD 연산은 단순 method로 구현을 하고 잇으나, findAll() 메서드는 구현하지 못했습니다.

​

이를 해결하기 위해 우리는 JPQL을 사용할 것입니다.

JPQL

Java Persistence Query Languages의 약자로 JAVA JPA에서 사용하는 커스텀(?) 쿼리라고 보면 이해가 빠르실 겁니다.

​

JPQL의 가장 큰 특징은 SQL과 달리 Entity에 초점을 맞춰 쿼리를 작성한다는 것입니다.


JPQL Syntax

거두절미하고 바로 어떻게 사용하는지 알아보겠습니다.

​

SELECT

JPQL의 꽃이 아닌가 생각됩니다. 복잡한 조건을 만족하는 Entity를 조회하기 위해 사용되는게 JQPL인데 가장 많이 사용되는 곳이 SELECT이기 떄문이죠.

기본형SELECT b(약칭) FROM Entity(조회할 ENtity) b WHERE ORDERBY... // 이 경우에 모든 Column을 조회한다.​SELECT b.title FROM Entity b;// 이경우 b Entity의 title field만을 조회한다.​매개변수를 받아서 질의WHERE b.title = :title// parmeter로 넘어온 데이터를 Query문에 작성할 수 있다. (아래 참고)

🔍EntityManger를 통해 질의하기 (기본형)

@Overridepublic List<BoardEntity> findAll() {return entityManager.createQuery("SELECT b FROM BoardEntity b", BoardEntity.class) .getResultList();}
  • 잘보시면 Entity대상으로 조회를 하고 있는 걸 보실 수 있습니다.
  • JPQL을 내보낼때는 createQuery메서드를 사용합니다.
  • getResultList()로 모든 결과를 List에 담아서 return합니다.

​

​

🔍EntityManger 통해 매개변수를 받아서 질의하

이번에는 사용자로부터 받은 입력값을 참조하여 쿼리를 생성해보도록 하겠습니다. Like문을 사용해서 사용자의 입력값을 사용하여 query를 만들어 보겠습니다.

@Overridepublic List<BoardEntity> findByWriterLike(String writer) {return entityManager.createQuery("SELECT b FROM BoardEntity b WHERE b.writer like :writer"​ ,BoardEntity.class) .setParameter("writer", writer) .getResultList();}
  • JPQL문에 :writer를 사용하여 변수를 지정합니다.
  • setParameter()메서드를 사용해 변수를 할당합니다.

​

DELETE

위의 SELECT와 WHERE을 이해했다면, 크게 sql과 다를게 없습니다.

"DELETE FROM Countury c WHERE c.population < :p"

ENTITY를 사용하여 조회하는 것이기에 SELECT와 맥락이 일치합니다.

​

UPDATE

UPDATE역시 거의 동일합니다.

"UPDATE Countury SET poplulation = 0, area = 0"); ​"UPDATE Countury c SET c.poplulation = 0, c.area = 0"); ​"UPDATE Countury as c SET c.poplulation = 0, c.area = 0");
  • 위 세 문장은 전부 같은 말입니다.
  • 여러가지 field를 지정하여 update할 수 잇습니다.

Complicated Syntax

And / OR / ()

하나의 조건이 아닌 여러 조건을 동시에 만족하는 조건문을 작성할 수 잇씁니다. 이때는 SQL처럼 and와 or 그리고 ()로 묶어서 연산을 할 수 있습니다.

​

🔍 두 개 이상 조건을 만족하는 데이터 조회

  1. BoardEntity.writer에 'admin'이 포함되어 있고, 'user'가 포함되어 있지 않는 데이터 조회
@Overridepublic List<BoardEntity> findByWriterAndNotContainLike(String writer, String notContain) {return entityManager.createQuery("SELECT b FROM BoardEntity b " + "WHERE b.writer LIKE :writer AND b.writer NOT LIKE :notContain", BoardEntity.class) .setParameter("writer", writer) .setParameter("notContain", notContain) .getResultList();}
  1. BoardEntity.writer에 'admin'이 포함되어 있고, 'user'가 포함되어 있지 않고, readingCountㄱ 100 이하이거나 10,000 보다 큰 데이터 조회
@Overridepublic List<BoardEntity> findByWriterAndReadingCount(String writer, String notContain,Integer minReads, Integer maxReads) { return entityManager.createQuery("SELECT b FROM BoardEntity b " + "WHERE b.writer LIKE :writer AND b.writer NOT LIKE :notContain " + "AND (b.readingCount > :minReads or b.readingCount <= :maxReads)" ,BoardEntity.class) .setParameter("writer", writer) .setParameter("notContain", notContain) .setParameter("minReads", minReads) .setParameter("maxReads", maxReads) .getResultList();}

연관된 두 테이블 질의하기

이전글에 BoardEntity의 구조가 기억이 나시나요? 혹시 안나시는 분들을 위해서 아래 다시 알려드릴게요.

package com.example.webserver.entity;​import jakarta.persistence.\;import java.time.LocalDateTime;import java.util.List;​@Entitypublic class BoardEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY*)@Column(name="id")private long boardId;​@Column(name = "title", nullable = false)private String title;​@Column(name = "writer", nullable = false)private String writer;​@Column(name = "writing\_time", nullable = false)private LocalDateTime writingTime;​@Column(name = "reading\_count", nullable = false)private Integer readingCount;​@Column(name = "text\_content", nullable = false)private String textContent;​@OneToMany(mappedBy = "board")private List<CommentEntity> comments;}

맨 아래의 comments 필드의 경우 CommentEntity와 1:m의 관계를 맺고 있는 필드임을 알 수 있는데요. 이는 BoardEntity하나당 대응되는 CommentEntity가 여러개라는 뜻이기도 합니다(게시글 구조를 생각해보세요!)

​

그럼 이번에 두 테이블을 함께 고려하여 조건을 만족하는 BoardEntity의 Comments를 return 해보도록 하겠습니다.

​

🔍 JOIN을 사용하지않고 두 테이블의 원하는 Entity 조회하기

  1. BoardEntity.writer에 'admin'이 포함되어 있고, 'user'가 포함되어 있지 않고, readingCountㄱ 100 이하이거나 10,000 보다 큰 BoardEntity의 comments를 모두 조회
public List<CommentEntity> findByCustomCriteria(CustomCriteria customCriteria) {return entityManager.createQuery( "SELECT c FROM CommentEntity c " + "WHERE c.board.writer LIKE :writer " + "AND c.board.writer NOT LIKE :notContain " + "AND c.board.readingCount > :minReads " + "AND c.board.readingCount <= :maxReads" , CommentEntity.class) .setParameter("writer", customCriteria.getWriter()) .setParameter("notContain", customCriteria.getNotContainWriter()) .setParameter("minReads", customCriteria.getMinReads()) .setParameter("maxReads", customCriteria.getMaxReads()) .getResultList();}
  • 이건 CommentEntity내부에 매핑되어있는 BoardEntity를 참조하여 질의를 하는 코드입니다.
  • ​

🔍 JOIN을 사용하여 두 테이블의 원하는 Entity 조회하기

public List<CommentEntity> findByCustomCriteria(CustomCriteria customCriteria) {return entityManager.createQuery( "SELECT c FROM CommentEntity c " + "JOIN c.board b " + "WHERE b.writer LIKE :writer " + "AND b.writer NOT LIKE :notContain " + "AND (b.readingCount > :minReads " + "OR b.readingCount <= :maxReads)" ,CommentEntity.class) .setParameter("writer", customCriteria.getWriter()) .setParameter("notContain", customCriteria.getNotContainWriter()) .setParameter("minReads", customCriteria.getMinReads()) .setParameter("maxReads", customCriteria.getMaxReads()) .getResultList();}

번외

JPQL에서는 다양한 내장 함수를 지원합니다.

@Query("SELECT UPPER(u.username) FROM User u") List<String> findAllUsernamesInUpperCase();​@Query("SELECT LENGTH(u.username) FROM User u")List<Integer> findAllUsernameLengths();​@Query("SELECT CONCAT(u.username, ' - ', u.email) FROM User u")List<String> findAllUsernameWithEmails();

이 외에도 다양한 내장함수를 지원하니 관심있으신 분들은 공식문서를 참조하세요!

​

​https://en.wikibooks.org/wiki/Java\_Persistence/JPQL#Functions

[Java Persistence/JPQL - Wikibooks, open books for an open world
Java Persistence/JPQL < Java Persistence Contents 1 JPQL 1.1 Select Queries 1.1.1 Select query examples 1.1.2 SELECT Clause 1.1.2.1 Aggregation functions 1.1.2.2 Constructors 1.1.3 FROM Clause 1.1.3.1 JOIN 1.1.3.2 JOIN FETCH 1.1.3.3 LEFT JOIN 1.1.3.4 ON (JPA 2.1) 1.1.3.5 Sub-selects in FROM clause 1...
en.wikibooks.org](https://en.wikibooks.org/wiki/Java_Persistence/JPQL#Functions)

​