Coding 공부/IntelliJ

[IntelliJ_Springboot_MariaDB] Update, Delete 예제, JPA 페이징, 페이징 예제, querydsl 사용 설정, Querydsl

CBJH 2024. 5. 9.
728x90
반응형

1. JpaRepository : Update, Delete 예제

 

@Test
public void testUpdate() {
    Long bno = 10L;
    Optional<Board> result = boardRepository.findById(bno);
    if (result.isPresent()) {
        Board board = result.orElseThrow();
        board.change("updateed... title", "updateed... content");
        boardRepository.save(board);
        log.info("---------------------------------------------------------");
        log.info(board);
    } else {
        log.info("No Board found with bno: " + bno);
    }
}

@Test
public void testDelete() {
    Long bno = 10L;
    Optional<Board> result = boardRepository.findById(bno);
    if (result.isPresent()) {
        Board board = result.get();
        boardRepository.delete(board);
        log.info("---------------------------------------------------------");
        log.info(board);
    }else{
        log.info("No Board found with bno: " + bno);
    }
}
  • Optional 콜렉션에 isPresent(), ifPresent() 메서드로 받아온 데이터가 있는지 체크할 수 있다. (데이터가 없으면 update, delete가 안되므로 if문을 통해 오류를 방지할 수 있다.)
  • isPresent()는 boolean 값을 리턴한다.
  • ifPresent()는 매개 변수에 Sysout같은 구문을 넣어 체크할 수 있다. 리턴값이 없는 void 메서드이다.

 

2. JPA 페이징

  • JPA에서 페이징 처리는 데이터를 페이지 단위로 나눠서 처리할 때 사용됩니다. 특히 대량의 데이터가 존재하는 경우 전체 데이터를 한 번에 로드하는 것이 아니라, 사용자가 요청한 만큼의 데이터만 서버로부터 가져오는 기법입니다. 이를 통해 서버의 부하를 줄이고, 응답 시간을 단축하며, 사용자 경험을 향상시킬 수 있습니다.
  • JPA에서 페이징을 구현하기 위해 주로 사용되는 클래스와 인터페이스는 다음과 같습니다:

  2.1 Pageable 인터페이스

  • Pageable은 페이징 정보를 추상화한 인터페이스입니다. 사용자로부터 페이지 번호와 페이지 크기, 정렬 정보 등을 받아 이를 페이징 처리에 필요한 정보로 사용합니다. Spring Data JPA에서는 PageRequest라는 클래스를 통해 이 인터페이스를 구현합니다. 이 클래스를 사용하여 요청 페이지 번호, 페이지당 데이터 수, 정렬 방향 등을 설정할 수 있습니다.

  2.2 Page 인터페이스

  • Page는 페이징된 데이터를 캡슐화하여 반환할 때 사용되는 인터페이스입니다. 이 인터페이스는 조회된 데이터뿐만 아니라 총 데이터 수, 페이지 번호, 전체 페이지 수 등 페이징 처리에 필요한 다양한 정보를 제공합니다.

  2.3 페이징 처리의 구현 예

  • Spring Data JPA를 사용하는 경우, 리포지토리 인터페이스에서 Pageable 객체를 파라미터로 받는 메서드를 선언하면 스프링 데이터가 자동으로 페이징 처리를 지원합니다. 예를 들어:
public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findAll(Pageable pageable);
}
  • 이 메서드를 사용할 때는 PageRequest를 생성하여 메서드에 전달합니다. 예를 들어, 첫 번째 페이지의 데이터 10개를 정렬 없이 조회하고 싶다면:
PageRequest pageRequest = PageRequest.of(0, 10); // 0은 첫 번째 페이지, 10은 페이지 당 데이터 수
Page<User> page = userRepository.findAll(pageRequest);
  • 이렇게 하면 Page 객체를 통해 데이터와 함께 페이징 처리에 필요한 추가 정보를 얻을 수 있습니다.

  2.4 정렬 추가

  • PageRequest 생성 시 정렬 방향과 기준이 될 속성을 추가로 지정할 수 있습니다:
