2024-04-15-TIL
2024-04-15-TIL
Today I Learned
The Method in Controller Must Be Public?
Spring MVC는 @RequestMapping 계열 메서드를 리플렉션으로 찾아 호출하므로, 접근 제어자가 public이 아니어도 핸들러로 등록되는 경우가 있다. 그런데 Stack Overflow 답변들은 컨트롤러 메서드를 public으로 두라고 권한다. 이유는 프록시다.
- 컨트롤러에
@Transactional,@PreAuthorize, 다른 AOP 어드바이스가 걸리면 Spring은 CGLIB으로 컨트롤러의 서브클래스 프록시를 만든다.private메서드는 오버라이드할 수 없어서 프록시가 가로채지 못하고, 호출이 실제 빈이 아닌 프록시 인스턴스에서 실행된다. 프록시 인스턴스의 필드는 주입되지 않은 상태라,private핸들러 안에서 주입받은 서비스가null로 보이는 문제가 생긴다. - 컨트롤러 메서드는 HTTP를 통해 외부에서 호출되는 진입점이므로
public이 의도와도 맞는다.protected는 오버라이드를 의도한 것처럼 읽힌다는 의견도 있다.
결론적으로 핸들러 메서드는 public으로 둔다. 핸들러 메서드의 인자가 어떻게 정해지는지는 세 번째 글이 다룬다. Spring MVC에서 이 역할은 HandlerMethodArgumentResolver 구현들이 맡는다.
@EmbeddedId vs @IdClass
JPA에서 복합 키를 매핑하는 방법은 두 가지다.
@IdClass: 엔티티에@Id필드를 여러 개 두고, 같은 이름과 타입의 필드를 가진 별도 키 클래스를 지정한다. 엔티티 필드에 바로 접근할 수 있어 쿼리가 단순하다.@EmbeddedId:@Embeddable키 클래스를 엔티티의 필드 하나로 넣는다. 키가 하나의 객체로 묶여 의미가 분명하고 재사용하기 쉽지만, JPQL에서e.id.userId처럼 한 단계 더 들어가야 한다.
어느 쪽이든 키 클래스는 Serializable을 구현하고, 기본 생성자와 equals, hashCode를 갖춰야 한다. 복합 키의 일부가 연관 엔티티의 키라면 @MapsId로 연관 관계와 키 필드를 연결한다.
Spring Data JPA에서 복합 키처럼 식별자를 직접 할당하면 주의할 점이 있다. SimpleJpaRepository.save는 엔티티가 새것인지(isNew)를 식별자가 null인지로 판단하는데, 직접 할당한 키는 처음부터 null이 아니므로 persist 대신 merge가 호출되고, 그 과정에서 불필요한 SELECT가 먼저 실행된다. 엔티티가 Persistable을 구현해 isNew를 직접 정의하면 이를 피할 수 있다.
LazyInitializationException
FetchType.LAZY가 기본적으로 권장되고 특정 필드를 조인으로 엮어서 가져오는게 빈번한 경우에는 해당 레파지토리의 메서드에 @EntityGraph를 지정해주면 된다.
LazyInitializationException은 지연 로딩 대상(프록시나 컬렉션)을 영속성 컨텍스트가 닫힌 뒤에 처음 접근할 때 발생한다. 지연 로딩은 실제 접근 시점에 DB를 조회하는데, 트랜잭션이 끝나 세션이 없으면 조회할 방법이 없기 때문이다. 보통 서비스 계층의 트랜잭션이 끝난 뒤 컨트롤러나 직렬화 과정에서 연관 엔티티를 건드릴 때 나타난다.
해결 방법은 필요한 데이터를 트랜잭션 안에서 미리 가져오는 것이다.
- JPQL
join fetch나@EntityGraph(attributePaths = ...)로 연관 엔티티를 함께 조회한다. - 엔티티 대신 필요한 필드만 담은 DTO로 조회해 반환한다.
- 트랜잭션 범위 안에서 연관 객체를 초기화한다.
spring.jpa.open-in-view로 요청 끝까지 영속성 컨텍스트를 열어 두는 방법도 있지만, DB 커넥션을 오래 잡고 뷰 계층에서 의도치 않은 쿼리가 나가므로 주의해서 쓴다.
1
2
@EntityGraph(attributePaths = {"members"})
Optional<Team> findWithMembersById(Long id);
@SneakyThrows
throws 나 try-catch 구문을 통해서 Exception에 대해 명시적으로 예외 처리를 해줘야 하는 경우에 @SneakyThrows 어노테이션을 사용하여 명시적인 예외 처리를 생략할 수 있다.
하지만 대중적이지 않으면서 애너테이션은 협업의 관점에서 자제하는게 좋다는 생각이고, 핸들링되지 않는 예외의 범위를 넓히는 방향이므로 사용하지 않는게 좋겠다.
동작 원리는 checked exception이 컴파일러만의 규칙이라는 데 있다. JVM 바이트코드 수준에서는 메서드 시그니처와 관계없이 어떤 예외든 던질 수 있고, Lombok은 이 점을 이용해 throws 선언 없이 checked exception을 그대로 던지는 코드를 생성한다. 감싸서 RuntimeException으로 바꾸는 것이 아니라 원래 예외가 그대로 전파된다.
Lombok 문서가 드는 적절한 사용처는 두 가지다. Runnable처럼 인터페이스가 checked exception을 선언할 수 없게 막혀 있는 경우, 그리고 UTF-8 인코딩의 UnsupportedEncodingException처럼 실제로는 일어날 수 없는 예외다. 주의할 점도 문서에 있다. 호출하는 쪽에서는 해당 checked exception을 직접 catch할 수 없다. 컴파일러가 선언되지 않은 checked exception의 catch 절을 허용하지 않기 때문이다. 위에서 정리한 생각처럼, 예외 흐름이 시그니처에서 보이지 않게 된다는 것이 가장 큰 단점이다.
Moving a File or Directory in Java
Java NIO의 Files.move(source, target, options...)로 파일이나 디렉터리를 옮긴다. 대상이 이미 있으면 기본적으로 예외가 나므로 덮어쓰려면 REPLACE_EXISTING을 준다. ATOMIC_MOVE는 원자적인 파일 연산으로 이동하며, 파일 시스템이 지원하지 않으면 예외가 난다. 감시 중인 디렉터리에 파일을 옮길 때 다른 프로세스가 반쯤 쓰인 파일을 보지 않도록 보장할 수 있다.
1
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
디렉터리는 Oracle 튜토리얼 기준으로 비어 있으면 옮길 수 있고, 비어 있지 않으면 내용물을 옮기지 않고 디렉터리만 옮길 수 있는 경우에 허용된다. UNIX에서 같은 파티션 안의 이동은 보통 이름 변경이라 내용물이 있어도 동작한다. 다른 파일 시스템으로 디렉터리 트리를 옮기려면 Files.walkFileTree로 순회하며 복사하고 지우는 방식을 써야 한다.
참고한 자료외부 출처 15
외부 출처
- stackoverflow.com/questions/38262666/springmvc-controller-public-or-default-protected
- stackoverflow.com/questions/46684838/private-method-with-springmvc
- (Spring MVC) 그래서 컨트롤러 메서드는 누가 호출하는데요? (Dispatcher Servlet, Handler Mapping, Handler Adapter, Argument Resolver)
- (JPA) 복합키 매핑하기 (@EmbeddedId, @MapsId, isNew())
- (Spring Data JPA)복합키 설정(PK가 2개 이상일 때)
- JPA의 LazyInitializationException
- (Spring Data JPA) LazyInitializationException 오류 해결 방법
- Spring Boot, JPA, LazyInitializationException 예외 설명 및 해결책 정리
- @SneakyThrows
- “Sneaky throws” in Java
- (Java) @SneakyThrows 란?(Lombok 어노테이션)
- Moving a File or Directory - The Java Tutorials
- jenkov.com/tutorials/java-nio/files.html
- java2s.com/example/java-utility-method/file-move/move-file-from-file-to-boolean-overwrite-80508.html
- support.box.com/hc/en-us/community/posts/1500000588722-Move-and-Overwrite-File
댓글
아직 댓글이 없습니다