2026年6月4日木曜日

SpringBoot で JobRunr を使って非同期処理する方法

SpringBoot で JobRunr を使って非同期処理する方法

概要

過去にデフォルトの非同期ジョブを使ってみました
ジョブキューには Redis を使いましたが JobRunr では MySQL などいろいろなものをジョブキューに設定できます
また管理画面やスケジュール実行も備えておりジョブキューとして必須の機能を実装しています

今回は SpringBoot のプロジェクトに簡単な JobRunr を使った非同期処理を実装してみます

環境

  • macOS 26.4.1
  • openjdk 26.0.1
  • SpringBoot 4.0.6
    • jasypt-spring-boot 4.0.4
    • jackson-databind 2.21.2
    • jobrunr 8.6.1
  • gradle 9.5.1
  • VSCode 1.121.0
  • MySQL 9.6.0
  • Redis 8.6.3

インストール

  • vim build.gradle
dependencies {
       implementation 'com.fasterxml.jackson.core:jackson-databind:2.21.4'
       implementation 'org.jobrunr:jobrunr:8.6.1'
}

JobRunr 接続設定

設定は @Configuration を使って定義します
SpringApplication 配下であれば定義できるます

storageProvider でジョブキューを保存するストレージを定義します
今回は MySQL を使います
MySQL の接続先は application.properties にあるデータベースをデフォルトでは使用します (参考)

DatabaseOptions.CREATE をオプションに指定することで自動で的にジョブキューに必要なテーブルを作成してくれます

jobrunr_jobs
jobrunr_recurring_jobs
jobrunr_backgroundjobservers
jobrunr_metadata

jobActivator はワーカーがジョブを実行する際に自動的に Bean オブジェクトをワーカーが取得できるようにする設定です

jobScheduler で実際にワーカー処理を登録します
先ほどまでに作成したデータストレージやアクティベータを登録します
useBackgroundJobServer を呼び出すことで非同期ワーカーを登録します

  • vim src/main/java/com/example/demo/config/JobRunrConfig.java
package com.example.demo.config;

import javax.sql.DataSource;

import org.jobrunr.configuration.JobRunr;
import org.jobrunr.scheduling.JobScheduler;
import org.jobrunr.server.JobActivator;
import org.jobrunr.storage.StorageProvider;
import org.jobrunr.storage.StorageProviderUtils.DatabaseOptions;
import org.jobrunr.storage.sql.mysql.MySqlStorageProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JobRunrConfig {

       @Bean
       StorageProvider storageProvider(DataSource dataSource) {
               return new MySqlStorageProvider(dataSource, DatabaseOptions.CREATE);
       }

       @Bean
       JobActivator jobActivator(ApplicationContext applicationContext) {
               return applicationContext::getBean;
       }

       @Bean
       JobScheduler jobScheduler(StorageProvider storageProvider, JobActivator jobActivator) {
               return JobRunr.configure().useStorageProvider(storageProvider).useJobActivator(jobActivator)
                               .useBackgroundJobServer().initialize().getJobScheduler();
       }
}

ワーカーの作成

実際にジョブを受け取ったワーカーが処理する内容を定義します
このあと定義しますがワーカー側ではエンキュー時に登録した引数の情報をそのまま受け取ることができます

package com.example.demo.worker;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class UserCreatedWorker {

       private static final Logger LOGGER = LoggerFactory.getLogger(UserCreatedWorker.class);

       public void processUserCreated(String name, String email) {
               LOGGER.info("Processing user created job - Name: {}, Email: {}", name, email);
       }
}

サービス側の修正

エンキューする処理をサービス側に定義します
基本的にはサービスレイヤーがある場合にはサービスレイヤーでエンキュー処理を行うほうが良いです

今までは StringRedisTemplate を使ってジョブキューを実行していましたがそれを JobRunr を使うように修正します

jobScheduler を使いエンキューし userCreatedWorker を使ってデキュー時のワーカー処理を呼び出してあげるようにします

  • vim src/main/java/com/example/demo/service/UserService.java
package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;

import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
import com.example.demo.entity.User;
import com.example.demo.exception.BusinessException;
import com.example.demo.repository.UserRepository;
import com.example.demo.worker.UserCreatedWorker;

@Service
public class UserService {

        @Autowired
        private UserRepository userRepository;

        @Autowired
        private JobScheduler jobScheduler;

        @Autowired
        private UserCreatedWorker userCreatedWorker;

        public void createUser(UserRequest req) {

                // ビジネスルール
                if (userRepository.existsByEmail(req.getEmail())) {
                        throw new BusinessException("USER_ALREADY_EXISTS", "既に登録されています");
                }

                User user = new User();
                user.setName(req.getName());
                user.setEmail(req.getEmail());

                userRepository.save(user);

                // ユーザー作成後の後続処理をJobRunrで非同期実行
                jobScheduler.enqueue(() -> userCreatedWorker.processUserCreated(req.getName(), req.getEmail()));
        }

        public List<UserResponse> getAllUsers() {
                List<User> users = new ArrayList<>();
                userRepository.findAll().forEach(users::add);

                return users.stream().map(this::toResponse).toList();
        }

        private UserResponse toResponse(User user) {
                UserResponse response = new UserResponse();
                response.setName(user.getName());
                return response;
        }
}

動作確認

アプリを起動し POST リクエストを送信しましょう
ジョブがエンキューされワーカー側のロギング処理が表示されれば OK です

  • ./gradlew bootRun --args='--jasypt.encryptor.password=xxx'
  • curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail5@someemailprovider.com"}' -H 'content-type: application/json'
2026-06-02T16:43:10.805+09:00  INFO 705817 --- [demo] [roundjob-worker] c.example.demo.worker.UserCreatedWorker  : Processing user created job - Name: First, Email: someemail5@someemailprov

最後に

SpringBoot + JobRunr を試してみました
これだとデフォルトの非同期処理とあまり変わらないので次回は管理画面の導入や別のストレージプロバイダーを使ってみたいと思います

参考サイト

0 件のコメント:

コメントを投稿