- JPQL 사용 이유
- 다중행 조회를 위해
- 특정 트랜잭션 전에 INSERT가 종료되어야 하기에
@Test
public void TypedQuery를_이용한_단일행_단일열_조회_테스트() {
String jpql = "SELECT menuName FROM Menu WHERE menuCode = 6";
TypedQuery<String> query = em.createQuery(jpql, String.class);
String resultMenuName = query.getSingleResult();
assertEquals(resultMenuName, "생마늘샐러드");
}
@Test
public void Query를_이용한_단일행_단일열_조회_테스트() {
String jpql = "SELECT menuName FROM Menu WHERE menuCode = 6";
Query query = em.createQuery(jpql, String.class);
Object resultMenuName = query.getSingleResult();
assertEquals(resultMenuName, "생마늘샐러드");
}
@Test
public void TypedQuery를_이용한_다중행_다중열_조회_테스트() {
// Jpql 에서는 : entity의 별칭을 적으면 모든 속성(컬럼)을 조회
String jpql = "SELECT m FROM Menu as m";
TypedQuery<Menu> query = em.createQuery(jpql, Menu.class);
List<Menu> foundMenuList = query.getResultList();
assertTrue(!foundMenuList.isEmpty());
}
@Test
public void distinct를_활용한_중복제거_여러_행_조회_테스트() {
String jpql = "SELECT DISTINCT m.categoryCode FROM Menu m";
TypedQuery<Integer> query = em.createQuery(jpql, Integer.class);
List<Integer> categoryCodeList = query.getResultList();
assertTrue(!categoryCodeList.isEmpty());
}
@Test
public void in_연산자를_활용한_조회_테스트() {
String jpql = "SELECT m FROM Menu m WHERE m.categoryCode IN (6, 10)";
List<Menu> foundMenuList = em.createQuery(jpql, Menu.class).getResultList();
assertTrue(!foundMenuList.isEmpty());
foundMenuList.forEach(System.out::println);
}
@Test
public void like_연산자를_활용한_조회_테스트() {
String jpql = "SELECT m FROM Menu m WHERE m.menuName LIKE '%마늘%'";
List<Menu> foundMenuList = em.createQuery(jpql, Menu.class).getResultList();
assertTrue(!foundMenuList.isEmpty());
}
엔티티 프로젝션
임베디드 타입 프로젝션
스칼라 타입 프로젝션
new 명령어를 활용한 프로젝션
new 패키지명.DTO명을 쓰면 해당 DTO로 바로 반환Spring
Spring Data(Module)
...
순수 JPA에서의 persist() -> save(for Spring Data JPA)
Repository Interface <- CrudRepository Interface
CrudRepository Interface <- PagingAndSortingRepository Interface
PagingAndSortingRepository Interface <- JpaRepository Interface
// 마크형 인터페이스
@Indexed
public interface Repository<T, ID> {
// T: Entity Type
// ID: PK Type
}
public interface MenuRepository extends JpaRepository<Menu, Integer> {
List<Menu> findByMenuPriceGreaterThan(int menuPrice);
}
public List<MenuDTO> findMenuList() {
return menuRepository.findAll(Sort.by("menuCode").descending()) // sort type 지정
.stream()
.map(menu -> mapper.map(menu, MenuDTO.class))
.collect(Collectors.toList());
}public interface MenuRepository extends JpaRepository<Menu, Integer> {
} ...
// https://mvnrepository.com/artifact/org.modelmapper/modelmapper
implementation 'org.modelmapper:modelmapper:3.1.1'
...
@Configuration
public class AppConfig {
// 엔티티와 DTO 매핑을 위한 modelmapper 라이브러리 bean 객체 등록(DI 추가)
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
@Service
public class MenuService {
private final ModelMapper mapper;
private final MenuRepository menuRepository;
@Autowired
public MenuService(ModelMapper mapper, MenuRepository menuRepository) {
this.menuRepository = menuRepository;
this.mapper = mapper;
}
public MenuDTO findMenuByCode(int menuCode) {
Menu menu = menuRepository.findById(menuCode).orElseThrow(IllegalAccessError::new);
return mapper.map(menu, MenuDTO.class);
}
}
스크롤 압박 시, 추가 데이터는 클라이언트 요청에 따라 paging 처리
[common.Pagination.java]
public class Pagination {
public static PagingButtonInfo getPagingButtonInfo(Page page) {
int currentPage = page.getNumber() + 1; // 인덱스 개념 -> 실제 페이지 번호
int defaultButtonCount = 10; // 한 페이지에 보일 버튼 최대 갯수
int startPage; // 한 페이지에 보여질 첫 버튼
int endPage; // 한 페이지에 보여질 마지막 버튼
startPage = (int) (Math.ceil((double) currentPage/defaultButtonCount) -1) * defaultButtonCount + 1;
endPage = startPage + defaultButtonCount - 1;
// endPage에 대한 예외상황 처리
if (page.getTotalPages() < endPage) {
endPage = page.getTotalPages();
}
// 아예 메뉴가 없거나 10개도 안될 때
if (page.getTotalElements() < endPage) {
endPage = startPage;
}
return new PagingButtonInfo(currentPage, startPage, endPage);
}
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class PagingButtonInfo {
private int currentPage;
private int startPage;
private int endPage;
}
public class MenuController {
private final MenuService menuService;
@GetMapping("/list")
public String findMenuList(@PageableDefault Pageable pageable, Model model) {
Page<MenuDTO> menuDTOList = menuService.findMenuList(pageable);
log.debug("조회한 내용 목록: {}", menuDTOList.getContent());
log.debug("총 페이지 수: {}", menuDTOList.getTotalPages());
log.debug("총 메뉴 수: {}", menuDTOList.getTotalElements());
log.debug("해당 페이지에 표시될 요소 수: {}", menuDTOList.getSize());
log.debug("해당 페이지에 실제 요소 수: {}", menuDTOList.getNumberOfElements());
log.debug("첫 페이지 여부: {}", menuDTOList.isFirst());
log.debug("마지막 페이지 여부: {}", menuDTOList.isLast());
log.debug("정렬 방식: {}", menuDTOList.getSort());
log.debug("여러 페이지 중 현재 페이지 인덱스: {}", menuDTOList.getNumber());
// 설명. 화면에서 페이징 버튼을 그려내기 위해 필요한 재료 준비(모듈화(2개))
PagingButtonInfo paging = Pagination.getPagingButtonInfo(menuDTOList);
model.addAttribute("menuList", menuDTOList);
model.addAttribute("paging", paging);
return "menu/list";
}
}
public Page<MenuDTO> findMenuList(Pageable pageable) {
pageable = PageRequest.of(pageable.getPageNumber() <= 0 ? 0 : pageable.getPageNumber() - 1,
pageable.getPageSize(),
Sort.by("menuCode").descending());
Page<Menu> menuList = menuRepository.findAll(pageable);
return menuList.map(menu -> mapper.map(menu, MenuDTO.class));
}
...
Logger logger = LoggerFactory.getLogger(getClass());
Logger logger = LoggerFactory.getLogger(MenuController.class);
logger.debug("variable: {}", variable);
SpringBoot의 내장기능
LEVELS
println vs logger
Project 전체에서 설정 가능
use
[application.yml]
...
logging:
level:
com:
team:
package: debug
SLF4J (in Lombok)
@Slf4j
public class Class {
public String string = "hello"
log.debug("string: {}", string);
}