PageRequest pageRequest = PageRequest.of(0, 10, Sort.Direction.ASC, "lastName");
  • 이 코드는 사용자의 성(lastName)에 대해 오름차순으로 정렬하여 첫 번째 페이지의 데이터 10개를 가져옵니다.

  2.5 결론

  • JPA에서 페이징은 데이터 처리의 효율성을 높이고 서버의 부하를 줄이는 중요한 기법입니다. Spring Data JPA와 같은 프레임워크를 사용하면 페이징 처리를 매우 간편하게 구현할 수 있으며, 개발자가 직접 페이징 로직을 작성하지 않아도 되는 큰 이점을 제공합니다.

 

3. 페이징 예제 

@Controller
@Log4j2
public class SampleController {
    
    @Autowired
    private BoardRepository boardRepository;

    @GetMapping("/ex/ex02")
    public String list(@RequestParam(value = "page", defaultValue = "0") int page,
                       @RequestParam(value = "size", defaultValue = "10") int size,
                       Model model) {

        Pageable pageable = PageRequest.of(page, size, Sort.by("bno").descending());
        Page<Board> boardPage = boardRepository.findAll(pageable);

        model.addAttribute("boards", boardPage.getContent());
        model.addAttribute("totalPages", boardPage.getTotalPages());
        model.addAttribute("currentPage", page);
        return "ex/ex02";
    }
}
  • Springboot에는 @RequestParam 같은 어노테이션들이 많다. 하나씩 써보면서 감을 익혀야겠다.
  • PageRequest.of()로 설정한 pageable을 JPA로 DB에 접근하는 함수의 매개 변수로 받으면 Page<Board> 컬렉션을 반환한다. 
  • Page<Board> 컬렉션은 getContent()로 List<Board>타입을 반환하거나, getTotalPages()로 총 페이지 수를 반환하는 등 페이지에 관한 다양한 값을 반환한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Board List</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <h1>Board List</h1>
    <table class="table table-hover">
        <thead>
        <tr>
            <th>Bno</th>
            <th>Title</th>
            <th>Writer</th>
            <th>Registration Date</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="board : ${boards}">
            <td th:text="${board.bno}"></td>
            <td th:text="${board.title}"></td>
            <td th:text="${board.writer}"></td>
            <td th:text="${board.regDate}"></td>
        </tr>
        </tbody>
    </table>
    <div>
        <ul class="pagination">
            <li th:classappend="${currentPage == 0} ? 'disabled' : ''" class="page-item">
                <a class="page-link" th:href="@{/ex/ex02(page=${currentPage - 1})}">Previous</a>
            </li>
            <li th:each="i : ${#numbers.sequence(0, totalPages - 1)}" class="page-item"
                th:classappend="${i == currentPage} ? 'active' : ''">
                <a class="page-link" th:href="@{/ex/ex02(page=${i})}" th:text="${i + 1}">1</a>
            </li>
            <li th:classappend="${currentPage + 1 >= totalPages} ? 'disabled' : ''" class="page-item">
                <a class="page-link" th:href="@{/ex/ex02(page=${currentPage + 1})}">Next</a>
            </li>
        </ul>
    </div>
</div>
</body>
</html>

 

이전 코드로 MariaDB Board 테이블에 199개 자료가 입력되어있다.

 

 

4. build.gradle 수정 : querydsl 사용 설정

buildscript {
    ext{
        queryDslVersion = "5.0.0"
    }
}

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.5'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'org.zerock'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'

    // https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect
    implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
    annotationProcessor(
            "jakarta.persistence:jakarta.persistence-api",
            "jakarta.annotation:jakarta.annotation-api",
            "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
    )
}

tasks.named('test') {
    useJUnitPlatform()
}

