개발 일지/주간 개발 일지

[03월 3주차] JPA 연관 관계 매핑, 복합 키, 제네릭 클래스에 의존성 주입

계란💕 2023. 3. 18. 18:08

에러 해결


제네릭 클래스에 의존성 주입이 가능한가?

  • 오류: @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 생성 방법

출처 -&nbsp; https://www.baeldung.com/thread-pool-java-and-guava )

  • 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 을 사용한다.
/**
 * 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 오류 발생 
<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 

https://afsdzvcx123.tistory.com/entry/Postgresql-PostgreSQL-JSON-vs-JSONB-%EA%B3%B5%ED%86%B5%EC%A0%90-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90

 

 

  • dependency를 추가하고 나서 다음과 같이 애너테이션을 적용한다. 
    • dependency: hibernate-types-52
  •  클래스에 @TypeDef 붙이기
    • Hybernate UserType을 통해 사용자가 정의한 타입을 만들 수 있다. 
    • TypeDef을 이용해서 UserType을 등록한다.  사용자 정의 타입에 짧은 이름을 등록한다. 
  • 필드에 다음과 같이 @Type을 붙이고 속성을 넣어준다. 


찾아보기