본문 바로가기

개발공부/Java(JPA)

[JPA] JPA에서 동기(Synchronous) vs 비동기(Asynchronous)

🌐 JPA에서 동기(Synchronous) vs 비동기(Asynchronous) 방식

JPA(Java Persistence API)는 기본적으로 동기 방식으로 작동합니다. 하지만, 비동기 처리가 필요할 때는 Spring Data JPAJava의 비동기 API(ex. @Async, CompletableFuture)를 함께 사용해야 합니다.

아래에서 동기와 비동기의 개념과 JPA에서의 구현 방법을 살펴보겠습니다! 🚀


🔍 1. 동기 방식(Synchronous)

동기 처리메서드가 끝날 때까지 호출한 스레드가 대기합니다.

JPA는 기본적으로 동기적으로 데이터베이스와 통신합니다.

🛠️ 동기 예제: 기본 JPA Repository

// User 엔티티
@Entity
@Getter @Setter
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

// JPA Repository (동기)
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByName(String name);
}

🧪 동기 서비스 메서드

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public List<User> findUsersByName(String name) {
        System.out.println("동기 시작");
        List<User> users = userRepository.findByName(name); // 동기 호출
        System.out.println("동기 완료: " + users.size() + "명 조회");
        return users;
    }
}

🕹️ 호출 결과 (동기)

동기 시작
(데이터베이스 조회 진행...메인 스레드 대기)
동기 완료: 5명 조회

🔍 특징:

  • JPA는 기본적으로 동기 처리를 합니다.
  • DB 조회 시 메서드가 끝날 때까지 메인 스레드는 대기합니다.
  • CPU 연산보다 I/O 작업(DB 접근)이 많으면 비동기를 고려해야 함.

2. 비동기 방식(Asynchronous)

비동기 방식은 작업을 별도 스레드에서 실행하고, 호출한 스레드는 바로 반환됩니다.

JPA 단독으로는 비동기 기능이 없지만, Spring Data JPA@Async, **CompletableFuture**를 활용해 비동기 호출이 가능합니다.


🧑‍💻 비동기 처리: @Async와 CompletableFuture 사용

📌 Step 1: 비동기 메서드 정의

public interface UserRepository extends JpaRepository<User, Long> {

    // 동기는 List<User> 반환, 비동기는 CompletableFuture<List<User>> 반환
    @Async
    CompletableFuture<List<User>> findByName(String name);
}

📌 Step 2: 비동기 서비스 구현

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    // 비동기 메서드
    public CompletableFuture<List<User>> findUsersByNameAsync(String name) {
        System.out.println("비동기 시작");
        CompletableFuture<List<User>> future = userRepository.findByName(name);
        future.thenAccept(users -> System.out.println("비동기 완료: " + users.size() + "명 조회"));
        return future;
    }
}

📌 Step 3: @EnableAsync 설정

@Configuration
@EnableAsync
public class AsyncConfig {
}
  • *🕹️ 호출 결과 (비동기)
public class MainApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class);
        UserService userService = context.getBean(UserService.class);

        // 비동기 호출
        CompletableFuture<List<User>> future = userService.findUsersByNameAsync("John");

        System.out.println("메인 스레드는 바로 다음 작업 실행 가능");

        // 비동기 결과 대기 (테스트용)
        future.join();
    }
}

🔍 출력 결과 (비동기)

비동기 시작
메인 스레드는 바로 다음 작업 실행 가능
(데이터베이스 조회는 별도 스레드에서 진행)
비동기 완료: 5명 조회

🔄 3. 동기 vs 비동기 비교

특성 동기 방식 (Synchronous) 비동기 방식 (Asynchronous)

메서드 실행 흐름 호출 후 결과를 기다림 호출 후 즉시 반환 (작업 완료 시 콜백)
JPA 기본 설정 기본 동기 처리 @Async 및 CompletableFuture 필요
스레드 관리 메인 스레드 사용 별도 스레드 풀에서 작업 수행
성능 다수의 DB 요청 시 I/O 대기로 느려질 수 있음 I/O 작업을 비동기로 처리하여 성능 개선 가능
코드 복잡도 단순하고 직관적 CompletableFuture 체이닝으로 복잡해질 수 있음

🔔 4. 언제 비동기를 사용해야 할까?

  • CPU 바운드 작업(연산이 많은 작업) → 동기 처리 추천
  • I/O 바운드 작업(DB 조회, API 호출) → 비동기 처리로 성능 개선
  • AWS Lambda와 같은 환경에서 비동기 DB 조회 → 성능 최적화에 유리

💡팁:

  • AWS Lambda와 함께 RDS MySQL을 사용할 때, 다수의 데이터 조회가 필요하면 비동기 JPA로 성능을 개선할 수 있어요! 😎

🚀 5. 추가 최적화 팁

  • Spring Data JPA + @Async 사용 시, JPA 영속성 컨텍스트가 **기본적으로 스레드 로컬(ThreadLocal)**을 사용하므로,비동기 호출 시 @Transactional 사용 시 주의가 필요합니다.
  • 비동기 메서드는 반드시 public 접근자를 사용해야 합니다.
  • 스레드 풀 관리: ThreadPoolTaskExecutor로 스레드 풀 크기를 적절히 설정해야 성능이 최적화됩니다.

