개발로드

☆KDT 2024-04-16★SpringBoot-쇼핑몰(2) 본문

JAVA

☆KDT 2024-04-16★SpringBoot-쇼핑몰(2)

위대한개발자 2024. 4. 17. 09:04

JPQL


JPA에서 제공하는 @Query 어노테이션을 이용하면 SQL과 유사한 JPQL이라는 개체지향 쿼리도 처리가 가능합니다.

SQL과 문법이 유사한 특징이있습니다.

JPQL은 테이블이 아닌, 엔티티 객체를 대상으로 검색하는 객체지향 쿼리입니다.

JPQL은 SQL을 추상화해서 사용하기 때문에 특정 데이터베이스에 의존하지 않습니다.

 

@Query("select i from Item i where i.itemDetail like %:itemDetail% order by i.price desc")
List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);

 

Item 엔티티에 접근하여 "itemDetail"을 포함하고있는 상품상세설명을 가격을 기준으로 내림차순으로 검색합니다.

 

테스트 코드

@Test
@DisplayName("@Query를 이용한 상품 조회 테스트")
public void findByItemDetailTest(){
    this.createItemList();
    List<Item> itemList = itemRepository.findByItemDetail("상품");
    for(Item item:itemList){
        System.out.println(item.toString());
    }//end of for
}//end of method

 

실행화면

 

Querydsl


Querydsl은 JPQL을 코드로 작성할 수 있도록 도와주는 빌더 API로 소스코드로 작성하기 때문에 컴파일러의 도움을 받아 소스작성시에 오타를 바로 확인 할 수 있고, 동적으로 쿼리를 생성해주는 장점이 있습니다.

 

테스트코드

@Test
@DisplayName("Querydsl 조회테스트1")
public void queryDslTest(){

    this.createItemList();
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    QItem qItem = QItem.item;
    JPAQuery<Item> query = queryFactory.selectFrom(qItem)
            .where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
            .where(qItem.itemDetail.like("%"+"테스트"+"%"))
            .orderBy(qItem.price.desc());

    List<Item> itemList = query.fetch();
    for (Item item:itemList){
        System.out.println(item.toString());
    }//end of for
}//end of method

 

실행화면

 

 

 

테스트코드

@Test
@DisplayName("상품 Querydsl 조회 테스트2")
public void queryDslTest2(){
    this.createItemList2();

    BooleanBuilder booleanBuilder = new BooleanBuilder();
    QItem item = QItem.item;

    String itemDetail = "상세 설명";
    int price = 10003;
    String itemSellStat = "SELL";

    booleanBuilder.and(item.itemDetail.like("%"+itemDetail+"%"));
    booleanBuilder.and(item.price.gt(price));

    if (StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){
        booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));
    }//end of if

    Pageable pageable = PageRequest.of(0,5);
    Page<Item> itemPagingResult =
            itemRepository.findAll(booleanBuilder, pageable);
    System.out.println("total elements : " + itemPagingResult.getTotalElements());

    List<Item> resultItemList = itemPagingResult.getContent();
    for (Item resultItem:resultItemList){
        System.out.println(resultItem.toString());
    }//end of for

}//end of method

 

 

실행화면

 

booleanBuilder.and(item.itemDetail.like("%"+itemDetail+"%"));//"상세 설명" 이 제품 상세 설명에 포함된 요소
booleanBuilder.and(item.price.gt(price));//제품 가격이 10003보다 높은 요소

 

booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));//제품 판매상태가 "SELL"인 요소

 

Pageable pageable = PageRequest.of(0,5);//첫 번째 페이지를 요청하고, 해당 페이지에서 최대 5개의 항목을 반환하도록 페이지네이션 정보를 설정하는 것입니다.

 

Page<Item> itemPagingResult =
        itemRepository.findAll(booleanBuilder, pageable);
//메서드는 JpaRepository 인터페이스에 정의된 메서드 중 하나입니다. 두 개의 매개변수를 가지고 있습니다. 첫 번째 매개변수는 Querydsl 의
// BooleanBuilder 를 사용하여 동적으로 생성된 쿼리 조건을 전달하고, 두 번째 매개변수는 페이지네이션 정보를 나타내는 Pageable 객체를 전달합니다.

 

 