compileJava.dependsOn('clean')
 
 
  • annotationProcessor 구문을 build.gradle 파일에 추가하는 것은 Gradle 빌드 프로세스에 특정 어노테이션 프로세서를 등록하는 것을 의미합니다. 이를 통해 Java 컴파일 시점에 어노테이션 기반 코드를 자동으로 생성하거나 수정할 수 있습니다. 여기서 추가한 각각의 의존성은 다음과 같은 목적으로 사용됩니다:
  1. jakarta.persistence:jakarta.persistence-api:
    • 이 라이브러리는 Jakarta Persistence API를 포함하고 있습니다. 이 API는 데이터베이스 컨텐츠를 자바 객체로 관리할 수 있는 표준 방법을 제공합니다. 일반적으로 JPA는 엔티티 객체와 데이터베이스 테이블 간의 매핑을 설정하고 데이터 액세스를 쉽게 처리하는 데 사용됩니다.
  2. jakarta.annotation:jakarta.annotation-api:
    • Jakarta Annotations API는 일반적인 어노테이션(주석)을 정의하여 Java 어플리케이션의 다양한 부분에서 사용할 수 있도록 합니다. 예를 들어, @PostConstruct, @PreDestroy 같은 생명주기 이벤트를 다루는 어노테이션들이 포함되어 있습니다. 이는 자원 관리, 의존성 주입 등의 설정을 어노테이션을 통해 자동으로 처리할 수 있게 도와줍니다.
  3. com.querydsl:querydsl-apt:${queryDslVersion}:jakarta:
    • QueryDSL은 타입-세이프한 쿼리를 Java에서 작성할 수 있게 하는 프레임워크입니다. querydsl-apt는 QueryDSL을 위한 어노테이션 프로세서로, 엔티티 클래스에 정의된 어노테이션을 바탕으로 Q타입 클래스를 자동으로 생성합니다. 이 Q타입 클래스들은 QueryDSL 쿼리 작성 시 사용되며, 컴파일 타임에 쿼리의 정확성을 보장해줍니다.
  • 이 의존성들을 추가함으로써, Java 프로젝트에서 JPA를 사용하여 데이터베이스 엔티티를 관리하고, 일반적인 어노테이션을 사용하여 다양한 설정을 쉽게 구현하며, QueryDSL을 사용하여 타입-세이프하고 유지보수가 쉬운 쿼리를 작성할 수 있게 됩니다. 이러한 도구들은 대체로 엔터프라이즈 수준의 어플리케이션 개발에 있어서 코드의 품질을 높이고, 개발 과정을 간소화하는 데 큰 도움을 줍니다.

 

 

5. Querydsl을 사용하여 사용자 정의 쿼리 메서드를 구현

  • Spring Data JPA 리포지토리를 확장
  • Querydsl은 타입 안전성을 제공하며, 복잡한 쿼리를 직관적으로 작성할 수 있게 해주는 프레임워크
public interface BoardSearch {
    // 사용자 정의 인터페이스, 페이징 처리된 Board 엔티티 목록을 검색하는 메서드 선언
    Page<Board> search1(Pageable pageable);
}

public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch {
    // Querydsl 지원을 위한 기반 클래스를 확장
    public BoardSearchImpl() {
        super(Board.class); // Board 엔티티를 기반 클래스에 전달
    }

    @Override
    public Page<Board> search1(Pageable pageable) {
        QBoard board = QBoard.board; // Querydsl의 Q 클래스 인스턴스 생성, 엔티티의 각 필드에 접근 가능
        JPQLQuery<Board> query = from(board); // 시작점 지정, from 절과 같은 역할
        query.where(board.title.contains("3")); // where 절 추가, 제목에 '3'이 포함된 엔티티 검색
        List<Board> list = query.fetch(); // 쿼리 결과를 List로 가져옴
        long count = query.fetchCount(); // 총 결과 수를 계산

        // TODO: 페이징 로직이 구현되지 않았습니다. PageImpl 객체를 반환해야 함
        return new PageImpl<>(list, pageable, count); // PageImpl을 사용하여 페이지 정보와 함께 결과 반환
    }
}