🌱 마무리

  1. JPA는 기본적으로 동기 처리합니다.
  2. 비동기 처리는 **@Async, CompletableFuture, ExecutorService*를 사용합니다.
  3. I/O 바운드 작업이 많으면 비동기 JPA를 고려해보세요!

 

 

 


Java에서는 C#의 invoke와 beginInvoke에 해당하는 개념이

동기(synchronous)비동기(asynchronous) 호출을 통해 구현됩니다.

Java에서는 C#의 **델리게이트(delegate)**와 비슷한 **java.lang.reflect.Method.invoke()**와, 비동기 작업을 위한 ExecutorService, CompletableFuture 같은 도구를 사용합니다.

아래에서 Java에서 C#의 invoke와 beginInvoke와 유사한 패턴을 살펴보겠습니다! 🚀


🛠️ 1. invoke(동기 호출) in Java

  • *Java의 Method.invoke()*는 동기적으로 메서드를 호출합니다.
import java.lang.reflect.Method;

public class InvokeExample {
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }

    public static void main(String[] args) throws Exception {
        // 클래스의 메서드를 동적으로 호출 (동기 호출)
        InvokeExample obj = new InvokeExample();
        Method method = InvokeExample.class.getMethod("greet", String.class);

        // invoke는 동기적으로 호출 → 호출이 끝나야 다음 코드 진행
        method.invoke(obj, "Java");
        System.out.println("메서드 호출 완료");
    }
}

🔍 실행 흐름:

  1. method.invoke()는 해당 메서드를 동기적으로 호출
  2. 메서드가 완료된 후에만 "메서드 호출 완료" 출력

💡포인트:

  • UI 작업 시에는 SwingUtilities.invokeLater()와 비슷한 역할

2. beginInvoke(비동기 호출) in Java

Java에는 비동기 작업을 위해 ExecutorService나 CompletableFuture를 사용합니다.

비동기 호출은 메인 스레드의 흐름을 막지 않고 백그라운드 스레드에서 작업을 처리합니다.

🧩 방법 1: ExecutorService.submit()

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BeginInvokeExample {
    public static void greet(String name) {
        System.out.println("Hello, " + name + " from " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 비동기 호출 (beginInvoke 유사)
        executor.submit(() -> greet("Java"));

        System.out.println("메인 스레드 작업 중...");
        executor.shutdown();
    }
}

🔍 실행 흐름:

  • 메인 스레드: "메인 스레드 작업 중..." 출력
  • 작업 스레드: "Hello, Java from pool-1-thread-1"

🧩 방법 2: CompletableFuture.supplyAsync() (Java 8+)

import java.util.concurrent.CompletableFuture;

public class BeginInvokeFuture {
    public static void greet(String name) {
        System.out.println("Hello, " + name);
    }

    public static void main(String[] args) {
        // 비동기 호출
        CompletableFuture.runAsync(() -> greet("Java"));

        System.out.println("메인 스레드 작업 중...");

        // 메인 스레드가 너무 빨리 종료되지 않도록 잠시 대기
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
}

🔍 실행 흐름:

  • 메인 스레드: "메인 스레드 작업 중..." 출력 후 바로 다음 작업 진행
  • 작업 스레드: "Hello, Java" 비동기 실행

💡포인트:

  • 동기적 호출과 달리 호출 즉시 반환

🔄 3. Java에서 invoke vs beginInvoke 비교

특징 invoke (동기) beginInvoke (비동기)

호출 방식 Method.invoke() ExecutorService.submit(), CompletableFuture.runAsync()
실행 흐름 호출이 완료될 때까지 대기 호출 직후 바로 반환
스레드 사용 호출한 스레드에서 실행 새로운(백그라운드) 스레드에서 실행
UI 작업 시 사용 SwingUtilities.invokeAndWait() SwingUtilities.invokeLater()
예외 발생 시점 호출 시 바로 발생 작업 완료 후 예외 발생 가능

🎯 4. 결론: Java에서 C# invoke와 beginInvoke 대체

  1. 동기 호출
    • Method.invoke() → 호출이 끝날 때까지 기다림
  2. 비동기 호출
    • ExecutorService.submit() 또는 CompletableFuture.runAsync() → 호출 즉시 반환
  3. UI 업데이트
    • Swing: invokeAndWait()(동기) / invokeLater()(비동기)
    • JavaFX: Platform.runLater()(비동기)

💡팁: AWS Lambda, Gradle, JPA를 사용하는 환경에서도 비동기 작업이 필요할 때 CompletableFuture를 잘 활용할 수 있습니다. 🚀

'개발공부 > Java(JPA)' 카테고리의 다른 글

[JPA] for문으로 save() 할 경우 생기는 이슈 및 처리  (0) 2025.02.18
[Java] Map 과 HashMap의 차이점!  (0) 2025.02.17
[Java] String VS StringBuilder 차이점  (0) 2025.02.17
JPA 란?  (1) 2025.02.13
Entity  (0) 2025.02.13