-
사이드 프로젝트를 하며 알게된 내용Project 2023. 1. 19. 01:15
Spring Boot, JPA 를 사용한 사이드 프로젝트를 진행하며 새로 알게된 개념 혹은 헷갈리는 내용들을 정리해보았다.
1. EqualsAndHashCode (of="id")
연관 관계가 복잡해질 때, @EqualsAndHashCode에서 서로 다른 연관 관계를 순환 참조 하느라 무한 루프가 발생하고, 결국 stack overflow가 발생할 수 있기 때문에 주로 id 값만 비교하도록 사용한다.
https://jinniedev.tistory.com/2
2. 이미지도 Spring Security에 걸린다 (403 오류).
Static resource 들은 Security Filter를 적용하지 않도록 설정해야 한다.
https://jinniedev.tistory.com/3
3. 유효성을 처리할 때 프론트엔드, 백엔드에서 모두 validation을 걸어야 한다.
- 프론트엔드에서만 유효성 검사를 하면, 개발자 도구를 통해 데이터를 변조할 수 있다.
- 백엔드에서만 유효성 검사를 하면, 프론트엔드에서 쉽게 처리할 수 있는 부분을 매번 서버에 질의를 던지고 받는 것은 비효율적이다.
4. @RequiredArgsConstructor를 추가하면, private final 타입 멤버의 생성자를 만들어 준다.
의존성을 주입해주기 위해 생성자, Setter, Field 타입의 방식을 사용해야 했다. @RequiredArgsConstructor 어노테이션을 사용하면 간단하게 생성자 주입을 할 수 있다.
@RequiredArgsConstructor는 final @NotNull이 붙은 필드의 생성자를 자동으로 만들어준다.
5. 패스워드를 평문으로 저장해서는 안된다.
- 해당 서비스의 관리자가 DB에서 모든 사용자의 비밀번호를 볼 수 있으며 DB가 노출된 경우 공격자에게 정보가 넘어갈 수 있다.
- id와 password 를 동일하게 여러 곳에서 쓰는 경우가 많아서 추가적인 피해가 발생할 수 있다.
https://jinniedev.tistory.com/5
6. 테스트 코드 작성 시에 MockMvc는 JUnit이 생성자 주입을 막는다. => @Autowired로 주입해야 한다.
테스트 코드가 아닌 일반 클래스 작성 시에 @RequiredArgsConstructor를 통해 쉽게 의존성을 주입하였다.
하지만, 테스트 코드에서는 @RequiredArgsCosntructor를 통한 의존성 주입이 불가능하다.
원인
- Junit5 로 테스트를 수행하게되면 생성자 매개변수 관리는 스프링 컨테이너가 아닌 테스트 프레임워크인 Junit5 가 스스로 지원하게되고, DI를 지원하는 타입이 지정되어 있다.
- lombok 방식으로 DI 를 지정하더라도 Junit 이 먼저개입하게 되어 에러가 발생한다.
테스트 코드
@SpringBootTest public class TestCode { @Autowired TestBean bean; }
일반 코드
@Controller @RequiredArgsConstructor public class TestController { private final TestService; private final TestRepository; }
7. Open EntityManager In View 필터
Open EntityManager In View 필터는 JPA EntityManager(영속성 컨텍스트: DB에서 읽어온 객체들을 관리하는 컨텍스트. persistent 상태인 것을 관리)를 요청을 처리하는 전체 프로세스에 바인딩 시켜주는 필더이다.
스프링부트는 기본적으로 Open EntityManager In View를 설정해준다.
persistent 상태인 객체들은 트랜잭션 안에서는 객체 상태의 변경만 감지하다가 트랜잭션이 종료될 때에 DB에 반영한다.
그러므로 데이터를 변경해야하는 일이 있는 경우, 트랜잭션 내에서 관리해주어야 한다.
뷰를 랜더링 할 때 까지 영속성 컨텍스트를 유지하기 때문에 필요한 데이터를 랜더링하는 시점에 추가로 읽어올 수 있다. (지연 로딩. Lazy Loading)
엔티티 객체 변경은 반드시 트랜잭션 안에서 해야 한다. 그래야 트랜잭션 종료 직전 또는 필요한 시점에 변경 사항을 DB에 반영할 수 있다.
repository에는 기본적으로 @Transactional이 적용되어 있다.
문제 상황: 컨트롤러에서 데이터를 변경했더니 DB에 반영이 되지 않았다.
원인: 트랜잭션 범위 밖에서 일어난 일이기 때문
해결: 데이터 변경은 서비스 계층으로 위임해서 트랜잭션 안에서 처리한다. (서비스 계층은 @Transactional 을 직접 설정해주어야 한다.)
팁: 데이터 변경은 서비스 계층으로 위임해서 트랜잭션 안에서 처리하고, 데이터 조회는 레파지토리 혹은 서비스를 이용하는게 좋다.
8. modelMapper - 업데이트 예정
전
public void updateProfile(User user, Profile profile) { user.setBio(profile.getBio()); user.setLocation(profile.getLocation()); user.setProfileImage(profile.getProfileImage()); userRepository.save(user); }
후
public void updateProfile(User user, Profile profile) { modelMapper.map(profile, user); userRepository.save(user); }
9. Setter 사용을 지양해야 한다.
setter를 사용하면 값을 변경한 의도를 파악하기가 어렵고 객체의 일관성을 유지하기가 어렵기 때문에 사용을 지양해야 한다.
https://jinniedev.tistory.com/6
10. Entity와 DTO를 분리하여 사용하여야 한다.
DB에 직접 접근하는 Entity 컬럼들의 값을 setter를 통해 변경이 가능하게 만들면 안된다.
데이터 교환이 이루어질 수 있도록 하는 객체인 DTO를 별도로 만들어 사용하여야 한다.
https://jinniedev.tistory.com/10