게시판 만들기 프로젝트. Java + Springboot와 관련 기술들을 공부한다.
- Intellij IDEA Ultimate 2023.1.5 - 2023.2
- Java 17
- Gradle 7.6.1
- Spring Boot 2.7.13
- Spring Boot Actuator
- Spring Web
- Spring Data JPA
- Rest Repositories
- Rest Repositories HAL Explorer
- Thymeleaf
- Spring Security
- H2 Database
- MySQL Driver
- Lombok
- Spring Boot DevTools
- Spring Configuration Processor
- QueryDSL 5.0.0
- Bootstrap 5.3.1
-
API Endpoint : https://docs.google.com/spreadsheets/d/15-0zZ_vmxEgqW62uNUNb9chC4As2JHcaNMpm9dv-5qg/edit?usp=sharing
-
Thymeleaf3 decoupled template logic :
-
start.spring.io 사이트를 이용하여 Thymeleaf dependency 추가시 주의사항 :
- thymeleaf-extras-springsecurity6 디펜던시는 Spring Security와 Thymeleaf를 동시에 선택해야 목록에 나타난다.
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'- Spring Security의 기본 form login 기능을 이용하면
DefaultLoginPageGeneratingFilter클래스에서 화면을 만들어준다. - Spring boot와 Spring Security를 연동하여 사용할 때는
@EnableWebSecurity애너테이션은 생략해도 된다. Auto-Configuration에 포함되어 있다. - SecurityConfig의 설정을 Test에도 적용하려면
@Import(SecurityConfig.class)애너테이션을 클래스 레벨로 붙여준다.- 기본적으로
@WebMvcTest애너테이션을 붙인 테스트는 Spring Security와 MockMvc도 자동으로 구성한다. - reference : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html
- 기본적으로
@DisplayName("View 컨트롤러 - 게시글")
@Import(SecurityConfig.class)
@WebMvcTest(ArticleController.class)
class ArticleControllerTest {
...
}- enum은 자바 클래스의 예약어라 팩키지명으로 사용하면 어떤 문제를 일으킬지 예측할 수 없다. Java 예약어는 팩키지명이나 변수명으로 사용하지 말자. IntelliJ에서는 예약어를 팩키지명으로 사용하면 미리 경고를 해준다.
@GetMapping에 여러 주소의 다중 매핑이 가능하다.value = { "/urlA", "/urlB" }의 형태로 작성해준다.
@GetMapping(value = {"", "/"})@WebMvcTest()어노테이션의 ( ) 내부에 테스트 할 대상을 명시해 줌으로써 Bean scanning을 최소화 할 수 있다.
@WebMvcTest(MainController.class)- MySQL에서 FK 제약조건이 걸려 있는 table은 일반적인 방법으로 drop 할 수 없기 때문에
set FOREIGN_KEY_CHECKS = 0;으로 제약 조건에 관계없이 table을 drop 할 수 있도록 설정하고, table drop 후 다시set FOREIGN_KEY_CHECKS = 1;로 원상복구 해준다.
set FOREIGN_KEY_CHECKS = 0;
drop table [table_name];
set FOREIGN_KEY_CHECKS = 1;- underscore(_)는 JPA Property Expressions에서 property 탐색을 위한 traversal points(순회 지점)을 수동으로 설정하는 예약어로 일반적인 경우에 사용해서는 안 된다. 아래와 같이 댓글 목록을 가져오는데 게시글의 id를 조회 조건으로 이용하거나, Pserson 객체의 목록을 조회하는데 Adderss 객체의 ZipCode property를 조회 조건으로 사용하는 등 property가 계층적(트리 구조) 연관관계를 가질 때 사용할 수 있다.
- 공식 문서에서는 Property Expressions에 스네이크 표기법이 아닌 자바에서 사용하는 카멜표기법을 사용할 것을 추천하고 있다.
- reference : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions
List<Person> findByAddress_ZipCode(ZipCode zipCode);
List<ArticleComment> findByArticle_Id(Long articleId);Pageinterface의map메서드를 이용하여 DB에서 가져온 Entity Object를 간편하게 Dto로 변환할 수 있다.
return articleRepository.findAll(pageable).map(ArticleDto::from);- JPA method name에
Containing을 붙이면 부분 검색이 가능하다. 하지만 like'%[keyword]%'와 같이 % wild card가 앞뒤로 붙는 형태이므로 인덱스를 사용하지 못하여 성능 관련 이슈가 발생할 수 있다.
Page<Article> findByContentContaining(String content, Pageable pageable);-
보통 연관 관계에 있는 엔티티를 저장하기 위해
findById()메서드를 사용하여 Entity를 불러오는데, 이는 불필요한 select문이 실행되는 결과를 낳는다. 이를 개선하기 위해getReferenceById()메서드를 사용하면 엔티티 생성에 프록시 객체를 넣어 주기 때문에 블필요한 select문이 실행되지 않는다. -
주의사항:
findById()메서드는 Optional로 반환되기 때문에 예외 처리에 유리하지만,getReferenceById()는 프록시 객체를 가져오기 때문에 예외 발생 위험이 있다. -
getOne()메서드는 Spring Boot 2.7에서 deprecated 되었고, 현재는getReferenceById()메서드 사용을 권장하고 있다. -
reference : https://docs.spring.io/spring-data/jpa/docs/current/api/deprecated-list.html
Article article = articleRepository.getReferenceById(dto.id());@MockBean애너테이션은 필드 주입만 가능하고 생성자 주입은 불가능하다.@AutoWired애너테이션이 METHOD, PARAMETER 에서 사용할 수 있도록 구현이 되어 있는 반면,@MockBean애너테이션은 TYPE, FIELD에서 사용할 수 있도록 구현이 되어 있다.
// AutoWired 애너테이션
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
// MockBean 애너테이션
@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(MockBeans.class)
public @interface MockBean {- Mockito의
any()메서드는 일종의 argument matcher로서 filed의 일부만 matrcher 할 수 없다. match 대상 field에eq()를 사용하여 모든 method field가 argument matcher로 이루어져 있어야 에러가 나지 않는다.
given(articleService.searchArticles(eq(null), eq(null), any(Pageable.class))).willReturn(Page.empty());