에러 해결
제네릭 클래스에 의존성 주입이 가능한가?
- 오류: @Autowired - NullpointerException
- EaiEventListener (@Service) 에서 new MyThread 호출한다.
- MyThread (extends Thread)에서 ListenerService를 멤버 변수로 선언한다.
- 그런데 MyThread 클래스에 Service 생성자 주입이 되지 않는다.
- MyThread를 스프링 컨테이너에 등록해도 해결되지 않는다. (@Component, @NoArgsConstructor을 이용해도 오류가 난다.)
- @Autowired, @RequiredArgsConstroctor 둘다 적용이 되지 않는다.
- 원인
- 제네릭 클래스 MyThread는 생성자 주입이 불가능하므로 NullpointerException 가 난다.
- 완전히 불가능한건 아니고 MyThread extends Thread<Listener> .. 이런 식으로 만들면 된다고 한다 .(by. chat GPT)
- 해결
- EaiEventListener는 @Component 를 통해 스프링 컨테이너에 빈 등록이 되므로 멤버 변수로 ListenerService를 선언해서 생성자 주입이 가능하다.
- MyThread 에 @AllArgsConstructor 을 붙여서 ListenerService를 매개변수로 받는다.
- 받은 것을 MyThread 의 멤버 변수인 ListenerService에 그 값을 넣어준다.
- 오류: BeanDefinitionStoreException,ConflictingBeanDefinitionException
- 이름 같은 Bean이 없는데 중복된다는 엉뚱한 오류가 난다.
- 그래서 해당 클래스 이름을 바꿔주면 이번에는 다른 클래스의 bean name이 중복된다는 오류 발생..
- 이런 식으로 반복된다.
- 원인
- build 관련 문제
- 해결
- maven clean 하고 다시 빌드해준다.
- 원인을 못 찾겠는 엉뚱한 오류는 빌드 관련 원인을 찾아보기
Note
@EventListener(이벤트리스너)란?
- 의존성이 강한 로직들의 레이어를 분리할 수 있다.
- 이벤트를 처리하는 간단한 방법이다. 특정 클래스를 상속하지 않고도 순수 POJO 객체만 활용해서 이벤트 프로그래밍이 가능하다.
- 두 서비스 간의 의존성을 끊고 이벤트 방식으로 필요한 처리를 각 서비스에 적절하게 위임할 수 있다.
- 서비스와 서비스 사이에서 ApplicationEventPublisher 클래스가 이벤트에 따라 연결해줌으로 순환 참조 문제가 발생하지 않도록 한다.
애플리케이션이 시작되자마자 어떤 동작을 수행하도록 지정하는 방법
- ApplicationListener<ApplicationStartedEvent>를 구현하는 클래스를 하나 만들고 @Bean을 통해 스프링 컨테이너에 등록한다.
- onApplicationEvent() 라는 메서드를 오버라이드하면 서버를 구동하자마자 안에 있는 메서드가 자동으로 실행된다.
- 아래와 같이 애플리케이션 시작하자 마자 실행된다.
Thread 생성 방법
- 1) Thread를 extends() 하는 클래스 만들기
- 2) Runnable 인터페이스를 implements 하는 클래스 만들기
- 3) ExecutorService
- 1, 2 번은 자바의 정석을 공부하면서 배운 스레드를 만드는 가장 기본적인 방법이었다. 그런데 차장님께서 ExecutorService 라는 인터페이스를 이용해서 만들라고 말씀해주셔서 새롭게 알게 된 내용을 아래에 정리했다.
- ExecutorService란?
-
- Executor 인터페이스의 하위 인터페이스로서 스레드를 관리해주는 인터페이스이다. (ThreadPool 이라고도 한다.)
- 병렬 작업 시 여러 개의 작업을 효율적으로 처리하기 위해 사용되는 Java 라이브러리이다.
- 몇 개의 스레드를 사용할 것인지 정해진 시간마다 반복할 것인지 설정 가능하다.
- 여러 개의 스레드를 생성해서 작업을 처리하고 완료되면 해당 스레드를 제거하는 작업을 일일히 해야하는 것을 쉽게 처리 가능하다.
- task만 지정해주면 알아서 Thread Pool을 이용해서 task를 실행하고 관리한다.
- Executors의 메서드
- newSingleThreadExecutor(): 오직 하나의 스레드로 처리하고 다른 스레드 생성 요청이 오면 현재 스레드가 종료될 때까지 대기한 후에 생성된다.
- newFixedThreadPool(): 매개 변수로 생성할 스레드 풀의 크기를 넣어준다. 스레드 풀의 크기 내에서 스레드가 생성되서 병렬 처리한다.
- newCachedThreadPool(): 멀티 스레드 처리를 위한 스레드 풀을 생성하되 기존에 생성한 스레드를 가능한 재사용한다.
- newScheduledThreadPool(int corePoolSize): 일정 시간이 흐른 뒤에 스레드를 실행시키는 기능이다.
- corePoolSize: 생성할 corePool 의 크기를 지정해준다.
- Thread Pool 작업 할당 메서드
- ExecutorService.execute(): 반환형이 void 라서 결과를 알 수 없음, 예외 발생 시 해당 스레드가 종료되고 스레드 풀에서 제거한 뒤, 새로운 스레드 생성하여 다른 작업을 처리한다.
- ExecutorService.submit(): task를 할당하고 Future 타입을 반환한다.Task를 인자로 준다. 예외가 발생해도 스레드가 종료되지 않고 다음 작업에 사용된다. execute()보다는 submit() 을 권장한다.
- Thread Pool 작업 종료 메서드
- ExecutorService.shutdown(): 실행 중인 모든 task가 수행되면 종료
- ExecutorService.shutdownNow(): 실행 중인 Thread들을 즉시 종료시키려고하지만 모든 Thread가 동시에 종료되는 것을 보장하지는 않고 실행되지 않은 task를 반환한다.
- 두 개의 메서드가 결합된 awaitTermination() 을 권장한다.
- 새로운 task 실행을 막고 현재 실행중인 task가 완료되기를 기다린다. 일정 시간 처리되지 않은 task는 강제로 종료시킨다.
-
Ex) Thread Pool 예제
- 어떤 리스트를 조회해서 하나의 Listener를 조회할 때마다 하나의 스레드를 생성, 실행시키는 반복문이다.
- 스레드 풀을 종료시키는 shutdown() 은 for문이 모두 끝난 다음 밖에서 실행한다.
<hide/>
UserService userService = null;
List<Listener> availableUserList = userService.getAvailableUserList();
for (User user : availableUserList) {
MyThread<Listener> thread = new MyThread(user, userService);
thread.run();
executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return (Thread) thread;
}
});
executorService.submit(thread); // 스레드 작업 실행
}
executorService.shutdown(); // 스레드 풀 종료
참고)
https://tecoble.techcourse.co.kr/post/2021-09-18-java-thread-pool/
스레드 풀 종류 https://devfunny.tistory.com/807
https://mangkyu.tistory.com/259
https://simyeju.tistory.com/m/119
Kafka 구현 방법
- dependency
- Application.yml 파일 등록
- Consumer 클래스 생성
- Properties 형태로 config 설정 (GroupId, Server IP, port 등..)
- new Consumer() 에 config를 매개변수로 넣는다.
- While문으로 무한 루프를 돌린다.
- ConsumerRecords에 있는 record에 대해서 하나씩
- 무한 루프를 이용해서 topic에 있는 메시지를 지속적으로 poll() 하도록 한다.
- postman이나 Kafka ui을 통해 message를 produce()해서 테스트할 수 있다.
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println(" value: " + record.value());
}
}
복합 키(composite key) 지정 방법
- A 테이블, B테이블에 각각 pk 가 있다.
- A, B 사이에 중간 테이블을 만들어서 이에 대한 일대일 연관 관계를 맺고 싶은 경우가 있다면?
- 1) 복합 키 지정을 위해서 Id 의 종류로 구성된 새로운 id 클래스를 만든다.
- Serializable를 구현한다.
- 세션에서 객체가 로드될 때 key의 index를 id로 사용하기 위해서는 각 객체가 Serializable 된 상태여야한다.
- 복합 키는 equals(), Hashcode()를 오버라이드해야한다.
- @EqualsAndHashCode: equals(), hashCode() 자동으로 생성해준다.
- equals(): 같은 객체인지 확인
- hashCode(): 두 객체 내부의 값이 같은지 숫자로 확인한다.
- @EqualsAndHashCode(onlyExplicitlyIncluded = true) :
- onlyExplicitlyIncluded: Only include fields and methods explicitly marked with @EqualsAndHashCode.Include.
- 영속성 컨텍스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다.
- 식별자를 비교할 때, equals(), hashcode()를 사용
- 식별자 객체의 동등성(equals)이 지켜지지 않으면 다른 엔티티가 조회되거나 엔티티를 찾을 수 없어서 관리상 문제 발생한다. => @EqualsAndHashCode 을 사용한다.
- @EqualsAndHashCode: equals(), hashCode() 자동으로 생성해준다.
- Serializable를 구현한다.
/**
* AB 테이블의 복합 키를 위한 class
*/
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class ABId implements Serializable {
private A b;
private B b;
}
- 2) 두 개의 FK를 복합 키로 갖는 클래스 생성
- A 테이블과 AB 테이블은 일대일 관계이지만 A 에서는 AB 를 필드로 갖지 않는다 .따라서, 아래와 같이 주 테이블에 일대일 단방향 매핑
- @OneToOne
- @JoinColumn(name = "A 엔티티 안에서 PK 이름")
- 주의) 대상 테이블에 외래 키 단방향은 JPA 에서 지원하지 않는다.
- B 테이블 입장에서는 AB 객체를 필드로 갖지 않는다. @ManyToOne 이므로 AB 에서만 B가 필요한다. 다대일 단방향 매핑
- @IdClass 안에 앞서 만든 Id 클래스 명을 넣는다.
- 주의) @JoinColumn이 있는데 @Column가 또 들어가면 @BeanCreationException 오류 발생
- A 테이블과 AB 테이블은 일대일 관계이지만 A 에서는 AB 를 필드로 갖지 않는다 .따라서, 아래와 같이 주 테이블에 일대일 단방향 매핑
<hide/>
@Data
@Entity
@Table
@NoArgsConstructor
@AllArgsConstructor
@Builder
@IdClass(ABId.class) // 복합 키
public class AB {
@Id
@OneToOne
@JoinColumn(name = "a_id")
private A a;
@Id
@ManyToOne
@JoinColumn(name = "B_id")
private B b;
}
제네릭 클래스(Generic class)
- 멤버 변수 T의 상한선 제한
- MyThread의 멤버변수 T는 Listener이거나 Listener의 하위 클래스(자식 클래스)여야한다.
- 멤버 변수 T의 하한선 제한
- MyThread의 멤버변수 T는 Listener이거나 Listener의 상위 클래스(조상 클래스)여야한다.
class MyThread<T extends Listener>{
private T listener;
}
class MyThread<T super Listener>{
private T listener;
}
엔티티에 Jsonb 형태의 필드 추가하기
- Json(JavaScript Object Notation): 이름 값 쌍으로 구성된 데이터 객체를 교환하기 위한 개방형 표준 형식이다.
- 데이터를 그대로 저장한다.
- Indexing 불가능
- 쓰기보다 읽기에 비용이 많이 든다.
- Jsonb(JavaScript Object Notation Binary)란?
- PostgreSQL 언어의 반구조화된 데이터를 보관하는데 사용되는 PostgreSQL 데이터 유형이다.
- 텍스트 형식의 Json 객체를 Binary 형식으로 변환한다. 불필요한 공백과 중복 키를 제거하고 키를 정렬한다. 이러한 전처리 때문에 더 많은 공간과 처리 능력을 필요로 한다.
- Json과 큰 차이점
- 데이터를 그대로 저장하지 않는다.
- 공백 제거
- Key의 순서를 보장하지 않는다.
- Indexing 가능
- 일반적으로 Json보다 디스크를 많이 사용한다. 더 많은 공간과 처리 능력을 필요로 한다. 그러나, 훨씬 더 효율적이다.
- 데이터 파싱 비용이 들지 않기 때문에 읽기 비용이 JSon보다 적게 든다.
참고) https://bitnine.net/ko/blog-postgresql/postgresql-internals-jsonb-type-and-its-indexes/?ckattempt=1
- dependency를 추가하고 나서 다음과 같이 애너테이션을 적용한다.
- dependency: hibernate-types-52
- 클래스에 @TypeDef 붙이기
- Hybernate UserType을 통해 사용자가 정의한 타입을 만들 수 있다.
- TypeDef을 이용해서 UserType을 등록한다. 사용자 정의 타입에 짧은 이름을 등록한다.
- 필드에 다음과 같이 @Type을 붙이고 속성을 넣어준다.
찾아보기
- JPA 연관 관계 매핑
- 참고) https://oranthy.tistory.com/327
- 주 테이블 vs 대상 테이블
- 제네릭 클래스
- POJO
- JPA, QueryDSL
'개발 일지 > 주간 개발 일지' 카테고리의 다른 글
[04월 3주차] Vue.js 3 과 Spring (0) | 2023.04.22 |
---|---|
[04월 2주차] JPA, Vue.js 3 기본 명령어 (0) | 2023.04.16 |
[04월 1주차] 페이징(Paging), QueryDSL, LocalDate와 String 변환 방법 (1) | 2023.04.08 |
[03월 5주차] Thread API 전체적인 흐름 파악하기, 고정 소수점과 부동 소수점 (2) | 2023.04.01 |
[03월 4주차] OkHttp 예제, Thread와 ExecutorService (1) | 2023.03.26 |