실무에서는 Logging이 기본이다.
(낮은 레벨)
✅ DEBUG → 개발 중
✅ INFO → 시스템 상태 모니터링
✅ WARN → 잠재적 위험 요소
✅ ERROR → 오류 발생 (빠른 조치 필요)
✅ FATAL (SEVERE) → 심각한 장애 (즉각 대응 필요)
(높은 레벨)
✅ OFF - 로그 출력 없음
BoardController 클래스 상단에 logger 객체 생성
private Logger logger
= LoggerFactory.getLogger(BoardController.class);
saved 객체를 logger로 기록 남기기
logger.debug("1 : "+saved.toString());
logger.info("2 : "+saved.toString());
logger.warn("3 : "+saved.toString());
logger.error("4 : "+saved.toString());
application.properties에 코드 추가
logging.level.org.hibernate.SQL=debug
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.orm.jdbc.bind=trace
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime regDate;
@UpdateTimestamp
@Column(insertable = false)
private LocalDateTime modDate;
Board(엔티티)
@ManyToOne
@JoinColumn(name="board_writer")
private Member member;
Member(엔티티)
@OneToMany(mappedBy = "member")
private List<Board> boards;
board/list.html에 작성자 정보 추가
<td th:text="${board.member.memberName}">작성자</td>
<!-- 검색 -->
<form action="/board" name="search_board_form" method="get" >
<div class="search">
<select name="search_type">
<option value="1" th:selected="${searchDto.search_type == 1}">제목</option>
<option value="2" th:selected="${searchDto.search_type == 2}">내용</option>
<option value="3" th:selected="${searchDto.search_type == 3}">제목+내용</option>
</select>
<input type="text" name="search_text" placeholder="검색어를 입력하세요." th:value="${searchDto.search_text}">
<input type="submit" value="검색">
</div>
</form>
public class SearchDto {
private int search_type;
private String search_text;
}
@GetMapping("/board")
public String selectBoardAll(Model model, SearchDto searchDto) {
List<Board> resultList = boardService.selectBoardAll(searchDto);
model.addAttribute("boardList", resultList);
return "board/list";
}
키워드
표현법
@Query(value = "SELECT 별칭 FROM 엔티티명 별칭 WHERE 별칭.필드명 = ?1")
리턴 메소드명(매개변수);
주의사항 : ?1 과 :keyword 두 가지 방법을 혼용해서 사용하면 오류 가능성이 높다.
동작 흐름
1. 검색 조건을 규정하는 메소드가 있는 클래스 생성
2. Repository에 JpaSpecificationExecutor 인터페이스 Implements
3. 검색 조건 메소드의 클래스 사용
1. BoardRepository에 커스텀 메소드 추가
package com.gn.mvc.repository;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import com.gn.mvc.entity.Board;
public interface BoardRepository extends JpaRepository<Board, Long>, JpaSpecificationExecutor<Board> {
// 3. Specification 사용 / 오버로딩 했다~ 이걸 서비스의 selectBoardAll 에서 썼다.
List<Board> findAll(Specification<Board> spec);
// 1. 메소드 네이밍. findBy + 필드명 + 키워드 형태
// List<Board> findByBoardTitleContaining(String keyword);
// List<Board> findByBoardContentContaining(String keyword);
// List<Board> findByBoardTitleContainingOrBoardContentContaining(String titleKeyword, String contentkeyword);
// 2. JPQL 이용
// 엔티티를 기준으로 하는 거라서 * 대신 별칭 정한 b라고 한다.
@Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%')") // :을 쓰는 경우 매개변수 이름 맞춰줘야한다.
// @Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%')")
List<Board> findByTitleLike(String keyword);
@Query(value="SELECT b FROM Board b WHERE b.boardContent LIKE CONCAT('%',?1,'%')")
List<Board> findByContentLike(String keyword01);
@Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%') OR b.boardContent LIKE CONCAT('%',?2,'%')")
List<Board> findByTitleOrContentLike(String title, String content);
}
2. BoardService에 상황별 메소드 셋팅
package com.gn.mvc.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import com.gn.mvc.dto.BoardDto;
import com.gn.mvc.dto.SearchDto;
import com.gn.mvc.entity.Board;
import com.gn.mvc.repository.BoardRepository;
import com.gn.mvc.specification.BoardSpecification;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class BoardService {
// 롬복이 대신 해줌! @RequiredArgsConstructor
// @Autowired
// BoardRepository repository;
// 롬복이 대신 해줌! @RequiredArgsConstructor
private final BoardRepository repository;
// 어노테이션 뿐만 아니라 JpaRepository를 상속받은 Interface들은 Bean 스캐닝 없이 사용 가능
public BoardDto createBoard(BoardDto dto) {
// 1. 매개변수 dto -> entity
Board param = dto.toEntity();
// 2. Repository의 save() 메소드 호출(insert 쿼리 실행)
Board result = repository.save(param);
// 3. 결과 entity -> dto로 바꿔서 return
return new BoardDto().toDto(result);
}
public List<Board> selectBoardAll(SearchDto searchDto){
// 1,2 방법
// List<Board> list = new ArrayList<Board>();
// if(searchDto.getSearch_type() == 1) {
// // 제목 기준 검색
// list = repository.findByTitleLike(searchDto.getSearch_text());
// } else if(searchDto.getSearch_type() == 2) {
// // 내용 기준 검색
// list = repository.findByContentLike(searchDto.getSearch_text());
//
// } else if(searchDto.getSearch_type() == 3) {
// // 제목+내용 기준 검색
// list = repository.findByTitleOrContentLike(searchDto.getSearch_text(),searchDto.getSearch_text());
//
// } else {
// // WHERE절 없이 검색(처음 진힙 했을 때)
// list = repository.findAll();
// }
// return list;
// 3 방법
Specification<Board> spec = (root, query, criteriaBuilder) -> null;
if(searchDto.getSearch_type() == 1) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()));
} else if(searchDto.getSearch_type() == 2) {
spec = spec.and(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
} else if(searchDto.getSearch_type() == 3) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()))
.or(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
}
List<Board> list = repository.findAll(spec);
return list;
}
}
3. list.html 검색창에 데이터 반영
package com.gn.mvc.repository;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import com.gn.mvc.entity.Board;
public interface BoardRepository extends JpaRepository<Board, Long>, JpaSpecificationExecutor<Board> {
// 3. Specification 사용 / 오버로딩 했다~ 이걸 서비스의 selectBoardAll 에서 썼다.
List<Board> findAll(Specification<Board> spec);
// 1. 메소드 네이밍. findBy + 필드명 + 키워드 형태
// List<Board> findByBoardTitleContaining(String keyword);
// List<Board> findByBoardContentContaining(String keyword);
// List<Board> findByBoardTitleContainingOrBoardContentContaining(String titleKeyword, String contentkeyword);
// 2. JPQL 이용
// 엔티티를 기준으로 하는 거라서 * 대신 별칭 정한 b라고 한다.
@Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%')") // :을 쓰는 경우 매개변수 이름 맞춰줘야한다.
// @Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%')")
List<Board> findByTitleLike(String keyword);
@Query(value="SELECT b FROM Board b WHERE b.boardContent LIKE CONCAT('%',?1,'%')")
List<Board> findByContentLike(String keyword01);
@Query(value="SELECT b FROM Board b WHERE b.boardTitle LIKE CONCAT('%',?1,'%') OR b.boardContent LIKE CONCAT('%',?2,'%')")
List<Board> findByTitleOrContentLike(String title, String content);
}
BoardSpecification 클래스 생성
package com.gn.mvc.specification;
import org.springframework.data.jpa.domain.Specification;
import com.gn.mvc.entity.Board;
// 3번 방법 Specification 사용 / BoardService와 같이 보자.
public class BoardSpecification {
// 제목에 특정 문자열이 포함된 검색 조건
public static Specification<Board> boardTitleContains(String keyword){
// root = Board 엔티티
// query = 쿼리문
// criteriaBuilder = 쿼리 조건(=like)
return (root, query, criteriaBuilder) ->
criteriaBuilder.like(root.get("boardTitle"), "%"+keyword+"%");
}
// 내용에 특정 문자열이 포함된 검색 조건
public static Specification<Board> boardContentContains(String keyword){
return (root, query, criteriaBuilder) ->
criteriaBuilder.like(root.get("boardContent"), "%"+keyword+"%");
}
}
참고