728x90
반응형
MockMvc
웹 어플리케이션을 어플리케이션 서버에 배포하지 않고 테스트용 MVC 환경을 만들어 요청 및 전송, 응답기능을
제공하는 유틸리티 클래스
Query DSL
- JPA를 좀 더 효율적으로 사용할 수 있는 라이브러리
- 오픈소스 프로젝트로 JPQL을 Java 코드로 작성할 수 있도록 함
- 정적 타입을 이용해서 SQL과 같은 쿼리를 생성해줌
@ConfigurationPropertiesScan
- @EnableConfigurationProperties를 이용해 설정 프로퍼티 클래스를 사용하는 경우 클래스가
많아지면 코드가 무거워지고 복잡해짐
✔ Repository
@RepositoryRestResource//내가 설정한 REST 기능을 부여함(기본적인 API 자동 생성)
public interface ArticleRepository extends
JpaRepository<Article, Long>,
QuerydslPredicateExecutor<Article>, // like검색이 아닌 전체단어 일치 검색
// 쿼리에 적용하고자 하는 엔티티, 기본기능 중 완벽하게 동일한 것만 구분 가능(like는 아님)
QuerydslBinderCustomizer<QArticle> { // like검색을 위해 오버라이드 필요함
//pageable : 한번에 가저올 수를 의미한다.
Page<Article> findByTitleContaining(String title, Pageable pageable);
//findByTitleContaining : 정화한 타이틀을 찾는것이 아닌 일부만으로 찾을 수 있다.
Page<Article> findByContentContaining(String title, Pageable pageable);
Page<Article> findByUserAccount_UserIdContaining(String userId, Pageable pageable);
// UserAccount와 같은 객체를 찾을 경우에는 _ Userid를 이용하여 찾는다.
Page<Article> findByUserAccount_NickNameContaining(String nickname, Pageable pageable);
Page<Article> findByHashtag(String hashtag, Pageable pageable);
@Override
default void customize(QuerydslBindings bindings, QArticle root){ //추상메소드 안에 적는 법 =default
bindings.excludeUnlistedProperties(true);
bindings.including(root.title, root.content, root.hashtag ,root.createdAt, root.createdBy);
bindings.bind(root.title).first(StringExpression::containsIgnoreCase); //like 검색이 된다.
bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
bindings.bind(root.hashtag).first(StringExpression::containsIgnoreCase);
bindings.bind(root.createdAt).first(DateTimeExpression::eq);
bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
}
}
findByTitleContaining : 타이틀 명칭을 찾을 때 토시하나 정확하게 입력하는 것이 아닌 해당 단어가 포함돼어 있으면 찾아주는 기능 - Containing
// 해당 관련된 엔티티가 아닌 다른 엔티티에서 찾는다면 findByUserAccount_userIdContaining 과 같이 언더바(_)를 사용한다.
✔ Dto
public record ArticleDto(
Long id,
UserAccountDto userAccountDto,
String title,
String content,
String hashtag,
String createdBy,
LocalDateTime createdAt,
String modifiedBy,
LocalDateTime modifiedAt
)
{
public static ArticleDto of( Long id, UserAccountDto userAccountDto
,String title, String content,
String hashtag, String createdBy,
LocalDateTime createdAt, String modifiedBy, LocalDateTime modifiedAt) {
return new ArticleDto( id, userAccountDto, title, content,
hashtag, createdBy, createdAt, modifiedBy, modifiedAt);
}
public static ArticleDto from(Article entity){
return new ArticleDto(
entity.getId(),
UserAccountDto.from(entity.getUserAccount()),// getUserAccountd에서 가저와서 UserAccountDto 적용
entity.getTitle(),
entity.getContent(),
entity.getHashtag(),
entity.getCreatedBy(),
entity.getCreatedAt(),
entity.getModifiedBy(),
entity.getModifiedAt()
);
}
public Article toEntity(){
return Article.of(
userAccountDto.toEntity(),
title,
content,
hashtag
);
}
}
레코드(record)
- 클래스의 특별한 종류
- DTO를 편리하게 생성하기 위한 클래스
- 자바 16부터 공식 기능이 되었음
- 열거타입과 마찬가지로 자바 클래스의 종류
- 다른 클래스를 상속받을 수 없음
- abstract로 선언할 수 없고 암시적으로 final로 선언됨
- 본문(body)에는 정적필드, 정적 메서드, 정적 이니셜라이저, 생성자, 인스턴스 메서드
증첩타입(클래스, 인터페이스, 열거형등)을 선언할 수 있음
✔ PageController
@RequiredArgsConstructor// 생성자 추가시 작성
@RequestMapping("articles")
@Controller
public class ArticleController {
private final ArticleService articleService;// 생성자
@GetMapping
public String articles(
@RequestParam(required =false)SearchType searchType,
@RequestParam(required =false)String searchValue,
@PageableDefault(size = 10, sort = "createdAt",direction = Sort.Direction.DESC)Pageable pageable, ModelMap map
){
map.addAttribute("articles", articleService
.searchArticles(searchType,searchValue,pageable).map(ArticleResponse::from));
return "articles/index"; // articles의 index 전달
}
@GetMapping("/{articleId}")
public String article(@PathVariable Long articleId, ModelMap map){
ArticleWithCommentResponse article = ArticleWithCommentResponse.from(articleService.getArticle(articleId));
map.addAttribute("article", article);
map.addAttribute("articleComments", List.of());
return "articles/detail";
}
}
✔ Config
package com.koreait.projectboard.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
// 직접 빈 설정파일에 빈을 등록하는 방법
@Configuration//컨피그로 적용을 하기 위해 사용, 이를 사용하기 위해서는 메인에서 @ConfigurationPropertiesScan 필요
// 위 페이지는 사용자가 직접 만드는 페이지가 아니기 때문에 괜찮음
// @Bean 을 수동으로 등록하고 있는 모습이다.
public class SecurityConfig {
@Bean // 하단 부 빈 주입하는 방법(SecurityConfig 설정 주입) -> 해당 사용자는 접근할 수 없는 보안 기능
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(auth -> auth.anyRequest().permitAll())
.formLogin().and()
.build();
}
}
// @Bean : 오래된 방식의 java 오브젝트, 속성으로는 class, id, scope, constructor-arg 가 있으며 ID 컨테이너에 의해 생성 및 관리된다.
// 해당 클래스를 직접 만드는 것이 아닌 가져다 쓰는 클래스인 경우에는 @Bean을 등록해 줘야한다. (+ 공유 기능, + 유지보수 용이)
// 자바에서는 new 연산자를 사용하여 객체를 생성하였다면, Spring 에서는 이를 Bean이라고 한다.
// Bean은 Spring의 ApplicationContext 가 담고있는 객체라고 생각하면 편하겠다.
// 자동 등록 방법
//@ComponentScan 어노테이션은 어느 지점부터 컴포넌트를 찾으라고 알려주는 역할
// @Component 어노테이션이 있는 클래스들을 찾아서 자동으로 빈 등록 (자동 등록)
✔ Thymleaf
❗ HTML
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Ryuzy">
<title>게시판 페이지</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<link href="/css/search-bar.css" rel="stylesheet">
</head>
<body>
<header id="header">
헤더 삽입
<hr>
</header>
<main class="container">
<div class="row">
<div class="card card-margin search-form">
<div class="card-body p-0">
<form id="card search-form">
<div class="row">
<div class="col-12">
<div class="row no-gutters">
<div class="col-lg-3 col-md-3 col-sm-12 p-0">
<label for="search-type" hidden>검색 유형</label>
<select class="form-control" id="search-type">
<option>제목</option>
<option>본문</option>
<option>id</option>
<option>닉네임</option>
<option>해시태그</option>
</select>
</div>
<div class="col-lg-8 col-md-6 col-sm-12 p-0">
<label for="search-value" hidden>검색어</label>
<input type="text" placeholder="검색어..." class="form-control" id="search-value" name="search-value">
</div>
<div class="col-lg-1 col-md-3 col-sm-12 p-0">
<button type="submit" class="btn btn-base">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<table class="table" id="article-table">
<thead>
<tr>
<th class="title col-6">제목</th>
<th class="hashtag col-2">해시태그</th>
<th class="user-id col">작성자</th>
<th class="created-at col">작성일</th>
</tr>
</thead>
<tbody>
<tr>
<td class ="title"><a>첫글</a></td>
<td class = "hashtag">#java</td>
<td calss = "user-id">김사과</td>
<td class = "created-at"><time>2023-01-03</time></td>
</tr>
<tr>
<td>두번째글</td>
<td>#spring</td>
<td>김사과</td>
<td>2023-01-03</td>
</tr>
<tr>
<td>세번째글</td>
<td>#java</td>
<td>김사과</td>
<td>2023-01-03</td>
</tr>
</tbody>
</table>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
</main>
<footer id="footer">
푸터 삽입
<hr>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>
❗ xml
<?xml version="1.0"?>
<thlogic>
<attr sel="#header" th:replace="header :: header"/>
<attr sel="#footer" th:replace="footer :: footer"/>
<attr sel="#article-table">
<attr sel="tbody" th:remove="all-but-first">
<attr sel="tr[0]" th:each="article : ${articles}">
<attr sel="td.title/a" th:text="${article.title}" th:href="@{'/articles/'+${article.id}}"/>
<attr sel="td.hashtag" th:text="${article.hashtag}"/>
<attr sel="td.user-id" th:text="${article.nickname}"/>
<attr sel="td.created-at/time" th:datetime="${article.createdAt}" th:text="${#temporals.format(article.createdAt, 'yyyy-MM-dd')}"/>
<!--temporals 시간포멧 설정 하는 기능-->
</attr>
</attr>
</attr>
</thlogic>
❗ 출력화면 - SQL 데이터를 활용하여 출력
반응형
'Spring' 카테고리의 다른 글
Spring Security 기본(세션, 스프링시큐리티) (0) | 2023.03.13 |
---|---|
Spring 시험 (7번), Thymleaf 이용 리스트 출력 (1) | 2023.01.09 |
Spring Project Board(게시판) 1 (0) | 2023.01.02 |
Spring 유용한 Setting, 사이트 (0) | 2022.12.29 |
Spring 타임리프 이용 기본 (0) | 2022.12.19 |