ItemDto.java 생성

@Getter
@Setter
public class ItemDto {

    private Long id;
    private String itemNm;
    private Integer price;
    private String itemDetail;
    private String sellStatCd;
    private LocalDateTime regTime;
    private LocalDateTime updateTime;

}//end of class

 

데이터를 주고 받을 때는Entity 클래스 자체를 반환하면 안 되고 데이터 전달용 객체(Data Transfer Object)를 생성해서 사용해야 합니다. 데이터베이스 설계를 외부에 노출할 필요도 없으며, 요청과 응답 객체가 항상 엔티티와 같지 않기 때문입니다.

 

MemberFormDto.java 생성

package com.kwj.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MemberFormDto {

    private String name;
    private String email;
    private String password;
    private String address;

}//end of class

 

Member entity 생성

package com.kwj.entity;

import com.kwj.constant.Role;
import com.kwj.dto.MemberFormDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.crypto.password.PasswordEncoder;

@Entity
@Table(name = "member")
@Getter
@Setter
@ToString
public class Member {

    @Id
    @Column(name = "member_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    @Column(unique = true)
    private String email;
    private String password;
    private String address;
    @Enumerated(EnumType.STRING)
    private Role role;

    public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){
        Member member = new Member();
        member.setName(memberFormDto.getName());
        member.setEmail(memberFormDto.getEmail());
        member.setAddress(memberFormDto.getAddress());
        String password = passwordEncoder.encode(memberFormDto.getPassword());
        member.setPassword(password);
        member.setRole(Role.USER);
        return member;
    }//end of method


}//end of class

 

Member 엔티티에서  직접 member 객체를 생성하면, 코드변경시 바로 수정할 수 있어 관리가 편합니다.

 

MemberRepository 생성

package com.kwj.repository;

import com.kwj.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByEmail(String email);//회원 가입시 중복된 회원이 있는지 검사하기 위해서 이메일로 쿼리 메소드 작성

}//end of interface

 

MemberService 생성

package com.kwj.service;

import com.kwj.entity.Member;
import com.kwj.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    public Member saveMember(Member member){
        validateDuplicateMember(member);
        return memberRepository.save(member);
    }//end of method

    private void validateDuplicateMember(Member member){
        Member findMember = memberRepository.findByEmail(member.getEmail());
        if(findMember != null){
            throw new IllegalStateException("이미 가입된 회원입니다");
        }//end of if
    }//end of method

}//end of class

 

validateDuplicateMember 메소드를 작성하여 이미 가입된 이메일이 있을경우 예외를 발생시킵니다.

 

 

회원가입 및 중복회원 가입 테스트

package com.kwj.repository;

import com.kwj.dto.MemberFormDto;
import com.kwj.entity.Member;
import com.kwj.service.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
class MemberServiceTest {

    @Autowired
    MemberService memberService;

    @Autowired
    PasswordEncoder passwordEncoder;

    public Member createMember(){
        MemberFormDto dto = new MemberFormDto();
        dto.setEmail("test@test.com");
        dto.setName("테스트회원");
        dto.setAddress("김포시 장기동");
        dto.setPassword("1234");
        return Member.createMember(dto, passwordEncoder);
    }//end of method

    @Test
    @DisplayName("회원가입 테스트")
    public void saveMemberTest(){

        Member member = createMember();
        Member savedMember = memberService.saveMember(member);

        assertEquals(member.getEmail(), savedMember.getEmail());
        assertEquals(member.getName(), savedMember.getName());
        assertEquals(member.getAddress(), savedMember.getAddress());
        assertEquals(member.getPassword(), savedMember.getPassword());
        assertEquals(member.getRole(), savedMember.getRole());

        System.out.println(member.toString());
        System.out.println(savedMember.toString());
    }//end of method

    @Test
    @DisplayName("중복회원가입 테스트")
    public void saveDuplicateMemberTest(){
        Member member1 = createMember();
        Member member2 = createMember();
        memberService.saveMember(member1);

        Throwable e = assertThrows(IllegalStateException.class, () ->{
            memberService.saveMember(member2);
        });

        assertEquals("이미 가입된 회원입니다",e.getMessage());
        System.out.println(e.getMessage());
    }//end of method
}//end of class