@Test
public void testSearch1(){
    Pageable pageable
            = PageRequest.of(1, 10, Sort.by("bno").descending()); // 페이지 요청 생성, 2번째 페이지, 페이지당 10개 항목, bno 내림차순 정렬
    boardRepository.search1(pageable); // 정의된 사용자 정의 메서드 호출
}
  • 페이징 처리: 위의 구현에서는 실제로 페이징 처리를 수행하지 않고 있습니다. Querydsl에서 페이징 처리를 위해서는 offset과 limit 메서드를 사용하여 쿼리를 수정해야 합니다.
  • PageImpl의 사용: 여기서 PageImpl은 Spring Data의 Page 인터페이스를 구현하는 클래스입니다. 이 클래스는 실제 페이지 데이터와 함께 전체 데이터 수를 포함할 수 있으므로, 완전한 페이징 지원을 위해 필요합니다.
  • 테스트의 범위: 테스트 메서드에서 실제로 반환된 Page 객체를 검증하는 로직이 없습니다. 실제 사용 시에는 반환된 객체의 내용을 검사하는 로직을 포함해야 할 것입니다.
  • 이 예제는 Querydsl을 사용하여 복잡한 데이터베이스 쿼리를 자바 코드 내에서 타입 안전하게 구성하고 실행하는 방법을 보여줍니다.

 

6. Querydsl

  • Querydsl은 복잡한 SQL 쿼리, JPQL(JPA Query Language), JDOQL(JDO Query Language) 등을 타입 안전하게 구성하고 실행할 수 있도록 하는 프레임워크입니다. 주로 Java 언어로 데이터베이스 쿼리를 표현하는 데 사용되며, SQL과 같은 쿼리 언어에 익숙한 개발자라도 Querydsl의 타입 안전성과 직관적인 API는 큰 이점을 제공합니다.

  6.1 주요 특징

  1. 타입 안전성: Querydsl은 Java 코드로 쿼리를 작성하므로, 컴파일 시점에 타입 체크가 가능합니다. 이로 인해 쿼리의 오류를 빠르게 발견하고 수정할 수 있습니다.
  2. 동적 쿼리: 복잡한 조건에 따라 동적으로 쿼리를 구성할 수 있습니다. 예를 들어, 사용자의 입력에 따라 검색 조건을 변경해야 할 때 유용합니다.
  3. 코드 중심 접근: 쿼리를 자바 코드로 작성함으로써, IDE의 자동 완성, 리팩토링, 코드 탐색 같은 기능을 이용할 수 있습니다.
  4. 다양한 백엔드 지원: Querydsl은 JPA, JDO, SQL, MongoDB, Lucene, Hibernate Search, Collections 등 다양한 저장소 기술을 지원합니다.
  5. 통합과 확장성: Spring Data JPA와 같은 다른 프레임워크와 잘 통합되어, 특히 Spring 프로젝트에서 많이 사용됩니다.

  6.2 구성 요소

  • Q-타입: 엔티티의 메타모델을 생성하여 사용합니다. 이 메타모델은 엔티티의 각 필드에 대한 접근을 제공하며, 이를 통해 쿼리를 구성합니다.
  • Querydsl JPAQuery: JPA 엔티티에 대한 쿼리를 작성할 때 사용됩니다.
  • Querydsl SQLQuery: 직접적인 SQL 데이터베이스 쿼리를 작성할 때 사용됩니다.
  • Querydsl Support Classes: Querydsl을 다른 데이터베이스 기술과 연동하기 위한 지원 클래스들을 포함합니다.

  6.3 사용 예제

  • 다음은 Querydsl을 사용하여 JPA 엔티티에 대해 쿼리를 작성하는 간단한 예제입니다.
QCustomer customer = QCustomer.customer;
JPAQuery<?> query = new JPAQuery<Void>(entityManager);
List<Customer> customers = query.select(customer)
                                .from(customer)
                                .where(customer.name.eq("John Doe"))
                                .fetch();

이 예제에서는 이름이 "John Doe"인 고객을 검색합니다. Querydsl은 이러한 쿼리 작성을 간단하고 타입 안전하게 만들어 주어, 복잡한 데이터베이스 작업을 보다 효과적으로 처리할 수 있도록 돕습니다.

댓글