0404 ~ 0410
# 0404 ~ 0410
# 0404 - try-with-resource
자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다.
ex) InputStream, OutputStream, java.sql.Connection
자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어지기도 한다.
전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.
# try-finally
public static String firstLineOfFile(String path) throw IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
- 자원이 둘 이상이면 지저분하다.
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
# try-with-resources
이 구조를 사용하려면 해당 자원이 AutoCloseable
인터페이스를 구현해야한다.
(AutoCloseable: 단순히 void를 반환하는 close 메서드 하나만 정의한 인터페이스) 닫아야 하는 자원을 뜻하는 클래스를 작성한다면 AutoCloseable을 반드시 구현해야 한다.
// try-with-resources에서도 catch 절을 쓸 수 있다
public static String firstLineOfFile(String path) throw IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (Exception e) {
return defaultVal;
}
}
// 복수의 자원을 처리하는 try-with-resources
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
- try-with-resources를 사용할 경우 close를 알아서 호출해주며, close호출에서 예외가 발생했을 때, close에서 발생한 예외는 숨겨지고 첫번째 예외가 기록된다. 이렇게 실전에서는 예외 하나만 보존되고 여러 개의 다른 예외가 숨겨질 수 있다. 이렇게 숨겨진 예외들은 스택 추적 내역에 suppressed 꼬리표를 달고 출력된다.
# 0407 - busy-waiting
# Busy Waiting 이란
- 원하는 자원을 얻기 위해 기다리는 것이 아니라 권한을 얻을 떄까지 확인하는 것을 의미한다.
# 사용하는 경우
- 자원의 권한을 얻는데 많은 시간이 소됴되지 않는 상황인 경우.
- Context Switching 비용보다 성능적으로 더 우수한 상황인 경우.
# 단점
- 권한 획들을 위해 많은 CPU를 낭비한다
# 다른방법
- 지속적으로 확인하는 Busy Waiting이 아닌 Sleeping 이라는 방법을 사용할 수 있다.
# Sleeping 이란
- 권한을 얻기 위해 기다리는 시간을 wait queue에 실행 중인 Thread 정보를 담고 다른 Thread에게 CPU를 양보하는 것을 의미한다.
- 커널은 권한 이벤트가 발생하면 Wait queue에 담긴 Thread를 깨워 CPU를 부여합니다.
# 사용하는 경우
- 기다리는 시간이 예측이 불가능한 상황인 경우
# 단점
- wait queue에 넣는 비용 + Context Switching 비용이 드는 단점이 있다.
# 0409 - @EntityListeners
# JPA EntityListener란
하이버네이트 문서 (opens new window)에서는 JPA Entity에 이벤트가 발생할 때 콜백을 처리하고 코드를 실행하는 방법이라고 소개하고 있따.
JPA에서는 아래의 7가지 이벤트를 제공한다.
- @PrePersist : Persist(insert)메서드가 호출되기 전에 실행되는 메서드
- @PreUpdate : merge메서드가 호출되기 전에 실행되는 메서드
- @PreRemove : Delete메서드가 호출되기 전에 실행되는 메서드
- @PostPersist : Persist(insert)메서드가 호출된 이후에 실행되는 메서드
- @PostUpdate : merge메서드가 호출된 후에 실행되는 메서드
- @PostRemove : Delete메서드가 호출된 후에 실행되는 메서드
- @PostLoad : Select조회가 일어난 직후에 실행되는 메서드
# 예제
// Auditable.java
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
// MyEntityListener.java
public class MyEntityListener {
@PrePersist
public void prePersist(Object o){
if(o instanceof Auditable){
((Auditable) o).setCreatedAt(LocalDateTime.now());
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
@PreUpdate
public void preUpdate(Object o){
if(o instanceof Auditable){
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
}
// User.java
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = {MyEntityListener.class})
public class User implements Auditable{
...
@Column(updatable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
# EntityListeners에서 DI하는 방법
# 1. ApplicationContext
public class PersonEntityListener {
@Autowired
private ApplicationContext applicationContext;
@PrePersist
public void prePersist(Person person) {
PersonRepository personRepository = applicationContext.getBean(PersonRepository.class);
System.out.println("prePersist : " + personRepository);
person.setCreatedTime(LocalDateTime.now());
}
}
- ApplicationContext를 주입 받아 getBean을 통해 가져온다.
# 2. @LazyPermalink
public class PersonEntityListener {
@Lazy
@Autowired
private PersonRepository personRepository;
@PrePersist
public void prePersist(Person person) {
System.out.println("prePersist : " + personRepository);
person.setCreatedTime(LocalDateTime.now());
}
}
- @Lazy를 추가하여, context refresh 시점에는 proxy 상태였다가, 해당 Repository가 처음 사용될 때 초기화가 될 수 있게 변경
# 3. BootstrapMode Deferred or Lazy
@EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFERRED)
spring:
data:
jpa:
repositories:
bootstrap-mode: deferred
- BootstrapMode를 Deffrred로 설정하게 되면, JPaRepositories를 proxy로 생성해준다.
- 또한, Spring context가 load하는 thread와 다른 thread를 이용해서 작업이 지행되고, ContextRefreshedEvent에 trigger에 의해서 repository가 초기화가 진행된다.
- 결론은 @Lazy와 비슷하게 동작 하지만 application이 시작전에 Repository 들이 초기화가 보장되어 있고, load속도도 빨라진다.
# 참고
https://milenote.tistory.com/79 https://kangwoojin.github.io/programing/jpa-entity-listeners/