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가지 이벤트를 제공한다. image

  • @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을 통해 가져온다.
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/

Last update: September 13, 2022 21:44
Contributors: ahnjs